JUnit4教程(四):利用Rule扩展JUnit

一、Rule简介

Rule是JUnit4中的新特性,它让我们可以扩展JUnit的功能,灵活地改变测试方法的行为。JUnit中用@Rule和@ClassRule两个注解来实现Rule扩展,这两个注解需要放在实现了TestRule借口的成员变量(@Rule)或者静态变量(@ClassRule)上。@Rule和@ClassRule的不同点是,@Rule是方法级别的,每个测试方法执行时都会调用被注解的Rule,而@ClassRule是类级别的,在执行一个测试类的时候只会调用一次被注解的Rule

 

二、JUnit内置Rule

JUnit4中默认实现了一些常用的Rule:

 

TemporaryFolder Rule

使用这个Rule可以创建一些临时目录或者文件,在一个测试方法结束之后,系统会自动清空他们。

 

Java代码   收藏代码
  1. //创建TemporaryFolder Rule  
  2. //可以在构造方法上加入路径参数来指定临时目录,否则使用系统临时目录  
  3. @Rule  
  4. public TemporaryFolder tempFolder = new TemporaryFolder();  
  5.   
  6. @Test  
  7. public void testTempFolderRule() throws IOException {  
  8.     //在系统的临时目录下创建文件或者目录,当测试方法执行完毕自动删除  
  9.     tempFolder.newFile("test.txt");  
  10.     tempFolder.newFolder("test");  
  11. }  

 

ExternalResource Rule

ExternalResource 是TemporaryFolder的父类,主要用于在测试之前创建资源,并在测试完成后销毁。

Java代码   收藏代码
  1. File tempFile;  
  2.   
  3. @Rule  
  4. public ExternalResource extResource = new ExternalResource() {  
  5.     //每个测试执行之前都会调用该方法创建一个临时文件  
  6.     @Override  
  7.     protected void before() throws Throwable {  
  8.         tempFile = File.createTempFile("test"".txt");  
  9.     }  
  10.   
  11.     //每个测试执行之后都会调用该方法删除临时文件  
  12.     @Override  
  13.     protected void after() {  
  14.         tempFile.delete();  
  15.     }  
  16. };  
  17.   
  18. @Test  
  19. public void testExtResource() throws IOException {  
  20.     System.out.println(tempFile.getCanonicalPath());  
  21. }  

 

ErrorCollector Rule

ErrorCollector允许我们收集多个错误,并在测试执行完后一次过显示出来

Java代码   收藏代码
  1. @Rule  
  2. public ErrorCollector errorCollector = new ErrorCollector();  
  3.   
  4. @Test  
  5. public void testErrorCollector() {  
  6.     errorCollector.addError(new Exception("Test Fail 1"));  
  7.     errorCollector.addError(new Throwable("fff"));  
  8. }  
 

Verifier Rule

Verifier是ErrorCollector的父类,可以在测试执行完成之后做一些校验,以验证测试结果是不是正确

Java代码   收藏代码
  1. String result;  
  2.   
  3. @Rule  
  4. public Verifier verifier = new Verifier() {  
  5.     //当测试执行完之后会调用verify方法验证结果,抛出异常表明测试失败  
  6.     @Override  
  7.     protected void verify() throws Throwable {  
  8.         if (!"Success".equals(result)) {  
  9.             throw new Exception("Test Fail.");  
  10.         }  
  11.     }  
  12. };  
  13.   
  14. @Test  
  15. public void testVerifier() {  
  16.     result = "Fail";  
  17. }  
 

TestWatcher Rule

TestWatcher 定义了五个触发点,分别是测试成功,测试失败,测试开始,测试完成,测试跳过,能让我们在每个触发点执行自定义的逻辑。

Java代码   收藏代码
  1. @Rule  
  2. public TestWatcher testWatcher = new TestWatcher() {  
  3.     @Override  
  4.     protected void succeeded(Description description) {  
  5.         System.out.println(description.getDisplayName() + " Succeed");  
  6.     }  
  7.   
  8.     @Override  
  9.     protected void failed(Throwable e, Description description) {  
  10.         System.out.println(description.getDisplayName() + " Fail");  
  11.     }  
  12.   
  13.     @Override  
  14.     protected void skipped(AssumptionViolatedException e, Description description) {  
  15.         System.out.println(description.getDisplayName() + " Skipped");  
  16.     }  
  17.   
  18.     @Override  
  19.     protected void starting(Description description) {  
  20.         System.out.println(description.getDisplayName() + " Started");  
  21.     }  
  22.   
  23.     @Override  
  24.     protected void finished(Description description) {  
  25.         System.out.println(description.getDisplayName() + " finished");  
  26.     }  
  27. };  
  28.   
  29. @Test  
  30. public void testTestWatcher() {  
  31.     /* 
  32.         测试执行后会有以下输出: 
  33.         testTestWatcher(org.haibin369.test.RulesTest) Started 
  34.         Test invoked 
  35.         testTestWatcher(org.haibin369.test.RulesTest) Succeed 
  36.         testTestWatcher(org.haibin369.test.RulesTest) finished 
  37.      */  
  38.     System.out.println("Test invoked");  
  39. }  

 

TestName Rule

TestName能让我们在测试中获取目前测试方法的名字。

Java代码   收藏代码
  1. @Rule  
  2. public TestName testName = new TestName();  
  3.   
  4. @Test  
  5. public void testTestName() {  
  6.     //打印出测试方法的名字testTestName  
  7.     System.out.println(testName.getMethodName());  
  8. }  

 

Timeout与ExpectedException Rule

分别用于超时测试与异常测试,在JUnit4学习笔记(一):基本应用中有提到,这里不再举例。

 

 

三、实现原理与部分源码解析

在Junit4的默认Test Runner - org.junit.runners.BlockJUnit4ClassRunner中,有一个methodBlock方法:

Java代码   收藏代码
  1. protected Statement methodBlock(FrameworkMethod method) {  
  2.     Object test;  
  3.     try {  
  4.         test = new ReflectiveCallable() {  
  5.             @Override  
  6.             protected Object runReflectiveCall() throws Throwable {  
  7.                 return createTest();  
  8.             }  
  9.         }.run();  
  10.     } catch (Throwable e) {  
  11.         return new Fail(e);  
  12.     }  
  13.   
  14.     Statement statement = methodInvoker(method, test);  
  15.     statement = possiblyExpectingExceptions(method, test, statement);  
  16.     statement = withPotentialTimeout(method, test, statement);  
  17.     statement = withBefores(method, test, statement);  
  18.     statement = withAfters(method, test, statement);  
  19.     statement = withRules(method, test, statement);  
  20.     return statement;  
  21. }  

 

 在JUnit执行每个测试方法之前,methodBlock方法都会被调用,用于把该测试包装成一个Statement。Statement代表一个具体的动作,例如测试方法的执行,Before方法的执行或者Rule的调用,类似于J2EE中的Filter,Statement也使用了责任链模式,将Statement层层包裹,就能形成一个完整的测试,JUnit最后会执行这个Statement。从上面代码可以看到,有以下内容被包装进Statement中:

    1)测试方法的执行;

    2)异常测试,对应于@Test(expected=XXX.class);

    3)超时测试,对应与@Test(timeout=XXX);

    4)Before方法,对应于@Before注解的方法;

    5)After方法,对应于@After注解的方法;

    6)Rule的执行。

 

在Statement中,可以用evaluate方法控制Statement执行的先后顺序,比如Before方法对应的Statement - RunBefores: 

Java代码   收藏代码
  1. public class RunBefores extends Statement {  
  2.     private final Statement fNext;  
  3.   
  4.     private final Object fTarget;  
  5.   
  6.     private final List<FrameworkMethod> fBefores;  
  7.   
  8.     public RunBefores(Statement next, List<FrameworkMethod> befores, Object target) {  
  9.         fNext = next;  
  10.         fBefores = befores;  
  11.         fTarget = target;  
  12.     }  
  13.   
  14.     @Override  
  15.     public void evaluate() throws Throwable {  
  16.         for (FrameworkMethod before : fBefores) {  
  17.             before.invokeExplosively(fTarget);  
  18.         }  
  19.         fNext.evaluate();  
  20.     }  
  21. }  

 在evaluate中,所有Before方法会先被调用,因为Before方法必须要在测试执行之前调用,然后再执行fNext.evaluate()调用下一个Statement。

 

理解了Statement,再看回Rule的接口org.junit.rules.TestRule:

Java代码   收藏代码
  1. public interface TestRule {  
  2.     Statement apply(Statement base, Description description);  
  3. }  

里面只有一个apply方法,用于包裹上级Statement并返回一个新的Statement。因此实现Rule主要是需要实现一个Statement。

 

四、自定义Rule

通过上面的分析,我们大概知道了如何实现一个Rule,下面是一个例子:

Java代码   收藏代码
  1. /* 
  2.    用于循环执行测试的Rule,在构造函数中给定循环次数。 
  3.  */  
  4. public class LoopRule implements TestRule{  
  5.     private int loopCount;  
  6.   
  7.     public LoopRule(int loopCount) {  
  8.         this.loopCount = loopCount + 1;  
  9.     }  
  10.   
  11.     @Override  
  12.     public Statement apply(final Statement base, Description description) {  
  13.         return new Statement() {  
  14.             //在测试方法执行的前后分别打印消息  
  15.             @Override  
  16.             public void evaluate() throws Throwable {  
  17.                 for (int i = 1; i < loopCount; i++) {  
  18.                     System.out.println("Loop " + i + " started!");  
  19.                     base.evaluate();  
  20.                     System.out.println("Loop "+ i + " finished!");  
  21.                 }  
  22.             }  
  23.         };  
  24.     }  
  25. }  

 

使用该自定义的Rule:

Java代码   收藏代码
  1. @Rule  
  2. public LoopRule loopRule = new LoopRule(3);  
  3.   
  4. @Test  
  5. public void testLoopRule() {  
  6.     System.out.println("Test invoked!");  
  7. }  

 

执行后打印出以下信息:

Java代码   收藏代码
  1. Loop 1 started!  
  2. Test invoked!  
  3. Loop 1 finished!  
  4. Loop 2 started!  
  5. Test invoked!  
  6. Loop 2 finished!  
  7. Loop 3 started!  
  8. Test invoked!  
  9. Loop 3 finished!  

 原文链接:http://haibin369.iteye.com/blog/2088541

猜你喜欢

转载自blog.csdn.net/u013001763/article/details/80256892
今日推荐