Junit 源码剖析(二)
junit4 下的所有的testcase都是在Runner下执行的, 可以将Runner理解为junit运行的容器, 默认情况下junit会使用JUnit4ClassRunner作为所有testcase的执行容器。
如果要定制自己的junit, 则可以实现自己的Runner,最简单的办法就是Junit4ClassRunner继承, spring-test, unitils这些框架就是采用这样的做法。
如在spring中是SpringJUnit4ClassRunner, 在unitils中是UnitilsJUnit4TestClassRunner, 一般我们的testcase都是在通过eclipse插件来执行的, eclipse的junit插件会在执行的时候会初始化指定的Runner。初始化的过程可以在ClassRequest中找到。
org.junit.internal.runners.Junit4ClassRunner
package org.junit.internal.runners; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import org.junit.runner.Description; import org.junit.runner.Runner; import org.junit.runner.manipulation.Filter; import org.junit.runner.manipulation.Filterable; import org.junit.runner.manipulation.NoTestsRemainException; import org.junit.runner.manipulation.Sortable; import org.junit.runner.manipulation.Sorter; import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunNotifier; import org.junit.runners.BlockJUnit4ClassRunner; /** * @deprecated Included for backwards compatibility with JUnit 4.4. Will be * removed in the next release. Please use * {@link BlockJUnit4ClassRunner} in place of * {@link JUnit4ClassRunner}. * * This may disappear as soon as 1 April 2009 */ @Deprecated public class JUnit4ClassRunner extends Runner implements Filterable, Sortable { private final List<Method> fTestMethods; private TestClass fTestClass; //将要测试的TestCase实例Class对象传入 public JUnit4ClassRunner(Class<?> klass) throws InitializationError { fTestClass = new TestClass(klass); fTestMethods = getTestMethods(); //对要进行测试的方法展开验证 validate(); } //获取@test的方法List protected List<Method> getTestMethods() { return fTestClass.getTestMethods(); } //对要进行测试的方法展开验证 protected void validate() throws InitializationError { MethodValidator methodValidator = new MethodValidator(fTestClass); methodValidator.validateMethodsForDefaultRunner(); methodValidator.assertValid(); } @Override public void run(final RunNotifier notifier) { new ClassRoadie(notifier, fTestClass, getDescription(), new Runnable() { public void run() { runMethods(notifier); } }).runProtected(); } protected void runMethods(final RunNotifier notifier) { for (Method method : fTestMethods) invokeTestMethod(method, notifier); } @Override public Description getDescription() { Description spec = Description.createSuiteDescription(getName(), classAnnotations()); List<Method> testMethods = fTestMethods; for (Method method : testMethods) spec.addChild(methodDescription(method)); return spec; } protected Annotation[] classAnnotations() { return fTestClass.getJavaClass().getAnnotations(); } protected String getName() { return getTestClass().getName(); } protected Object createTest() throws Exception { return getTestClass().getConstructor().newInstance(); } protected void invokeTestMethod(Method method, RunNotifier notifier) { Description description = methodDescription(method); Object test; try { test = createTest(); } catch(InvocationTargetException e) { testAborted(notifier, description, e.getCause()); return; } catch(Exception e) { testAborted(notifier, description, e); return; } TestMethod testMethod = wrapMethod(method); new MethodRoadie(test, testMethod, notifier, description).run(); } private void testAborted(RunNotifier notifier, Description description, Throwable e) { notifier.fireTestStarted(description); notifier.fireTestFailure(new Failure(description, e)); notifier.fireTestFinished(description); } protected TestMethod wrapMethod(Method method) { return new TestMethod(method, fTestClass); } protected String testName(Method method) { return method.getName(); } protected Description methodDescription(Method method) { return Description.createTestDescription(getTestClass().getJavaClass(), testName(method), testAnnotations(method)); } protected Annotation[] testAnnotations(Method method) { return method.getAnnotations(); } public void filter(Filter filter) throws NoTestsRemainException { for (Iterator<Method> iter = fTestMethods.iterator(); iter.hasNext();) { Method method = iter.next(); if (!filter.shouldRun(methodDescription(method))) iter.remove(); } if (fTestMethods.isEmpty()) throw new NoTestsRemainException(); } public void sort(final Sorter sorter) { Collections.sort(fTestMethods, new Comparator<Method>() { public int compare(Method o1, Method o2) { return sorter.compare(methodDescription(o1), methodDescription(o2)); } }); } protected TestClass getTestClass() { return fTestClass; } }
org.junit.internal.runners.TestClass类
不同于上一节提到的org.junit.runners.model.TestClass类
package org.junit.internal.runners; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runners.BlockJUnit4ClassRunner; /** * @deprecated Included for backwards compatibility with JUnit 4.4. Will be * removed in the next release. Please use * {@link BlockJUnit4ClassRunner} in place of * {@link JUnit4ClassRunner}. */ @Deprecated public class TestClass { private final Class<?> fClass; public TestClass(Class<?> klass) { fClass = klass; } public List<Method> getTestMethods() { return getAnnotatedMethods(Test.class); } List<Method> getBefores() { return getAnnotatedMethods(BeforeClass.class); } List<Method> getAfters() { return getAnnotatedMethods(AfterClass.class); } public List<Method> getAnnotatedMethods(Class<? extends Annotation> annotationClass) { List<Method> results = new ArrayList<Method>(); for (Class<?> eachClass : getSuperClasses(fClass)) { Method[] methods = eachClass.getDeclaredMethods(); for (Method eachMethod : methods) { Annotation annotation = eachMethod.getAnnotation(annotationClass); if (annotation != null && !isShadowed(eachMethod, results)) results.add(eachMethod); } } if (runsTopToBottom(annotationClass)) Collections.reverse(results); return results; } private boolean runsTopToBottom(Class<? extends Annotation> annotation) { return annotation.equals(Before.class) || annotation.equals(BeforeClass.class); } private boolean isShadowed(Method method, List<Method> results) { for (Method each : results) { if (isShadowed(method, each)) return true; } return false; } private boolean isShadowed(Method current, Method previous) { if (!previous.getName().equals(current.getName())) return false; if (previous.getParameterTypes().length != current.getParameterTypes().length) return false; for (int i = 0; i < previous.getParameterTypes().length; i++) { if (!previous.getParameterTypes()[i].equals(current.getParameterTypes()[i])) return false; } return true; } private List<Class<?>> getSuperClasses(Class<?> testClass) { ArrayList<Class<?>> results = new ArrayList<Class<?>>(); Class<?> current = testClass; while (current != null) { results.add(current); current = current.getSuperclass(); } return results; } public Constructor<?> getConstructor() throws SecurityException, NoSuchMethodException { return fClass.getConstructor(); } public Class<?> getJavaClass() { return fClass; } public String getName() { return fClass.getName(); } }
org.junit.internal.runners.MethodValidator类
用于在org.junit.internal.runners.Junit4ClassRunner 类当中进行方法验证
package org.junit.internal.runners; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runners.BlockJUnit4ClassRunner; /** * @deprecated Included for backwards compatibility with JUnit 4.4. Will be * removed in the next release. Please use * {@link BlockJUnit4ClassRunner} in place of {@link JUnit4ClassRunner}. */ @Deprecated public class MethodValidator { private final List<Throwable> fErrors= new ArrayList<Throwable>(); private TestClass fTestClass; //org.junit.internal.runners.JUnit4ClassRunner类中 //validate()方法中调用该构造函数-----第一步 public MethodValidator(TestClass testClass) { fTestClass = testClass; } public void validateInstanceMethods() { validateTestMethods(After.class, false); validateTestMethods(Before.class, false); validateTestMethods(Test.class, false); List<Method> methods= fTestClass.getAnnotatedMethods(Test.class); if (methods.size() == 0) fErrors.add(new Exception("No runnable methods")); } public void validateStaticMethods() { validateTestMethods(BeforeClass.class, true); validateTestMethods(AfterClass.class, true); } //Junit4ClassRunner类中调用第二步 public List<Throwable> validateMethodsForDefaultRunner() { //校验无参构造方法 validateNoArgConstructor(); //校验注解方法 validateStaticMethods(); validateInstanceMethods(); return fErrors; } //Junit4ClassRunner类中第三步 public void assertValid() throws InitializationError { if (!fErrors.isEmpty()) throw new InitializationError(fErrors); } public void validateNoArgConstructor() { try { fTestClass.getConstructor(); } catch (Exception e) { fErrors.add(new Exception("Test class should have public zero-argument constructor", e)); } } //校验要测试的方法 是否静态方法、是否public方法、是否返回值void、是否无参数 //@param annotation 要测试的annotation类 //@param isStatic 是否要求静态方法 private void validateTestMethods(Class<? extends Annotation> annotation,boolean isStatic) { List<Method> methods= fTestClass.getAnnotatedMethods(annotation); for (Method each : methods) { if (Modifier.isStatic(each.getModifiers()) != isStatic) { String state= isStatic ? "should" : "should not"; fErrors.add(new Exception("Method " + each.getName() + "() " + state + " be static")); } if (!Modifier.isPublic(each.getDeclaringClass().getModifiers())) fErrors.add(new Exception("Class " + each.getDeclaringClass().getName() + " should be public")); if (!Modifier.isPublic(each.getModifiers())) fErrors.add(new Exception("Method " + each.getName() + " should be public")); if (each.getReturnType() != Void.TYPE) fErrors.add(new Exception("Method " + each.getName() + " should be void")); if (each.getParameterTypes().length != 0) fErrors.add(new Exception("Method " + each.getName() + " should have no parameters")); } } }