问题
当我与支持的类型级编程库的工作,我经常发现自己写类似(从下面的评论的例子所提交的奇怪循环2012保罗Snively ):
// 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]
这是表示对这些方法的行为的一些事实的一个非常粗略的方式,我们可以想像想使这些说法更正规的单位或回归测试等。
为了让的这可能是为什么像无形库的上下文中的具体例子,前几天我写了下面作为一个答案,一个快速的第一次尝试了这个问题 :
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]
我很惊讶地发现,这个实现的一个类型级的unique
的HList
没有工作,因为无形会高兴地发现一个FilterAux
在后一种情况下的实例。 换句话说,下面会进行编译,即使你可能希望它不要:
implicitly[FilterAux[Char :: Char :: HNil, Char, Char :: HNil]]
在这种情况下,我所看到的是一个bug或者至少一些错误十岁上下,而且它已经被修复 。
更一般地,我们可以想像想检查的那种不变的,这是我如何预期隐含的FilterAux
应该像一个单元测试,怪异,因为它听起来可能是谈论像这样的测试类型级别的代码工作,具有所有关于类型与测试相对益处的最近的辩论。
我的问题
问题是,我不知道任何一种测试框架(在任何平台),允许程序员断言说一定不能编译的。
一种方法是我能想象的FilterAux
情况下会使用旧的隐含参数的,与无效默认绝招 :
def assertNoInstanceOf[T](implicit instance: T = null) = assert(instance == null)
这将让你写在你的单元测试如下:
assertNoInstanceOf[FilterAux[Char :: Char :: HNil, Char, Char :: HNil]]
以下将是一个很大的挫折感更加方便和表现,虽然:
assertDoesntCompile(('a' :: 'b' :: HNil).unique[Char])
我要这个。 我的问题是,是否有人认为远程支持这样的事情,非常斯卡拉任何测试库或框架的人都知道,但我会解决任何东西。
没有一个框架,但豪尔赫·奥尔蒂斯( @JorgeO )提到一些公用事业他补充说在2012年支持非编译测试测试在NEScala Foursquare的盗贼库:你可以找到例子在这里 。 我一直像这样添加了一些无形的相当长一段时间。
最近,罗兰·库恩( @rolandkuhn )又增加了一个类似的机制,这次使用的Scala 2.10的运行时编译,到了阿卡输入通道测试 。
这些当然都动载试验:他们未能在(测试)运行时,如果事情不应该做编译。 非类型化宏可能会提供一个静态选项:即。 宏可以接受无类型树,类型检查它,如果它成功抛出一个类型的错误)。 这可能是一些与实验上的无形的宏观天堂分支。 但不适合2.10.0或更早的版本,显然是一个解决方案。
更新
因为回答这个问题,另一种方法,由于斯特凡Zeiger( @StefanZeiger ), 已经浮出水面 。 这一个是有趣的,因为,像无类型宏一个上面提到的,它是一个编译时间,而不是(测试)运行时检查,但它也与斯卡拉2.10.x.兼容 因此,我认为最好是罗兰的做法。
现在我已经加入到实现无形的使用豪尔赫的做法2.9.x ,对于使用斯特凡的做法2.10.x和使用非类型化的宏观方法宏天堂 。 相应测试的例子可以发现这里2.9.x , 这里2.10.x和这里的宏天堂 。
无类型宏测试是最干净的,但斯特凡的2.10.x兼容的做法是紧随其后。
ScalaTest 2.1.0具有以下语法断言 :
assertTypeError("val s: String = 1")
而对于匹配器 :
"val s: String = 1" shouldNot compile
你知道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
这不是你的问题再合适不过了(因为你没有断言来指定你的测试用例),但可能是一种方法和/或给你一个良好的开端。
根据所提供的链接Miles Sabin
我能够用阿卡版本
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)
}
}
然后在我的测试中,我用它像这样
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
在μTest宏观做到了这一点:
compileError("true * false")
// CompileError.Type("value * is not a member of Boolean")
compileError("(}")
// CompileError.Parse("')' expected but '}' found.")