测试一些必须无法编译的断言

Tra*_*own 46 testing types scala type-level-computation shapeless

问题

当我使用支持类型级编程的库时,我经常会发现自己编写如下的注释(来自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)
Run Code Online (Sandbox Code Playgroud)

或者这,从一个例子中的无形资源库:

/**
 * 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]
Run Code Online (Sandbox Code Playgroud)

这是表明这些方法行为的一些非常粗略的方式,我们可以想象想要使这些断言更正式 - 用于单位或回归测试等.

为了给出一个具体的例子,说明为什么这可能在像Shapeless这样的库的上下文中有用,几天前我写了以下内容作为对这个问题的答案的快速第一次尝试:

import shapeless._

implicit class Uniqueable[L <: HList](l: L) {
  def unique[A](implicit ev: FilterAux[L, A, A :: HNil]) = ev(l).head
}
Run Code Online (Sandbox Code Playgroud)

意图是这将编译:

('a' :: 'b :: HNil).unique[Char]
Run Code Online (Sandbox Code Playgroud)

虽然这不会:

('a' :: 'b' :: HNil).unique[Char]
Run Code Online (Sandbox Code Playgroud)

我很惊讶地发现这种类型级别的unique实现HList不起作用,因为FilterAux在后一种情况下,Shapeless会愉快地找到一个实例.换句话说,即使您可能期望它不会:

implicitly[FilterAux[Char :: Char :: HNil, Char, Char :: HNil]]
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我看到的是一个错误 - 至少是一些bug-ish-并且它已被修复.

更一般地,我们可以想像想检查的那种不变的,这是我如何期待隐含FilterAux 应该有类似的单位工作,测试怪异,因为它听起来可能是谈论像这样的测试类型级别的代码,所有的最近关于类型测试的相对价值的争论.

我的问题

问题是我不知道任何类型的测试框架(对于任何平台)允许程序员断言某些东西不能编译.

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

def assertNoInstanceOf[T](implicit instance: T = null) = assert(instance == null)
Run Code Online (Sandbox Code Playgroud)

哪个可以让您在单元测试中编写以下内容:

assertNoInstanceOf[FilterAux[Char :: Char :: HNil, Char, Char :: HNil]]
Run Code Online (Sandbox Code Playgroud)

以下将是一个更方便和富有表现力的方面:

assertDoesntCompile(('a' :: 'b' :: HNil).unique[Char])
Run Code Online (Sandbox Code Playgroud)

我要这个.我的问题是,是否有人知道任何支持远程的测试库或框架 - 最好是Scala,但我会满足于任何事情.

Mil*_*bin 25

不是框架,但Jorge Ortiz(@JorgeO)提到了他在2012年NEScala的Foursquare Rogue库测试中添加的一些实用工具,它们支持非编译测试:你可以在这里找到例子.我一直想把这样的东西添加到无形状中一段时间​​.

最近,Roland Kuhn(@rolandkuhn)添加了一个类似的机制,这次使用Scala 2.10的运行时编译,来测试Akka类型的频道.

当然,这些都是动态测试:如果不应该编译的话,它们会在(测试)运行时失败.无类型的宏可能提供静态选项:即.一个宏可以接受一个非类型化的树,键入检查它并在成功时抛出一个类型错误).这可能是在无形的宏观天堂分支上进行试验的东西.但显然不是2.10.0或更早版本的解决方案.

更新

自回答这个问题以来,由于Stefan Zeiger(@StefanZeiger)的另一种方法已经浮出水面.这个很有趣,因为像上面提到的无类型宏一样,它是编译时而不是(测试)运行时检查,但它也与Scala 2.10.x兼容.因此我认为这比罗兰的方法更可取.

我现在已经使用Jorge的方法2.9.x添加了无形的实现,使用Stefan的方法2.10.x使用无类型宏方法的宏天堂.相应测试的例​​子可以在这里找到2.9.x,这里是2.10.x,这里是宏天堂.

无类型的宏测试是最干净的,但Stefan的2.10.x兼容方法紧随其后.

  • 你最后一段中的建议[作品](https://gist.github.com/travisbrown/5066283).如果你不想通过这种方式实现一系列测试来对'topic/macro-paradise`提出拉取请求,那么你现在最好阻止我.:) (5认同)

Bil*_*ers 20

ScalaTest 2.1.0具有以下Assertions语法:

assertTypeError("val s: String = 1")
Run Code Online (Sandbox Code Playgroud)

对于Matchers:

"val s: String = 1" shouldNot compile
Run Code Online (Sandbox Code Playgroud)


rin*_*ius 9

你知道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.
*/
Run Code Online (Sandbox Code Playgroud)

它能够检查这个源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

它不适合您的问题(因为您没有根据断言指定您的测试用例),但可能是一种方法和/或为您提供一个良好的开端.


EEC*_*LOR 6

根据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)
  }
}
Run Code Online (Sandbox Code Playgroud)

然后在我的测试中我像这样使用它

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]") 
}
Run Code Online (Sandbox Code Playgroud)