JUnit 的测试用例总是由 Runner 去执行,JUnit 提供了 @RunWith 这个测试类的 Annotation, 可来指定自定义的 Runner。如果未指定特别的 Runner,那么会采用默认的 Runner,可能不同的环境,如 Eclipse,控制台下会有不同的默认 Runner。
如果不清楚 Runner 是什么,那么可能见过 @RunWith(SpringJUnit4ClassRunner.class) 这个东西,它有助你加载 Spring 的配置文件,及与 Spring 相关的事物。
那么自定义的 Runner 有什么用呢?它可以截获到 @BeforeClass, @AfterClass, @Before, @After 这些事件,也就是能在测试类开始和结束执行前后,每个测试方法的执行前后处理点事情。
比如说从外部读取内容进行初始化测试数据,而且 JUnit 本身就提供了 @RunWith(Parameterized.class) 这个参数化 Runner,用了为带参数测试方法循环填充数据进行测试。JUnit 的参数化测试比 C# 还是要笨拙一些,C# 直接用方法注解一行行设置参数,我想 JUnit 稍加定制的话也行的。
现在用个例子来说明定制化 Runner 的表现功力,需继承自 BlockJUnit4ClassRunner, 这又是继承自 ParentRunner。可自定义那些 with 开头的方法来截获相应的事件,如:
来自 ParentRunner 的 withBeforeClasses, withAfterClasses
和定义的 BlockJUnit4ClassRunner 中的 withBefores, withAfters, withPotentialTimeout
等等,总之祖先类中的 protected 方法一般就是给大家开的口,看例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
package cc.unmi; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; import org.junit.runners.model.Statement; /** * @author Unmi * @created 2013-04-17 */ @SuppressWarnings("deprecation") public class MyTestRunner extends BlockJUnit4ClassRunner { private Class<?> clazz; public MyTestRunner(Class<?> klass) throws InitializationError { super(klass); this.clazz = klass; } // 拦截 BeforeClass 事件 protected Statement withBeforeClasses(final Statement statement) { final Statement junitStatement = super.withBeforeClasses(statement); return new Statement() { @Override public void evaluate() throws Throwable { System.out.println("Before Class: " + clazz.getName()); junitStatement.evaluate(); } }; } // 拦截每一个方法的 Before 事件 protected Statement withBefores(final FrameworkMethod method, Object target, final Statement statement) { final Statement junitStatement = super.withBefores(method, target, statement); return new Statement() { @Override public void evaluate() throws Throwable { System.out.println("Before before method: " + method.getName()); junitStatement.evaluate(); System.out.println("After before method: " + method.getName()); } }; } // 截获每一个测试方法的 after 事件 protected Statement withAfters(final FrameworkMethod method, Object target, final Statement statement) { final Statement junitStatement = super.withAfters(method, target, statement); return new Statement() { @Override public void evaluate() throws Throwable { System.out.println("After method: " + method.getName()); junitStatement.evaluate(); } }; } // 截获测试类的 after 事件 protected Statement withAfterClasses(final Statement statement) { final Statement junitStatement = super.withAfterClasses(statement); return new Statement() { @Override public void evaluate() throws Throwable { junitStatement.evaluate(); System.out.println("After Class: " + clazz.getName()); } }; } } |
上面的 junitStatement.evaluate() 才是真正去执行相应的用 @Before, @After, @BeforeClass, @AfterClass 和 @Test 标的方法,所以可以在执行真正方法前面植入点什么。
看测试类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
package cc.unmi; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; /** * @author Unmi * @created 2013-04-17 */ @RunWith(MyTestRunner.class) public class MyTest { @BeforeClass public static void beforeClass() { System.out.println("execute beforeClass"); } @Before public void before() { System.out.println("execute before"); } @Test public void should_return_something_if_age_equals_18() { System.out.println("execute should_return_something_if_age_equals_18"); } @After public void after() { System.out.println("execute after"); } @AfterClass public static void afterClass() { System.out.println("execute afterClass"); } } |
看执行效果吧:
Before Class: cc.unmi.MyTest
execute beforeClass
After method: should_return_something_if_age_equals_18
Before before method: should_return_something_if_age_equals_18
execute before
execute should_return_something_if_age_equals_18
After before method: should_return_something_if_age_equals_18
execute after
execute afterClass
After Class: cc.unmi.MyTest
由于给出了的执行结果,所以就不多解释了。
应用场景探讨:因为在 “拦截” 方法中可以感知道当前执行类和方法,所以可以在 withBeforeXxx 时初始化测试数据,比如可以加入自定义的注解,根据注解,继续用反射的方式对类、实例变量进行初始化,或某些清理工作。
再学习下 JUnit 的 @RunWith(Parameterized.class) 参数化测试。除此之外,还有 @ClassRule 和 @Rule 也有异曲同功之妙,见 JUnit扩展方式(一)-使用Rule对JUnit进行扩展(JUnit4.10) 。
参考: 1. JUnit扩展方式(二)-使用Runner对JUnit进行扩展(基础)大
2. JUnit中的测试套件和参数化测试
3. 使用 Feed4JUnit 进行数据与代码分离的 Java 单元测试
4. 使用RunWith注解改变JUnit的默认执行类,并实现自已的Listener
5. JUnit扩展方式(一)-使用Rule对JUnit进行扩展(JUnit4.10)
本文链接 https://yanbin.blog/extend-junit-4-customized-runner/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。