一个通用的单元测试框架的思考和设计03-实现篇-核心类源码
第二节里我们介绍了iunit整体的设计思路以及核心类之间的关系,这篇将以源码+解释的方式来演示核心类的实现方式
1.IUnitRunner 类
这个类是测试的入口类,直接继承自junit4.8的BlockJunit4ClassRunner,在构造函数里,我们把iunit框架的扩展功能添加了进来,因为整个框架呃设计都是基于Listener的,所以只需要把监听器在框架运行的时候加载进来即可--见构造函数,listener的注册是通过注解来进行的,因为测试类本身可能会有继承关系,因此需要遍历父类中的Listener,把子类+所有父类中的Listener合并起来,当然还要注意剔除掉重复注册的Listener,否则很可能导致一个Listener被执行多次(既在子类中注册过了又在父类中注册过了)
package com.crazycoder2010.iunit; import java.util.ArrayList; import java.util.List; import org.junit.Ignore; import org.junit.internal.AssumptionViolatedException; import org.junit.internal.runners.model.EachTestNotifier; import org.junit.runner.Description; import org.junit.runner.notification.RunNotifier; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; import org.junit.runners.model.Statement; import com.crazycoder2010.iunit.annotation.IUnitTestExecuteListeners; public class IUnitRunner extends BlockJUnit4ClassRunner { /** * 监听器 */ private List<IUnitTestExecuteListener> executeListeners = new ArrayList<IUnitTestExecuteListener>(); private Class<?> clazz; private TestContext testContext; public IUnitRunner(Class<?> klass) throws InitializationError { super(klass); //这个构造函数是junt的调用入口,这里我们把扩展功能的初始化写到其后 this.clazz = klass; this.testContext = new TestContext(); initListeners(); } private void initListeners(){ this.executeListeners.addAll(findListeners()); } /** * 解析为当前测试类注册的监听器 * @return */ @SuppressWarnings("rawtypes") private List<IUnitTestExecuteListener> findListeners(){ List<IUnitTestExecuteListener> result = new ArrayList<IUnitTestExecuteListener>(); List<Class> listeners = new ArrayList<Class>(); Class<?> c = this.clazz; while(c != null){ IUnitTestExecuteListeners listener = c.getAnnotation(IUnitTestExecuteListeners.class); if(listener != null){ for(Class<? extends IUnitTestExecuteListener> l : listener.value()){ if(!listeners.contains(l)){ listeners.add(l); } } } c = c.getSuperclass(); } for(Class clazz:listeners){ try { result.add((IUnitTestExecuteListener) clazz.newInstance()); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } return result; } @Override protected Object createTest() throws Exception { Object testInstance = super.createTest(); //加上我们框架的扩展功能 this.testContext.setTestInstance(testInstance); for(IUnitTestExecuteListener executeListener : this.executeListeners){ executeListener.prepareTestInstance(testContext); } return testInstance; } @Override protected void runChild(FrameworkMethod method, RunNotifier notifier) { EachTestNotifier eachNotifier= makeNotifier(method, notifier); if (method.getAnnotation(Ignore.class) != null) { runIgnored(eachNotifier); } else { runNotIgnored(method, eachNotifier); } } private EachTestNotifier makeNotifier(FrameworkMethod method, RunNotifier notifier) { Description description= describeChild(method); return new EachTestNotifier(notifier, description); } private void runIgnored(EachTestNotifier eachNotifier) { eachNotifier.fireTestIgnored(); } private void runNotIgnored(FrameworkMethod method, EachTestNotifier eachNotifier) { eachNotifier.fireTestStarted(); try { Statement statement = methodBlock(method); doBefore(); statement.evaluate(); doAfter(); } catch (AssumptionViolatedException e) { eachNotifier.addFailedAssumption(e); } catch (Throwable e) { eachNotifier.addFailure(e); testContext.setThrowable(e); doAfterThrowable(); } finally { eachNotifier.fireTestFinished(); } } private void doBefore()throws Exception{ for(IUnitTestExecuteListener executeListener : this.executeListeners){ executeListener.beforeTest(testContext); } } private void doAfter() throws Exception{ for(IUnitTestExecuteListener executeListener : this.executeListeners){ executeListener.afterTest(testContext); } } private void doAfterThrowable(){ for(IUnitTestExecuteListener executeListener : this.executeListeners){ try { executeListener.afterThrowable(testContext); } catch (Exception e) { e.printStackTrace(); } } } }2.IUnitTestExecuteListener接口
这个接口定义了测试用例执行生命周期的几个关键点
package com.crazycoder2010.iunit; /** * 框架执行监听器 * @author Kevin * */ public interface IUnitTestExecuteListener { /** * TestCase对象被加载后执行的操作,每个TestCase只会执行一次 * @param testContext */ public void prepareTestInstance(TestContext testContext) throws Exception; /** * 在每执行一个单元测试方法之前运行 * @param testContext * @throws Exception */ public void beforeTest(TestContext testContext) throws Exception; /** * 每个单元测试方法执行完时执行 * @param testContext * @throws Exception */ public void afterTest(TestContext testContext) throws Exception; /** * 发生异常时做的处理 * @param testContext */ public void afterThrowable(TestContext testContext) throws Exception; }3.IUnitTestExecuteListeners注解
这个用来给测试类注册监听器的注解,一个IUnitTestExecuteListeners可以注册多个Listener
package com.crazycoder2010.iunit.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import com.crazycoder2010.iunit.IUnitTestExecuteListener; /** * 为TestCase注册监听器 * * @author Kevin * */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited @Documented public @interface IUnitTestExecuteListeners { /** * 实际用到的监听器类 * * @return */ Class<? extends IUnitTestExecuteListener>[] value() default {}; }4.AbstractIUnitTestCase测试基类
这个类是为了便于测试定义了一个TestCase的基类,可以在此注册一些通用的监听器,注意@Runwith(IUnitRunner.class)这个是关键,否则我们写在runner中扩展的功能是不会被junit4执行到的
package com.crazycoder2010.iunit; import org.junit.runner.RunWith; import com.crazycoder2010.iunit.annotation.IUnitTestExecuteListeners; @RunWith(IUnitRunner.class) @IUnitTestExecuteListeners({DatasetProviderListener.class}) public class AbstractIUnitTestCase { }