JUnit 是个很著名的飞行模式测试框架,即使到了 Scala 中还是免不了要用 JUnit Style 的测试方式,基于 Spec 的方式并不处处行得通,比如想要在 Scala 中使用 JMockit 框架时。
JUnit 提供给我们有两个扩展点,Runner 和 Rule, Runner 扩展点一般被各种框架劫持了,自己搞个 @RunWith(SomeRunner.class)
可能让你无法在测试中应用框架。于是剩下了 Rule 是个更自由的扩展点,这里不讲述怎么定制自己的 Rule,而是怎么用它,怎么在 Scala 中用它。之前的一篇 JUnit 4 如何正确测试异常 中使用了 ExpectedException
这个 Rule。
Rule 的要求是: Annotates fields that reference rules or methods that return a rule. A field must be public, not static, and a subtype of TestRule
(preferred) or MethodRule
. A method must be public, not static, and must return a subtype of TestRule
(preferred) or MethodRule
. 属性或方法必须是 public 非静态的,它们的类型或返回类型必须分别是 TestRule 和 MethedRule。
这里尝试以 Java 的方式使用另一个 Rule,TestName, 可以得到当前测试方法的名称
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import org.junit.{Assert, Test, Rule} import org.junit.rules.TestName class RuleTest { @Rule val name = new TestName @Test def testTestName { Assert.assertEquals("testTestName", name.getMethodName()); } } |
运行上面的测试,得到错误
[error] Test RuleTest.initializationError failed: java.lang.Exception: The @Rule 'name' must be public.
[error] at com.novocode.junit.JUnitRunner.run(JUnitRunner.java:87)
[error] at sbt.RunnerWrapper$1.runRunner2(FrameworkWrapper.java:220)
[error] at sbt.RunnerWrapper$1.execute(FrameworkWrapper.java:233)
[error] at sbt.ForkMain$Run.runTest(ForkMain.java:239)
[error] at sbt.ForkMain$Run.runTestSafe(ForkMain.java:211)
[error] at sbt.ForkMain$Run.runTests(ForkMain.java:187)
[error] at sbt.ForkMain$Run.run(ForkMain.java:251)
[error] at sbt.ForkMain.main(ForkMain.java:97)
[info] RuleTest
[info] x initializationError
'name’ 必须是 public 的,但是 Scala 的属性可以为 private/protected, 就是不能为 public 的,而是根据 val/var 生成 setter/getter 方法,默认时
Scala 为 val name
生成 public TestName name()
方法,为 var name
生成 public TestName name()
和 public void name_$eq(TestName arg)
方法。
而 JUnit 需要一个 public 的 name 属性该怎么办呢,得曲线救援。Scala 的方法是 public 的,而且 Scala 是符合 统一访问原则 的,所以可以让 @Rule 标在方法上,写成
1 2 3 4 5 6 7 8 9 |
val name = new TestName @Rule def nameRef = new name; @Test def testTestName { Assert.assertEquals("testTestName", name.getMethodName()); Assert.assertEquals("testTestName", nameRef.getMethodName()); } |
这时候 name 和 nameRef 都能得到当前测试方法名。上面必须要两行,一行定义 name,另一行 @Rule 指向前面声明的 name,我们却不能把这两行写成
1 2 |
@Rule def name = new TestName |
这样的话断言会失败
[error] Test RuleTest.testTestName failed: expected:<testTestName> but was:<null>
由 name.getMethodName() 的是 null
补充一下:JUnit 有两种类型的 Rule, 分别是 分别是 TestRule 和 MethedRule, 分别应用属性和方法中,在 JUnit 4.11 中带了一些实现:
TestRule: ErrorCollector, ExpectedException, ExternalResource, RuleChain, TemporaryFolder, TestName, TestWatcher, Timeout, Verifier
MethodRule: TestWatchman
在使用 MethodRule 上,Java 与 Scala 应该没用区别,Scala 像是在使用 MethodRule 一样使用 TestRule, 因为 @Rule 标注到方法上去了。
参考:
- http://www.scottlogic.com/blog/2013/07/18/betamax-in-scala.html
- http://randomallsorts.blogspot.com/2012/11/junit-411-whats-new-rules.html
- http://blog.jiffle.net/post/41125006846/extending-junit-functionality-with-additional
本文链接 https://yanbin.blog/scala-junit-how-to-rule/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。