zoukankan      html  css  js  c++  java
  • 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可以创建一些临时目录或者文件,在一个测试方法结束之后,系统会自动清空他们。

     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的父类,主要用于在测试之前创建资源,并在测试完成后销毁。

     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 }  
    22  

    ErrorCollector Rule

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

    @Rule  
    public ErrorCollector errorCollector = new ErrorCollector();  
      
    @Test  
    public void testErrorCollector() {  
        errorCollector.addError(new Exception("Test Fail 1"));  
        errorCollector.addError(new Throwable("fff"));  
    }  
     

    Verifier Rule

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

     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 }  
    18  

    TestWatcher Rule

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

     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能让我们在测试中获取目前测试方法的名字。

    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方法:

    protected Statement methodBlock(FrameworkMethod method) {  
        Object test;  
        try {  
            test = new ReflectiveCallable() {  
                @Override  
                protected Object runReflectiveCall() throws Throwable {  
                    return createTest();  
                }  
            }.run();  
        } catch (Throwable e) {  
            return new Fail(e);  
        }  
      
        Statement statement = methodInvoker(method, test);  
        statement = possiblyExpectingExceptions(method, test, statement);  
        statement = withPotentialTimeout(method, test, statement);  
        statement = withBefores(method, test, statement);  
        statement = withAfters(method, test, statement);  
        statement = withRules(method, test, statement);  
        return statement;  
    }  

     在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: 

     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:

    1 public interface TestRule {  
    2     Statement apply(Statement base, Description description);  
    3 }  

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

    四、自定义Rule

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

     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 }  
    26  

    使用该自定义的Rule:

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

    执行后打印出以下信息:

    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!  
  • 相关阅读:
    grunt in webstorm
    10+ Best Responsive HTML5 AngularJS Templates
    响应式布局
    responsive grid
    responsive layout
    js event bubble and capturing
    Understanding Service Types
    To add private variable to this Javascript literal object
    Centering HTML elements larger than their parents
    java5 新特性
  • 原文地址:https://www.cnblogs.com/johnson-blog/p/3890548.html
Copyright © 2011-2022 走看看