Проверка неожиданных исключений с помощью ScalaTest + ScalaCheck

Я пытаюсь написать свойство, которое в основном гласит: «оно должно либо не генерировать исключение, либо генерировать одно из списка возможных исключений», используя ScalaTest, и это GeneratorDrivenPropertyChecks, которое, в свою очередь, использует scalatest. Проблема в том, что я не смог объединить noException с логическим или, поэтому лучшее, что я мог сделать, это этот уродливый тест:

it should "not throw unexpected exceptions" in {
  forAll { (s: String) =>
    try { parse(s) }
    catch { case e:Throwable => e shouldBe a [ParsingFailedException] }
    true shouldBe true // prevent compile error
}}

То, что я хотел бы видеть вместо этого, будет больше похоже на

it should "not throw unexpected exceptions" in {
  forAll { (s: String) => {
    (noException should Be thrownBy) or (a [ParsingFailedException] shouldBe thrownBy) { parse(s)  }
}}

person c089    schedule 01.12.2014    source источник
comment
Является ли эта разница недетерминированной? Потому что в противном случае я бы предложил разделить ваше тестирование на два сценария: один для случаев, когда не следует создавать исключения, и один для одного из списка. Таким образом, другим читателям также будет яснее знать, что должно произойти, когда   -  person Diego Martinoia    schedule 01.12.2014


Ответы (1)


Поскольку мы хотим использовать исключение как значение, а не управлять исключительным потоком, мы могли бы использовать scala.util.Try и делать утверждения относительно значения Try. Таким образом, вместо вызова parse(s) мы могли бы вызвать Try(parse(s)).

К сожалению, поскольку теперь значения упакованы, я не могу придумать чистый способ утверждения предикатов для них, кроме как написать собственный сопоставитель. Для вашего конкретного примера пользовательский сопоставитель может выглядеть следующим образом:

class FailAsMatcher[E <: Throwable](t: Class[E]) extends Matcher[Try[Any]] {
  def apply(theTry: Try[Any]) =
    MatchResult(
      theTry.isFailure && (theTry.failed.get.getClass == t),
      s"did not fail as a $t",
      s"did fail as a $t"
    )
}

Поскольку Try является ковариантным, мы можем определить общий тип нашего пользовательского сопоставления как Try[Any]. Сопоставитель сопоставляет только экземпляры Failure[Any], которые не работают, за исключением предоставленного типа. Теперь мы могли бы назвать это так:

it should "not throw unexpected exceptions" in {
  forAll { (s: String) =>
    Try(parse(s)) should ( be ('success) or failAs(classOf[ParsingFailedException]))
  }
}

def failAs[E <: Throwable](t: Class[E]) = new FailAsMatcher[E](t)

Теперь, если произойдет непредвиденное исключение, ошибка может выглядеть следующим образом:

TestFailedException was thrown during property evaluation.
  Message: Failure(java.lang.NullPointerException) was not success, and did not fail as a class ParsingFailedException
person Kulu Limpa    schedule 01.12.2014
comment
Возможно, мы могли бы изменить это, чтобы принимать только общий параметр, то есть Try(parse(s)) should ( be ('success) or failAs[ParsingFailedException]), но я не уверен, как этого добиться. - person Kulu Limpa; 02.12.2014
comment
Спасибо за это, не подумал о реализации пользовательского сопоставления :) - person c089; 02.12.2014