测试不能编译的断言 [英] Testing an assertion that something must not compile

查看:174
本文介绍了测试不能编译的断言的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

问题

当我使用支持类型级编程的库时,我经常发现自己在写如下注释(来自 Shapeless 存储库中的示例:

/**
 * 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]

这是表示有关这些方法行为的某些事实的一种非常粗糙的方法,我们可以想象要使这些断言更加正式化-用于单元测试或回归测试等.

要给出一个具体的示例,说明为什么在像Shapeless这样的库的上下文中这可能有用,几天前,我写了以下内容作为对一个错误-或至少是一些类似于bug的东西-并且它已修复.. >

更笼统地说,我们可以想像一下要检查在我对FilterAux 应该如何与单元测试类似工作的期望中所隐含的不变式,这听起来很奇怪正在谈论这样的类型级代码测试,以及最近关于类型 vs.测试的相对优点的所有争论.

我的问题

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

我可以想象的FilterAux情况的一种方法是使用旧的 implicit-argument-with-null-default技巧:

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

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

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

尽管如此,以下内容将更加方便和富有表现力:

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

我想要这个.我的问题是,是否有人知道任何支持远程支持的测试库或框架,这对Scala来说是理想的选择,但是我会解决的.

解决方案

不是框架,而是Jorge Ortiz( @JorgeO )提到了他在2012年添加到NEScala的Foursquare Rogue库测试中的一些实用程序,这些程序支持非编译测试:您可以找到示例 @rolandkuhn )添加了类似的机制,这次使用的是Scala 2.10运行时编译,到测试Akka类型的频道.

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

更新

自从回答了这个问题,由于Stefan Zeiger( @StefanZeiger ),此处适用于2.9.x an example presented by Paul Snively at 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)

Or this, from an example in the Shapeless repository:

/**
 * 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]

This is a very rough way of indicating some fact about the behavior of these methods, and we could imagine wanting to make these assertions more formal—for unit or regression testing, etc.

To give a concrete example of why this might be useful in the context of a library like Shapeless, a few days ago I wrote the following as a quick first attempt at an answer to this question:

import shapeless._

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

Where the intention is that this will compile:

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

While this will not:

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

I was surprised to find that this implementation of a type-level unique for HList didn't work, because Shapeless would happily find a FilterAux instance in the latter case. In other words, the following would compile, even though you'd probably expect it not to:

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

In this case, what I was seeing was a bug—or at least something bug-ish—and it has since been fixed.

More generally, we can imagine wanting to check the kind of invariant that was implicit in my expectations about how FilterAux should work with something like a unit test—as weird as it may sound to be talking about testing type-level code like this, with all the recent debates about the relative merit of types vs. tests.

My question

The problem is that I don't know of any kind of testing framework (for any platform) that allows the programmer to assert that something must not compile.

One approach that I can imagine for the FilterAux case would be to use the old implicit-argument-with-null-default trick:

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

Which would let you write the following in your unit test:

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

The following would be a heck of a lot more convenient and expressive, though:

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

I want this. My question is whether anyone knows of any testing library or framework that supports anything remotely like it—ideally for Scala, but I'll settle for anything.

解决方案

Not a framework, but Jorge Ortiz (@JorgeO) mentioned some utilities he added to the tests for Foursquare's Rogue library at NEScala in 2012 which support tests for non-compilation: you can find examples here. I've been meaning to add something like this to shapeless for quite a while.

More recently, Roland Kuhn (@rolandkuhn) has added a similar mechanism, this time using Scala 2.10's runtime compilation, to the tests for Akka typed channels.

These are both dynamic tests of course: they fail at (test) runtime if something that shouldn't compile does. Untyped macros might provide a static option: ie. a macro could accept an untyped tree, type check it and throw a type error if it succeeds). This might be something to experiment with on the macro-paradise branch of shapeless. But not a solution for 2.10.0 or earlier, obviously.

Update

Since answering the question, another approach, due to Stefan Zeiger (@StefanZeiger), has surfaced. This one is interesting because, like the untyped macro one alluded to above, it is a compile time rather than (test) runtime check, however it is also compatible with Scala 2.10.x. As such I think it is preferable to Roland's approach.

I've now added implementations to shapeless for 2.9.x using Jorge's approach, for 2.10.x using Stefan's approach and for macro paradise using the untyped macro approach. Examples of the corresponding tests can be found here for 2.9.x, here for 2.10.x and here for macro paradise.

The untyped macro tests are the cleanest, but Stefan's 2.10.x compatible approach is a close second.

这篇关于测试不能编译的断言的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆