-->

测试断言说一定不能编译(Testing an assertion that something mu

2019-07-20 16:13发布

问题

当我与支持的类型级编程库的工作,我经常发现自己写类似(从下面的评论的例子所提交的奇怪循环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]

我很惊讶地发现,这个实现的一个类型级的uniqueHList没有工作,因为无形会高兴地发现一个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])

我要这个。 我的问题是,是否有人认为远程支持这样的事情,非常斯卡拉任何测试库或框架的人都知道,但我会解决任何东西。

Answer 1:

没有一个框架,但豪尔赫·奥尔蒂斯( @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兼容的做法是紧随其后。



Answer 2:

ScalaTest 2.1.0具有以下语法断言 :

assertTypeError("val s: String = 1")

而对于匹配器 :

"val s: String = 1" shouldNot compile


Answer 3:

你知道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

这不是你的问题再合适不过了(因为你没有断言来指定你的测试用例),但可能是一种方法和/或给你一个良好的开端。



Answer 4:

根据所提供的链接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]") 
}


Answer 5:

compileError在μTest宏观做到了这一点:

compileError("true * false")
// CompileError.Type("value * is not a member of Boolean")

compileError("(}")
// CompileError.Parse("')' expected but '}' found.")


文章来源: Testing an assertion that something must not compile