吃柿子专挑软的捏。JUnit4的核心是org.junit.runner.Runner,但是它涉及的类型太多了,今天看几个简单的类型,清扫它的外围。
JUnit4的输入,是一个或多个(成组测试时)单元测试类的Class对象。为了使用反射机制、为了支持过滤/ filtering和排序/ sorting等,JUnit4进行了一系列预处理。Request、Description和TestClass、FrameworkMethod、FrameworkField等由此产生。
由于JDK中对标注的处理代价高昂,TestClass事先对单元测试类的标注相关的内容提取出来,1便于处理,2尽量共享。TestClass的3个成员变量
private final Class<?> fClass;
private Map<Class<?>, List<FrameworkMethod>> fMethodsForAnnotations= new HashMap<>();
private Map<Class<?>, List<FrameworkField>> fFieldsForAnnotations= new HashMap<>();
fClass是TestClass封装的单元测试类的Class对象。public Class<?> getJavaClass() 返回的就是它,而String getName()是它映射的单元测试类的类全名(或"null")。
fMethodsForAnnotations 每一种标识与被该标注修饰的方法的封装类的List即List<FrameworkMethod>的键值对。如果JUnit4设计一个标记接口JUnit4Annotation,其类型定义可以更小一些 如 Map<Class<? extends JUnit4Annotation>, List<FrameworkMethod> >。要是我,会这么做。
fFieldsForAnnotations 暂时不想看它,for Rules。
TestClass的构造器初始化3个成员变量。它要求单元测试类只能够有一个构造器。通过反射,构造器将单元测试类的所有祖先类放在一个List<Class<?>>中,找到它们每一个声明的方法,并按照方法的标注填入fMethodsForAnnotations和fFieldsForAnnotations。
而曝露出来的接口则是按照标注(key)获得Map中映射的List。
public List<FrameworkMethod> getAnnotatedMethods(Class<? extends Annotation> annotationClass)
public List<FrameworkField> getAnnotatedFields( Class<? extends Annotation> annotationClass)
以及
public <T> List<T> getAnnotatedFieldValues(Object test,Class<? extends Annotation> annotationClass, Class<T> valueClass) 暂时忽略
public Annotation[] getAnnotations()
public Constructor<?> getOnlyConstructor()
TestClass的源代码比较容易看懂,目标清晰的代码都容易懂。
相关的类:FrameworkMethod、FrameworkField和它们的父类FrameworkMember<T>。
封装一个被标注的方法,标注为@Test 、@Before、@After、@BeforeClass、@AfterClass、@Ignore等。
以单元测试类TestUnit为例(修改了一下原例子——反映参数验证的作用.增加了addx(int x)方法):
package myTest.TestClass; import static org.junit.Assert.*; import org.junit.Test; public class TestUnit{ public TestUnit(){ } @Test public void addx(int x){ assertEquals(5, 1+x); } @Test public void add(){ assertEquals(5.0,4.0, 0.1); } @Test public void hello(){ assertEquals(5.0,4.0, 0.1); } }在BlueJ中能够直接运行的只有add()、hello()。
package myTest.TestClass; import static tool.Print.*; import java.util.List; import java.lang.reflect.*; import org.junit.runners.model.*; import org.junit.Test; /** * org.junit.runners.model.FrameworkMethod * 封装一个被测试的方法 * @Test 、@Before、@After、@BeforeClass、@AfterClass、@Ignore */ public class TestClassDemo{ public static void test()throws Throwable{ TestClass klass = new TestClass(TestUnit.class); pln(klass.getName() ); List<FrameworkMethod> list = klass.getAnnotatedMethods(Test.class); for(FrameworkMethod fm :list){ try { fm.invokeExplosively((TestUnit)klass.getJavaClass().newInstance(), new Object[0]) ; }catch (Throwable e) { pln(e); }finally{ pln(fm.getName()+" invoked!" ); } } } }
注意:invokeExplosively(final Object target, final Object... params)用于调用单元测试类的@Test方法,参数target为(TestUnit)klass.getJavaClass().newInstance(),通常的测试方法的没有参数,取new Object[0]。
运行结果:
myTest.TestClass.TestUnit
java.lang.AssertionError:expected:<5.0> but was:<4.0>
add invoked!
java.lang.AssertionError:expected:<5.0> but was:<4.0>
hello invoked!
java.lang.IllegalArgumentException:wrong number of arguments
addx invoked!
单元测试类可能使用各种@Target(ElementType.TYPE)的标注如@Ignore、@RunWith、@SuiteClasse。org.junit.runners.model.RunnerBuilder针对这些标注产生不同的Runner。RunnerBuilder虽然取名builder,其实是工厂方法。
public abstract Runner runnerForClass(Class<?>testClass) throws Throwable; //工厂方法
1. NullBuilder 如同定义数学的0一样。@Override Runner runnerForClass(Class<?>)返回null。删除
2. IgnoredBuilder 如果测试类由@Ignore标注,生成一个Runner子类IgnoredClassRunner对象。删除
3. AnnotatedBuilder 如果测试类由@RunWith标注,生成一个Runner对象。
4.兼容用JUnit3Builder 如果测试类使用JUnit3风格,生成一个Unit38ClassRunner对象。删除
5. JUnit4Builder 如果测试类使用JUnit4风格,生成一个BlockJUnit4ClassRunner对象。删除
6. SuiteMethodBuilder 组的问题保留
7. AllDefaultPossibilitiesBuilder 合集。按照IgnoredBuilder、AnnotatedBuilder、SuiteMethodBuilder(如果不使用组,则返回NullBuilder)、JUnit3Builder和JUnit4Builder的顺序创建各种RunnerBuilder,先调用RunnerBuilder.safeRunnerForClass方法再判断一个RunnerBuilder是否为null,非空则是AllDefaultPossibilitiesBuilder将使用的RunnerBuilder。