junit4 下的全部的testcase都是在Runner下執行的, 能夠將Runner理解爲junit運行的容器, 默認狀況下junit會使用JUnit4ClassRunner做爲全部testcase的執行容器。java
若是要定製本身的junit, 則能夠實現本身的Runner,最簡單的辦法就是Junit4ClassRunner繼承, spring-test, unitils這些框架就是採用這樣的作法。spring
如在spring中是SpringJUnit4ClassRunner, 在unitils中是UnitilsJUnit4TestClassRunner, 通常咱們的testcase都是在經過eclipse插件來執行的, eclipse的junit插件會在執行的時候會初始化指定的Runner。初始化的過程能夠在ClassRequest中找到。app
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類eclipse
不一樣於上一節提到的org.junit.runners.model.TestClass類ide
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")); } } }