`
jgnan
  • 浏览: 88906 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

JUnit 4.7学习笔记(三)——MethodRule

阅读更多
昨天我们研究到junit的MethodRule对象。虽然我不知道这个东东究竟是否这个版本新加的东西(因为自从4.1版本以后我好久没看过它的源代码了),不过既然我还不懂得它,就有研究的必要了

首先来看看MethodRule的翻译:
一个MethodRule就是对测试的运行及报告方式的一种替代方案。一个测试方法可以同时实施多个不同的MethodRule。执行测试方法的Statement对象会依次传递给各个Rule注解,并且每一个规则都会返回一个替换了的或者修改过的Statement对象,并且如果存在下一个规则的话,会把这个Statement抛给它。

并且我发现junit默认提供了下列的MethodRule,当然我们也可以写自己的:
  • ErrorCollector : 收集一个测试方法里面的多个错误
  • ExpectedException : 对抛出的异常提供灵活的判断
  • ExternalResource : 对外部资源的操控,例如启动或者停止一个服务器
  • TemporaryFolder : 进行测试期间相关的测试操作。例如创建测试所需的临时文件,并且在测试完结之后删除它们。
  • TestName : 在测试方法中记住测试的名称
  • TestWatchman : 在方法执行的事件中添加额外的逻辑
  • Timeout : 当测试的执行超过特定时间后导致测试失败
  • Verifier : 假如对象状态不正确时让测试失败


就我经验来说,它尝试把以前的expect,timeout等标记进行了整合,并且添加了一些其它的扩展。这套规则机制除了把以前凌乱的测试扩展的控制机制统一起来之外,还允许你对测试的策略进行自定义。可见这个版本的junit更加简单并且灵活。

MethodRule里面就只有一个方法:
    Statement apply(Statement base, FrameworkMethod method, Object target);


其中,base就是上面提到的执行测试方法的Statement对象,method为要执行的测试方法,target则是测试类的实例。

返回结果,根据javadoc的描述,当然就是一个Statement对象了。。。

现在我们进一步看看各个默认Rule的详细情况,它们都在org.junit.rules里面:
[img]http://dl.iteye.com/upload/attachment/182501/50591715-67ee-31c8-a19e-0c66ce7ca8d5.gif[/img]

[b]ErrorCollector[/b]
作用:允许你把测试过程的所有异常都交给它,然后在最后让它一次性对所有异常进行汇报。
示例:
[code="java]
public static class UsesErrorCollectorTwice {
    @Rule
    public ErrorCollector collector= new ErrorCollector();
 
    @Test
    public void example() {
        collector.addError(new Throwable("first thing went wrong"));
        collector.addError(new Throwable("second thing went wrong"));
        collector.checkThat(getResult(), not(containsString("ERROR!")));
        // all lines will run, and then a combined failure logged at the end.
    }
}


在上面的例子里面,我们并不是像传统的做法那样直接把异常往外抛直接导致一个异常的Statement中断测试,而上放到这个ErrorCollector里面,最后通过Assert.checkThat方法一次性对结果进行判断是否通过。这种做法无疑是一个非常节省时间的进步,不再需要像以前那样无法一次性尝试走完测试才结束用例。

ExpectedException
作用:在测试方法里面动态控制期待抛出的异常类型甚至其内容
示例:
// These tests all pass.
public static class HasExpectedException {
    @Rule
    public ExpectedException thrown= new ExpectedException();
 
    @Test
    public void throwsNothing() {
        // no exception expected, none thrown: passes.
    }
 
    @Test
    public void throwsNullPointerException() {
        thrown.expect(NullPointerException.class);
        throw new NullPointerException();
    }
 
    @Test
    public void throwsNullPointerExceptionWithMessage() {
        thrown.expect(NullPointerException.class);
        thrown.expectMessage("happened?");
        thrown.expectMessage(startsWith("What"));
        throw new NullPointerException("What happened?");
    }
}


看到最后一个例子的时候,只能惊讶这个东西真是神奇。不过出于使用习惯,个人觉得还是@expect标记比较实用。不过无可非议,当我们对那些只会抛出同一个异常,但是message会根据出错的情况而发生变化的场合使用这个规则是非常好的选择。

ExternalResource
作用:在测试之前对外部资源进行初始化及在测试结束之后对这些资源进行清理的规则。
示例:
public static class UsesExternalResource {
    Server myServer= new Server();
 
    @Rule
    public ExternalResource resource= new ExternalResource() {
        @Override
        protected void before() throws Throwable {
            myServer.connect();
        };
 
        @Override
        protected void after() {
            myServer.disconnect();
        };
    };
 
    @Test
    public void testFoo() {
        new Client().run(myServer);
    }
}


个人感觉这个东西比较无聊。它要么是封装了@Before和@After,要么是封装了@BeforeClass和@AfterClass。虽然就字面理解它应该是实现了前者,但是为了让大家可以更好地理解我决定做个实验看看:
package com.amway.training.junit;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExternalResource;

public class ExternalResourceTest{
    @Before
    public void before()
    {
        System.out.println("Running in before()");
    }
	
    @Before
    public void before2()
    {
        System.out.println("Running in before2()");
    }
	
    @BeforeClass
    public static void beforeClass()
    {
        System.out.println("Running in beforeClass()");
    }
	
    @Rule
    public ExternalResource resource = new ExternalResource() {

        @Override
        protected void after() {
            System.out.println("Running in resource.after()");
        }

        @Override
        protected void before() throws Throwable {
            System.out.println("Running in resource.before()");
        }
		
    };
	
    @Rule
    public ExternalResource resource2 = new ExternalResource() {

        @Override
        protected void after() {
            System.out.println("Running in resource2.after()");
        }

        @Override
        protected void before() throws Throwable {
            System.out.println("Running in resource2.before()");
        }
	
};
	
    @After
    public void After()
    {
        System.out.println("Running in after()");
    }
	
    @AfterClass
    public static void AfterClass()
    {
        System.out.println("Running in afterClass()");
    }
	
    @Test
    public void aTest()
    {
        System.out.println("I'm a simple test");
    }
}


结果如我所想的一样无聊。。。
Running in beforeClass()
Running in before()
Running in before2()
Running in resource2.before()
Running in resource.before()
I'm a simple test
Running in resource.after()
Running in resource2.after()
Running in after()
Running in afterClass()


可见,它只是@Before和@After的一套替代品。不过从让@Before的执行方法和@After的执行方法一一对应这点来说,使用这个Rule来进行外部资源管理的确是一个最佳实践。因为如果我们依靠@Before和@After来实现外部资源的初始化及清理,如果只有一对组合还好,一多了就会很难搞清谁先谁后。。。。

TemporaryFolder
作用:帮我们在测试过程中创建特定的临时文件夹或文件用于测试,并且在测试结束后,无论是否成功,都会删除这些由它创建的东西。
示例:
public static class HasTempFolder {
    @Rule
    public TemporaryFolder folder= new TemporaryFolder();
 
    @Test
    public void testUsingTempFolder() throws IOException {
        File createdFile= folder.newFile("myfile.txt");
        File createdFolder= folder.newFolder("subfolder");
        // ...
    }
}


注意,必须通过TemporaryFolder的方法创建出来的临时文件及文件夹,才会在测试结束的时候被删除。让我们来解读一下它的代码就知道是怎么回事了:
public class TemporaryFolder extends ExternalResource {
    private File folder;

    @Override
    protected void before() throws Throwable {
        create();
    }

    @Override
    protected void after() {
        delete();
    }

    // testing purposes only
    /**
     * for testing purposes only.  Do not use.
     */
    public void create() throws IOException {
        folder= File.createTempFile("junit", "");
        folder.delete();
        folder.mkdir();
    }

    /**
     * Returns a new fresh file with the given name under the temporary folder.
     */
    public File newFile(String fileName) throws IOException {
        File file= new File(folder, fileName);
        file.createNewFile();
        return file;
    }

    /**
     * Returns a new fresh folder with the given name under the temporary folder.
     */
    public File newFolder(String folderName) {
        File file= new File(folder, folderName);
        file.mkdir();
        return file;
    }

    /**
     * @return the location of this temporary folder.
     */
    public File getRoot() {
        return folder;
    }

    /**
     * Delete all files and folders under the temporary folder.
     * Usually not called directly, since it is automatically applied 
     * by the {@link Rule}
     */
    public void delete() {
        recursiveDelete(folder);
    }

    private void recursiveDelete(File file) {
        File[] files= file.listFiles();
        if (files != null)
        for (File each : files)
            recursiveDelete(each);
            file.delete();
    }
}


可以看到,其实它是ExternalResource的一个子类,并且在里面有一个私有字段folder。这个字段会在before的时候通过create()方法在当前的测试类目录下被创建为junit的文件夹。并且在后续用newFolder和newFile创建目录的时候都会在此目录下进行创建。在after()推出测试方法时则会把这个folder及其子文件和文件夹通通删除。

所以,这个规则在多线程测试模式下要慎用!!一旦你的同一个目录下有两个测试在同时执行的时候,就可能会出现资源竞争的情况导致删除出问题,甚至还会有其它死锁之类的漏洞!

总体来说,当我们使用这个规则的时候看来是要经过自己重构才能够使用,把folder的名字用个不会重复的名称吧,是最简单的方法了。

TestName
作用:让测试方法可以调用到当前测试的测试名变量
示例:
public class TestNameTest {
    @Rule
    public TestName name= new TestName();
 
    @Test
    public void testA() {
        assertEquals("testA", name.getMethodName());
    }
 
    @Test
    public void testB() {
        assertEquals("testB", name.getMethodName());
    }
}


很简单的一个例子。这个东西主要对于统一处理进程的日志显示会有帮助。后面的例子我们就会用到它。

TestWatchman
作用:一个测试过程的观察者。只会记录测试过程的关键事件,但是不会对测试造成任何影响。
示例:
public static class WatchmanTest {
    private static String watchedLog;
 
    @Rule
    public MethodRule watchman= new TestWatchman() {
        @Override
        public void failed(Throwable e, FrameworkMethod method) {
            watchedLog+= method.getName() + " " + e.getClass().getSimpleName()
                    + "\n";
        }
 
        @Override
        public void succeeded(FrameworkMethod method) {
            watchedLog+= method.getName() + " " + "success!\n";
        }
    };
 
    @Test
    public void fails() {
        fail();
    }
 
    @Test
    public void succeeds() {
    }
 }


查看它的apply方法源码如下:
public Statement apply(final Statement base, final FrameworkMethod method,Object target) {
    return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                starting(method);
                try {
                    base.evaluate();
                    succeeded(method);
                } catch (Throwable t) {
                    failed(t, method);
                    throw t;
                } finally {
                    finished(method);
                }
            }
    };
}


可见我们可以监控4种状态:
  • starting
  • succeeded
  • failed
  • finished


Timeout
作用:对所有测试方法定义一个统一的timeout时间
示例:
public static class HasGlobalTimeout {
    public static String log;
 
    @Rule
    public MethodRule globalTimeout= new Timeout(20);
 
    @Test
    public void testInfiniteLoop1() {
        log+= "ran1";
        for (;;) {
        }
    }
 
    @Test
    public void testInfiniteLoop2() {
        log+= "ran2";
        for (;;) {
        }
    }
}


以前要在@Test里面逐个定义timeout属性,现在可以统一在这个规则里面定义。老问题又来了。如果我又在@Test里面定义timeout,又在Timeout里面定义超时时间,哪个会生效呢?我们可以先来看看以下实例实例:
package com.amway.training.junit;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExternalResource;
import org.junit.rules.MethodRule;
import org.junit.rules.TestName;
import org.junit.rules.Timeout;

public class TimeoutTest {
    @Rule
    public MethodRule timeout = new Timeout(1000);
	
    @Rule
    public TestName testName = new TestName();
	
    @Rule
    public MethodRule extres = new ExternalResource(){

        @Override
        protected void after() {
            end = System.currentTimeMillis();
            System.out.println("Run time for ["+testName.getMethodName()+"] is:"+ (end-start));
            end = start = 0;
        }

        @Override
        protected void before() throws Throwable {
            start = System.currentTimeMillis();
            end=0;
        }
		
    };
	
    private long start,end;
	
    @Test
    public void testWithoutSpecTimeout()
    {
        for(;;){}
    }
	
	
    @Test(timeout=100)
    public void testWithSpecTimeout()
    {
        for(;;){}
    }
}


结果为:
Run time for [testWithoutSpecTimeout] is:1000
Run time for [testWithSpecTimeout] is:109

结果就是,具体的@Test的timeout可以改写这个统一的timeout。来看看其apply方法的源码:
public Statement apply(Statement base, FrameworkMethod method, Object target) {
    return new FailOnTimeout(base, fMillis);
}


然后再来看看FailOnTimeout这个类的evaluate方法:
@Override
public void evaluate() throws Throwable {
    Thread thread= new Thread() {
        @Override
        public void run() {
            try {
                fNext.evaluate();
                fFinished= true;
            } catch (Throwable e) {
                fThrown= e;
            }
        }
    };
    thread.start();
    thread.join(fTimeout);
    if (fFinished)
        return;
    if (fThrown != null)
        throw fThrown;
    Exception exception= new Exception(String.format(
                "test timed out after %d milliseconds", fTimeout));
    exception.setStackTrace(thread.getStackTrace());
    throw exception;
}


正如大家所见,这个东西会执行fNext.evaluate()方法,而对于Timeout规则来说,这个fNext是由之前的规则传递过来的,回忆一下我们在核心分析时讨论过的methodBlock方法,它最先封装的就是@Test标记的方法。所以当两者同时定义了不同的timeout时间时,它会以@Test定义的优先。

Verifier
作用:验证失败时可以控制测试直接失败。
示例:
package com.amway.training.junit;

import junit.framework.Assert;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.MethodRule;
import org.junit.rules.Verifier;

public class ErrorLogVerifier {
    private StringBuffer errorLog = new StringBuffer();
     
    @Rule
    public MethodRule verifier = new Verifier() {
        @Override 
        public void verify() {
            Assert.assertTrue(errorLog.length()==0);
        }
    };
        
    @Test 
    public void testThatWriteErrorLog() {
        errorLog.append("some error!!");
    }
    
    @Test 
    public void testThatWontWriteErrorLog() {
        
    }
}


结果:


其实。。。除非你有必要对某种异常进行统一的验证处理,否则没必要使用这个Rule。

我们还能够自己去实现自己的MethodRule。由于时间关系我就不详细说了,大家可以多参考默认的规则然后自己练练手。总体来说这个东西还是蛮有用的。

下一次我们来看看Statement的种类吧。
  • 大小: 6 KB
  • 大小: 6.4 KB
分享到:
评论
发表评论

文章已被作者锁定,不允许评论。

相关推荐

    junit4.7完整jar包

    这个jar包中的实例和源码是学习和理解Junit4.7的宝贵资源。通过查看这些示例,你可以了解到如何编写有效的测试用例,如何组织测试结构,以及如何利用Junit提供的各种工具和功能来提高测试效率。源码分析有助于深入...

    Junit 4.7完整 全面jar包

    其次,`junit-4.7-src.jar`包含的是JUnit 4.7的源代码,这对于学习和理解JUnit的工作原理非常有用。开发者可以直接查看源码,了解内部实现细节,也可以在遇到问题时参考源码寻找解决方案。这对于想要深入学习和定制...

    junit4.7及相关教程

    JUnit 4.7是这个框架的一个版本,提供了许多增强的功能和改进,使得测试更加灵活和高效。 JUnit的核心概念是测试用例(Test Case),它是测试特定功能或方法的一组相关断言(Assertion)。在JUnit 4.7中,你可以...

    junit4.7

    JUnit 4.7 是一个广泛使用的Java编程语言的单元测试框架,它极大地简化了软件开发中的测试过程。这个版本是JUnit系列的一个重要迭代,引入了许多改进和新特性,旨在提高测试的效率和覆盖率。让我们深入了解一下JUnit...

    junit4.7全套

    下面将详细介绍JUnit 4.7及其在Java开发中的应用。 1. **JUnit简介** JUnit是由Ernst Mak和Kent Beck共同创建的开源项目,它基于Java设计模式,使得编写和执行测试用例变得简单。JUnit 4.7作为早期版本4.x系列的一...

    junit-4.7.jar下载

    这里我们关注的是JUnit 4.7版本,它是一个重要的里程碑,引入了许多新特性和改进。 首先,JUnit 4.7是JUnit系列的一个升级版,它在JUnit 4的基础上进行了增强。JUnit 4相较于之前的版本,最大的改变是引入了注解...

    junit4.7.zip

    《深入理解JUnit 4.7:Java开发中的单元测试利器》 JUnit是Java开发者进行单元测试最常用的工具之一,尤其在版本4.7中,它提供了丰富的功能和改进,使得测试更加高效、易于理解和维护。本文将深入探讨JUnit 4.7的...

    junit4.7单元测试

    JUnit 4.7 是一个流行的开源测试框架,主要用于编写和执行Java程序的单元测试。它在软件开发过程中扮演着至关重要的角色,确保代码的质量和稳定性。这个版本的JUnit是在JUnit 4系列的一个更新,带来了许多改进和新...

    单元测试Junit 4.7

    文件名`junit4.7-SNAPSHOT-20090511-2347`可能指的是JUnit 4.7的一个快照版本,其中`SNAPSHOT`通常表示这是一个开发中的不稳定版本,`20090511-2347`可能是该版本的构建日期和时间。在实际开发中,为了确保稳定性,...

    Java单元测试JUnit4.7

    JUnit 4.7是该框架的一个版本,包含了对之前版本的改进和新功能。 在Java开发中,单元测试是对单个或小部分代码进行的功能验证,通常针对方法。通过单元测试,开发者可以确保代码的正确性,降低bug的出现,并且在...

    junit-4.7.jar+junit-4.7-src.jar

    同时,`junit-4.7-src.jar` 可以作为学习资料,帮助你在遇到问题时查阅源代码。 **总结** JUnit 4.7 是一个强大的单元测试框架,它的注解驱动和丰富的功能使得编写测试变得简单。这两个JAR文件的结合为开发者提供了...

    Junit4.8.2(Junit4.7)

    源代码有助于开发者理解JUnit的内部工作原理,对于学习和扩展JUnit有极大的帮助。 总结来说,JUnit 4.8.2和4.7都是强大的单元测试工具,选择4.8.2可以避免额外的库依赖问题,而4.7则适用于对老版本有特殊需求的场景...

    junit4.7以及hamcrest-core-1.3

    JUnit 4.7 和 Hamcrest-Core-1.3 是两个重要的Java单元测试工具,它们在软件开发过程中扮演着至关重要的角色。JUnit 是一个开源的、基于Java的单元测试框架,而Hamcrest是一个匹配器库,提供了丰富的断言方法,使得...

    junit 4.7 api chm 中文版

    深圳电信培训中心徐海蛟老师上课用的junit4.7 api chm 速查中文手册.吐血奉献o(∩_∩)o...哈哈

    junit-4.7.jar包

    `junit-4.7.jar`是JUnit 4.7版本的库文件,这个版本发布于2008年,是JUnit系列的一个重要里程碑。在Java开发中,单元测试是验证代码功能正确性的重要手段,它确保了每个独立的代码模块都能正常工作,为软件质量提供...

    junit4.7-SNAPSHOT-20090511-2347.rar

    这个名为"junit4.7-SNAPSHOT-20090511-2347.rar"的压缩包文件包含了JUnit 4.7的一个快照版本,发布日期为2009年5月11日,具体时间是23:47。这个版本可能是一个开发过程中的不稳定版本,"SNAPSHOT"通常表示这是一个...

    junit-4.7-src.jar

    junit-4.7-src.jar junit-4.7-src.jar junit-4.7-src.jar junit-4.7-src.jar junit-4.7-src.jar junit-4.7-src.jar junit-4.7-src.jar

    junit-4.7.jar

    junit-4.7.jar 用于java测试的包

    struts2_spring3.0_Junit4.7_Maven2.2.1_整合运行说明_培训.pdf )

    本文档主要介绍了如何将Struts2、Spring3.0、JUnit4.7和Maven2.2.1这几种技术框架整合在一起,以构建一个功能完善的Java EE项目。该文档的目标是帮助开发人员理解如何在实际项目中应用这些框架,并确保它们能够协同...

Global site tag (gtag.js) - Google Analytics