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!  
  • 相关阅读:
    如何在控件的设计时得到窗体设计器中的所有控件
    如何将一个数组绑定到DataList
    在找C#语言规范吗?只要你装了VS,你就能找到
    lync相关功能介绍
    下表描述了Foundation 2010 、 SharePoint Server 2010 和 FAST Search Server 2010三者的搜索能力
    sharepoint 2010 针对移动端的支持
    SharePoint Foundation 2010 SP1 改进概述
    RMS FOR EXCHANGE 2010
    Excel Web App使用说明
    win2008 server rms部署
  • 原文地址:https://www.cnblogs.com/johnson-blog/p/3890548.html
Copyright © 2011-2022 走看看