我们在使用JUnit的时候,为了使测试结果更加清晰明确,会有以下的需求:
-
一个类有多个测试方法,想知道日志是哪个测试方法
-
想在所有测试方法的前后加上一些语句,用于初始化和销毁一些资源
-
如果一个测试方法可能抛出多个异常,想知道运行时到底抛出哪些异常
-
...
以上的需求可以通过为每个方法加上相似的语句来实现,但这样未免有点麻烦和冗余,好在JUnit4为我们提供了一劳永逸的方法,使用Rule。
Rule简介
Rule是JUnit4.7加入的新特性,有点类似于拦截器,用于在测试方法执行前后添加额外的处理。实际上是@Before,@After的另一种实现。使用时需要放在实现了TestRule的成员变量上或者返回TestRule的方法上,且修饰符为public。Rule会应用于该类每个测试方法。
内置Rule
JUnit提供了很多内置的TestRule的实现满足日常使用,具体有如下:
-
Verifier:所有测试结束后对测试执行结果添加额外的逻辑验证测试最终成功与否。该抽象类为子类提供一个接口方法verify()供扩展
String result = "success"; /** * Verifier:用于在测试方法执行结束后,进行一些结果校验 */ @Rule public Verifier verifier = new Verifier() { @Override protected void verify() throws Throwable { if (result.equals("fail")) { throw new Exception("failed"); } } }; @Test public void verifierTest() { result = "fail"; }
运行结果:
java.lang.Exception: failed ...
-
ErrorCollector:是Verifier类的一个子类实现,用于在测试执行过程中收集错误信息,不会中断测试,最后调用verify()方法处理。
/** * ErrorCollector:可以收集多个多个异常,并在方法结束后一起打印出来 */ @Rule public ErrorCollector errorCollector = new ErrorCollector(); @Test public void errorCollectorTest() { errorCollector.addError(new RuntimeException("error 1")); System.out.println("=================================="); errorCollector.addError(new RuntimeException("error 2")); }
运行结果:
================================== java.lang.RuntimeException: error 1 ... java.lang.RuntimeException: error 2 ...
-
TemporaryFolder:是抽象类ExternalResource的一个子类实现,用于在JUnit测试执行前后,创建和删除临时目录
/** * TemporaryFolder:创建临时目录/文件,测试方法执行结束后自动删除 * 可以在构造方法中传入使用的父目录,否则默认使用系统临时目录 */ @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(new File("C:\Users\test")); @Test public void temporaryFolderTest() throws IOException, InterruptedException { temporaryFolder.newFolder("test"); temporaryFolder.newFile("hello.txt"); Thread.sleep(10 * 1000); }
-
TestName:是抽象类TestWatcher的一个子类实现,用于在测试执行过程中获取测试方法名称。在starting()中记录测试方法名,在getMethodName()中返回
/** * TestName:获取当前测试方法的方法名 */ @Rule public TestName testName = new TestName(); @Test public void testNameTest() { System.out.println("method is " + testName.getMethodName()); }
运行结果:
method is testNameTest
-
TestWatcher:监视测试方法生命周期的各个阶段。该抽象类为子类提供了五个接口方法succeeded(), failed(), skipped(), starting()及finished()供扩展
/** * TestWatcher:在测试方法开始,结束,成功,失败,跳过这些时间点调用相应方法 */ @Rule public TestWatcher testWatcher = new TestWatcher() { @Override protected void succeeded(Description description) { System.out.println("success"); } @Override protected void failed(Throwable e, Description description) { System.out.println("failed"); } @Override protected void starting(Description description) { System.out.println("starting"); } @Override protected void finished(Description description) { System.out.println("finished"); } @Override protected void skipped(AssumptionViolatedException e, Description description) { System.out.println("skipped"); } }; @Test public void testWatcherTest() { System.out.println("testWatcher"); }
运行结果:
starting testWatcher success finished
-
Timeout:与@Test中的timeout相对应,@Test只能修饰待测试方法,Timeout可以修饰待测试类
/** * Timeout:超时时间,方法运行超时则抛出TestTimedOutException异常 */ @Rule public Timeout timeout = new Timeout(5, TimeUnit.SECONDS); @Test public void timeoutTest() throws InterruptedException { System.out.println("timeout"); Thread.sleep(6*1000); }
运行结果:
org.junit.runners.model.TestTimedOutException: test timed out after 5 seconds ...
-
ExpectedException:与@Test中的expected相对应,提供更强大灵活的异常验证功能,@Test只能修饰待测试方法,ExpectedException可以修饰待测试类
/** * ExpectedException:指定测试方法出现的异常,未出现或者出现其他类型的异常测试不通过 */ @Rule public ExpectedException expectedException=ExpectedException.none(); @Test public void expectedExceptionTest(){ expectedException.expect(NullPointerException.class); throw new RuntimeException(); }
运行结果:
java.lang.AssertionError: Expected: an instance of java.lang.NullPointerException but: is a java.lang.RuntimeException
自定义Rule
当内置的Rule无法满足你的需求的时候,你还可以通过实现TestRule来自定义Rule。
-
自定义RuleTest实现类
/** * 自己实现TestRule */ public class MyTestRule implements TestRule { /** * @param base 基础行为,要进行封装的行为 * @param description test方法的描述,包括方法名,类名等 * @return 可以使在基础行为上添加新动作,也可以是一个全新的动作 */ @Override public Statement apply(Statement base, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { String name = description.getMethodName(); System.out.println("开始调用" + name); base.evaluate(); System.out.println("结束调用" + name); } }; } }
使用自定义的Rlue类:
@Rule public MyTestRule myTestRule=new MyTestRule(); @Test public void myTestRuleTest(){ System.out.println("myTestRuleTest"); }
运行结果:
开始调用myTestRuleTest myTestRuleTest 结束调用myTestRuleTest
-
使用匿名类自定义Rule
@Rule public TestRule myRule(){ TestRule rule = (base, description) -> new Statement() { @Override public void evaluate() throws Throwable { String name = description.getMethodName(); System.out.println("start invoke " + name); base.evaluate(); System.out.println("finished invoke " + name); } }; return rule; } @Test public void myRuleTest(){ System.out.println("myRule"); }
运行结果:
start invoke myRuleTest myRule finished invoke myRuleTest