testing一个不能编译的断言

问题

当我使用支持types级编程的库时,我经常发现自己写下如下的评论(从Paul Snively在Strange Loop 2012上提供的例子 ):

// But these invalid sequences don't compile: // isValid(_3 :: _1 :: _5 :: _8 :: _8 :: _2 :: _8 :: _6 :: _5 :: HNil) // isValid(_3 :: _4 :: _5 :: _8 :: _8 :: _2 :: _8 :: _6 :: HNil) 

或者,从无形存储库的一个例子 来看 :

 /** * If we wanted to confirm that the list uniquely contains `Foo` or any * subtype of `Foo`, we could first use `unifySubtypes` to upcast any * subtypes of `Foo` in the list to `Foo`. * * The following would not compile, for example: */ //stuff.unifySubtypes[Foo].unique[Foo] 

这是一个非常粗糙的方式来表明这些方法的行为的一些事实,我们可以想象想要这些断言更正式 – 单位或回归testing等。

为了给出一个具体的例子,说明为什么在像Shapeless这样的图书馆的背景下这可能是有用的,前几天我写了以下内容作为对这个问题的一个答案的第一次尝试:

 import shapeless._ implicit class Uniqueable[L <: HList](l: L) { def unique[A](implicit ev: FilterAux[L, A, A :: HNil]) = ev(l).head } 

如果这样做的目的是编译:

 ('a' :: 'b :: HNil).unique[Char] 

虽然这不会:

 ('a' :: 'b' :: HNil).unique[Char] 

我很惊讶地发现这种HList unique的types级别unique实现不起作用,因为Shapeless将在后一种情况下愉快地findFilterAux实例。 换句话说,即使你可能期望它不会:

 implicitly[FilterAux[Char :: Char :: HNil, Char, Char :: HNil]] 

在这种情况下,我所看到的是一个错误,或者至less是一些错误,现在已经修复了 。

更一般地说,我们可以想象想要检查一下我所期望的FilterAux 应该如何像unit testing一样工作的那种不variables – 这FilterAux像是在讨论像这样的types级代码testing。所有最近关于typestesting相对优点的争论。

我的问题

问题是,我不知道任何一种testing框架(对于任何平台),它允许程序员断言一定不能编译

我可以想象的FilterAux案例中的一种方法是使用旧的隐式参数与空默认技巧 :

 def assertNoInstanceOf[T](implicit instance: T = null) = assert(instance == null) 

哪个可以让你在你的unit testing中写下如下内容:

 assertNoInstanceOf[FilterAux[Char :: Char :: HNil, Char, Char :: HNil]] 

但是,下面的内容会更加方便和expression:

 assertDoesntCompile(('a' :: 'b' :: HNil).unique[Char]) 

我要这个。 我的问题是,是否有人知道任何testing库或框架,支持任何远程喜欢它 – 理想的斯卡拉,但我会解决任何事情。

不是一个框架,但Jorge Ortiz( @JorgeO )提到了他在2012年在NEScala上为Foursquare的Rogue库添加的一些实用工具,它支持非编译testing:你可以在这里find示例。 我一直在这样的意思添加这样的东西在一段时间内不成形。

最近,Roland Kuhn( @rolandkuhn )也join了类似的机制,这次使用Scala 2.10的运行时编译来testingAkkatypes的通道 。

当然,这些都是dynamictesting:如果不能编译的话,它们在(testing)运行时会失败。 无types的macros可能会提供一个静态选项:即。 一个macros可以接受一个无types的树,types检查它,并抛出一个types错误,如果成功的话)。 这可能是在无形的macros观天堂分支上试验的东西。 但显然,不是2.10.0或更早版本的解决scheme。

更新

自从回答这个问题以来,由于斯蒂芬· 泽格 (Stefan Zeiger)( @StefanZeiger ) 出现的另一种方法已经出现了 。 这一点很有趣,因为像上面提到的无typesmacros一样,这是一个编译时而不是(testing)运行时检查,但是它也与Scala 2.10.x兼容。 因此,我认为罗兰的做法更可取。

现在,我已经使用Jorge的方法为使用Stefan的方法的2.10.x以及使用无types的macros观方法的macros观天堂添加了用于2.9.x的不成形实现。 相应testing的例​​子可以在这里find2.9.x , 这里是2.10.x , 这里是macros观天堂 。

无types的macrostesting是最干净的,但Stefan的2.10.x兼容的方法是紧随其后的。

ScalaTest 2.1.0对于断言有以下语法:

 assertTypeError("val s: String = 1") 

对于Matchers :

 "val s: String = 1" shouldNot compile 

你知道Scala项目中的partest吗? 例如CompilerTest具有以下文档:

 /** For testing compiler internals directly. * Each source code string in "sources" will be compiled, and * the check function will be called with the source code and the * resulting CompilationUnit. The check implementation should * test for what it wants to test and fail (via assert or other * exception) if it is not happy. */ 

它能够检查是否这个源https://github.com/scala/scala/blob/master/test/files/neg/divergent-implicit.scala将有这个结果https://github.com/scala /scala/blob/master/test/files/neg/divergent-implicit.check

这不是一个完美的适合你的问题(因为你没有按照断言来指定你的testing用例),但可能是一个方法和/或给你一个开始。

根据Miles Sabin提供的链接,我可以使用akka版本

 import scala.tools.reflect.ToolBox object TestUtils { def eval(code: String, compileOptions: String = "-cp target/classes"): Any = { val tb = mkToolbox(compileOptions) tb.eval(tb.parse(code)) } def mkToolbox(compileOptions: String = ""): ToolBox[_ <: scala.reflect.api.Universe] = { val m = scala.reflect.runtime.currentMirror m.mkToolBox(options = compileOptions) } } 

然后在我的testing中,我用这个

 def result = TestUtils.eval( """|import ee.ui.events.Event |import ee.ui.events.ReadOnlyEvent | |val myObj = new { | private val writableEvent = Event[Int] | val event:ReadOnlyEvent[Int] = writableEvent |} | |// will not compile: |myObj.event.fire |""".stripMargin) result must throwA[ToolBoxError].like { case e => e.getMessage must contain("value fire is not a member of ee.ui.events.ReadOnlyEvent[Int]") } 

compileError中的compileErrormacros只是这样做的:

 compileError("true * false") // CompileError.Type("value * is not a member of Boolean") compileError("(}") // CompileError.Parse("')' expected but '}' found.")