【问题标题】:How do I write a scala unit test that ensures compliation fails?如何编写确保编译失败的 scala 单元测试?
【发布时间】:2013-12-05 12:42:08
【问题描述】:

有没有办法编写类似“单元测试”的东西来确保某些代码不会编译?

我为什么想要这样的东西?两个原因。

1) 检查我的 API 的类型安全性。我想要一种方法来确保如果有人传入一个错误的值,你会得到一个编译器错误,而不仅仅是一个运行时错误。显然,我可以只运行编译器并检查错误,但是在单元测试中将其形式化有利于避免回归和文档。

例如,考虑这个测试。有一些我用来检查类型安全性的注释掉的代码: https://github.com/squito/boxwood/blob/master/core/src/test/scala/com/quantifind/boxwood/EnumUnionTest.scala#L42 (第 42 和 48 行——在第 34 行我调用了一个不同的 API,它有一个运行时异常,我可以检查)

实际上,我花了一些时间才得到正确的类型安全,所以这些都是重要的检查。现在,如果我去修改底层实现,我不能只运行我的测试套件——我还必须记得取消注释这些行并检查编译器错误。

2) 测试宏的错误处理。如果宏有一些错误的输入,它应该会导致编译器错误。同样的问题,同样希望在易于运行的测试套件中使用它。

我使用 ScalaTest,但我很高兴这里有任何单元测试框架的解决方案。

【问题讨论】:

标签: unit-testing scala


【解决方案1】:

正如我在上面的评论中所指出的,Shapeless 2.0(尚未发布,但目前作为里程碑可用)基于 Stefan Zeiger 的解决方案,对您正在寻找的功能进行了非常好的实现。我已在您的项目here 中添加了一个演示(请注意,我必须更新到 Scala 2.10,因为此解决方案使用宏)。它的工作原理是这样的:

import shapeless.test.illTyped

//this version won't even compile
illTyped("getIdx(C.Ooga)")

//We can have multiple enum unions exist side by side
import Union_B_C._
B.values().foreach {b => Union_B_C.getIdx(b) should be (b.ordinal())}
C.values().foreach {c => Union_B_C.getIdx(c) should be (c.ordinal() + 2)}

//Though A exists in some union type, Union_B_C still doesn't know about it,
// so this won't compile
illTyped("""
  A.values().foreach {a => Union_B_C.getIdx(a) should be (a.ordinal())}
""")

如果我们将第二次调用illTyped 中的代码更改为可以编译的代码:

B.values().foreach {a => Union_B_C.getIdx(a) should be (a.ordinal())}

我们会得到以下编译错误:

[error] .../EnumUnionTest.scala:56: Type-checking succeeded unexpectedly.
[error] Expected some error.
[error]     illTyped("""
[error]             ^
[error] one error found
[error] (core/test:compile) Compilation failed

如果你更喜欢失败的测试,你可以很容易地适应the implementation in Shapeless。请参阅Miles's answermy previous question 进行一些补充讨论。

【讨论】:

  • 感谢 Travis,对于重复的问题,我们深表歉意。通过为我修改我的代码,您超越了一切!另外,请参阅下面我的回答,关于在 repl 中使用它的问题,好奇你是否对此有任何想法。
【解决方案2】:

Scalatest 也可以做到这一点。

Checking that a snippet of code does not compile

通常在创建库时,您可能希望确保某些 代表潜在“用户错误”的代码安排不 编译,以便您的库更耐错误。 ScalaTest Matchers trait 为此目的包括以下语法:

"val a: String = 1" shouldNot compile

如果你想确保一个 由于类型错误,代码的 sn-p 无法编译(相反 语法错误),使用:

"val a: String = 1" shouldNot typeCheck

请注意,shouldNot typeCheck 语法只有在给定的 sn-p 代码成功时才会成功 由于类型错误而无法编译。仍然会导致语法错误 在一个被抛出的TestFailedException

如果你想声明一段 sn-p 代码确实可以编译,你可以使 更明显的是:

"val a: Int = 1" should compile

虽然前面三个构造 用宏来实现,这些宏在编译时确定是否 由字符串表示的代码的 sn-p 是否编译, 错误在运行时报告为测试失败。

【讨论】:

    【解决方案3】:

    特拉维斯布朗的回答是绝对正确的。为了完整起见,我想补充一点,这也适用于测试宏,如 here 所示。

    一个小问题:illTyped 检查似乎在 repl 中不起作用。即使给定的表达式进行类型检查,它也不会抛出错误。但不要被它骗了,它确实很好用。

    > test:console
    Welcome to Scala version 2.10.2 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_65).
    Type in expressions to have them evaluated.
    Type :help for more information.
    
    scala> def foo(s: String) = s
    foo: (s: String)String
    
    scala> import shapeless.test.illTyped
    import shapeless.test.illTyped
    
    scala> foo(1)
    <console>:10: error: type mismatch;
     found   : Int(1)
     required: String
                  foo(1)
                      ^
    
    scala> illTyped("""foo(1)""")
    
    scala> illTyped("""foo("hi there")""")  // <--- works, but shouldn't!
    

    【讨论】:

    • 问题在于,在 REPL 中,代码被类型检查的上下文不一定是您所期望的。 illTyped("2 + 2") 将按预期失败,使用 REPL 之外定义的任何可用类的良好类型代码也会失败。但是,宏上下文看不到foo 的定义,因此它认为foo("hi there") 类型错误并让它过去。我承认这很令人困惑,但这完全是一个 REPL 问题。
    猜你喜欢
    • 2022-11-04
    • 2020-12-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-03
    • 2019-01-28
    相关资源
    最近更新 更多