我在测试中有这种方便的方法:
def assertFormat[T: SexpFormat](start: T,expect: Sexp): Unit = { val sexp = start.toSexp assert(sexp === expect,s"${sexp.compactPrint} was not ${expect.compactPrint}") expect.convertTo[T] should be(start) }
这基本上是一个运行断言模式的便利,我做了很多.
由于对SexpFormat [T]的隐含要求,不可能将其重写为Matcher(尽管我有兴趣听到如何做到这一点,不要求我在foo中写入MyFormat类型roundTrip [MyFormat] (…))
如果在此实用程序方法中任何测试失败,则scalatest会将assertFormat的内部标记为测试失败的原因.但我真的希望scalatest能够检测到这种方法的调用者是测试的原因.我怎样才能做到这一点?
即电流输出是
[info] - should support custom missing value rules *** FAILED *** [info] SexpNil did not equal SexpCons(SexpSymbol(:duck),SexpCons(SexpNil,SexpNil)) nil was not (:duck nil) (FormatSpec.scala:11) [info] org.scalatest.exceptions.TestFailedException: [info] at org.scalatest.Assertions$class.newAssertionFailedException(Assertions.scala:529) [info] at org.scalatest.FlatSpec.newAssertionFailedException(FlatSpec.scala:1691) [info] at org.scalatest.Assertions$AssertionsHelper.macroAssert(Assertions.scala:502) [info] at org.ensime.sexp.formats.FormatSpec$class.assertFormat(FormatSpec.scala:11) [info] at org.ensime.sexp.formats.test.FamilyFormatsSpec.assertFormat(FamilyFormatsSpec.scala:151) [info] at org.ensime.sexp.formats.test.FamilyFormatsSpec.roundtrip(FamilyFormatsSpec.scala:156) [info] at org.ensime.sexp.formats.test.FamilyFormatsSpec$$anonfun$12.apply(FamilyFormatsSpec.scala:222) [info] at org.ensime.sexp.formats.test.FamilyFormatsSpec$$anonfun$12.apply(FamilyFormatsSpec.scala:221)
FormatSpec.scala:11是我定义的assertFormat的地方.真正的失败在于FamilyFormatsSpec.scala:222(它正在调用另一种方便方法FamilyFormatsSpec.scala:156)
解决方法
通过在自定义断言中采用隐式org.scalactic.source.Position,可以在ScalaTest 3.0中实现这一点.无论何时调用assertFormat方法,该位置都将被计算(通过宏),并且该位置将由assertFormat内的assert和matcher表达式获取.它的外观如下:
import org.scalactic.source def assertFormat[T: SexpFormat](start: T,expect: Sexp)(implicit pos: source.Position): Unit = { val sexp = start.toSexp assert(sexp === expect,s"${sexp.compactPrint} was not ${expect.compactPrint}") expect.convertTo[T] should be(start) }
以下示例说明了它.如果您在类路径上有ScalaTest 3.0,只需:将以下文件加载到Scala REPL中:
:paste import org.scalatest._ import org.scalactic._ import Matchers._ case class Sexp(o: Any) { def compactPrint: String = o.toString def convertTo[T: SexpFormat]: Sexp = implicitly[SexpFormat[T]].convertIt(o) override def toString = "I'm too sexp for my shirt." } trait SexpFormat[T] { def convertIt(o: Any): Sexp = new Sexp(o) } implicit class Sexpify(o: Any) { def toSexp: Sexp = new Sexp(o) } implicit def universalSexpFormat[T]: SexpFormat[T] = new SexpFormat[T] {} def assertFormat[T: SexpFormat](start: T,expect: Sexp): Unit = { val sexp = start.toSexp assert(sexp === expect,s"${sexp.compactPrint} was not ${expect.compactPrint}") expect.convertTo[T] should be(start) } import org.scalatest.exceptions.TestFailedException val before = intercept[TestFailedException] { assertFormat(1,new Sexp) } println(s"${before.failedCodeStackDepth} - This stack depth points to the assert call inside assertFormat") import org.scalactic.source def betterAssertFormat[T: SexpFormat](start: T,s"${sexp.compactPrint} was not ${expect.compactPrint}") expect.convertTo[T] should be(start) } val after = intercept[TestFailedException] { betterAssertFormat(1,new Sexp) } println(s"${after.failedCodeStackDepth} - This stack depth is the betterAssertFormat call itself in your test code")
它将打印:
3 - This stack depth points to the assert call inside assertFormat 4 - This stack depth is the betterAssertFormat call itself in your test code