SpringRunner
继承 SpringJUnit4ClassRunner
,SpringJUnit4ClassRunner
继承 BlockJUnit4ClassRunner
,本文中将阅读 SpringJUnit4ClassRunner
的源码
SpringRunner 源码阅读
继承BlockJUnit4ClassRunner
-
继承BlockJUnit4ClassRunner
SpringRunner
继承SpringJUnit4ClassRunner
public final class SpringRunner extends SpringJUnit4ClassRunner { /** * Construct a new {@code SpringRunner} and initialize a * {@link org.springframework.test.context.TestContextManager TestContextManager} * to provide Spring testing functionality to standard JUnit 4 tests. * @param clazz the test class to be run * @see #createTestContextManager(Class) */ public SpringRunner(Class<?> clazz) throws InitializationError { super(clazz); } }
SpringJUnit4ClassRunner
继承BlockJUnit4ClassRunner
public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner { ... }
BlockJUnit4ClassRunner
源码阅读在前面博客已经讲述过,下面直接来看下SpringJUnit4ClassRunner
重写了哪些方法:-
重写 getDescription
@Override public Description getDescription() { //当类上有@IfProfileValue注解时,代表指定了运行环境,isTestEnabledInThisEnvironment=false if (!ProfileValueUtils.isTestEnabledInThisEnvironment(getTestClass().getJavaClass())) { //返回Description,与父类区别是没有加入child return Description.createSuiteDescription(getTestClass().getJavaClass()); } //没有@IfProfileValue则使用父类方法 return super.getDescription(); }
private static boolean isTestEnabledInThisEnvironment(ProfileValueSource profileValueSource, @Nullable IfProfileValue ifProfileValue) { //如果没有ifProfileValue则返回true if (ifProfileValue == null) { return true; } //实际调用了System.getProperty(key)获取配置信息 String environmentValue = profileValueSource.get(ifProfileValue.name()); String[] annotatedValues = ifProfileValue.values(); if (StringUtils.hasLength(ifProfileValue.value())) { Assert.isTrue(annotatedValues.length == 0, () -> "Setting both the 'value' and 'values' attributes " + "of @IfProfileValue is not allowed: choose one or the other."); annotatedValues = new String[] { ifProfileValue.value() }; } for (String value : annotatedValues) { //如果指定的value与获取的环境信息相等则返回true if (ObjectUtils.nullSafeEquals(value, environmentValue)) { return true; } } return false; }
这里对
@IfProfileValue
的理解比较模糊,于是去翻了一下 javadoc-api,原示例如下/** When using SystemProfileValueSource as the ProfileValueSource implementation (which is configured by default), you can configure a test method to run only on Java VMs from Oracle as follows: **/ @IfProfileValue(name = "java.vendor", value = "Oracle Corporation") public void testSomething() { // ... }
就是说可以通过这个注解指定类或方法的运行环境,那么这个注解对于我只在本地跑单元测试来说相当
@Ignore
,本文就不对@IfProfileValue
进行详细探讨了,等后面需要了再研究一下。 -
重写run
@Override public void run(RunNotifier notifier) { //同上上文getDescription描述 if (!ProfileValueUtils.isTestEnabledInThisEnvironment(getTestClass().getJavaClass())) { notifier.fireTestIgnored(getDescription()); return; } super.run(notifier); }
-
重写withBeforeClasses
@Override protected Statement withBeforeClasses(Statement statement) { Statement junitBeforeClasses = super.withBeforeClasses(statement); //这里的关键是getTestContextManager,将Statement带上TestContext return new RunBeforeTestClassCallbacks(junitBeforeClasses, getTestContextManager()); }
通过
getTestContextManager
看一下SpringJUnit4ClassRunner
增加了哪些方法private final TestContextManager testContextManager; protected final TestContextManager getTestContextManager() { return this.testContextManager; } public SpringJUnit4ClassRunner(Class<?> clazz) throws InitializationError { super(clazz); if (logger.isDebugEnabled()) { logger.debug("SpringJUnit4ClassRunner constructor called with [" + clazz + "]"); } ensureSpringRulesAreNotPresent(clazz); this.testContextManager = createTestContextManager(clazz); } //判断不存在SpringRules private static void ensureSpringRulesAreNotPresent(Class<?> testClass) { for (Field field : testClass.getFields()) { Assert.state(!SpringClassRule.class.isAssignableFrom(field.getType()), () -> String.format( "Detected SpringClassRule field in test class [%s], " + "but SpringClassRule cannot be used with the SpringJUnit4ClassRunner.", testClass.getName())); Assert.state(!SpringMethodRule.class.isAssignableFrom(field.getType()), () -> String.format( "Detected SpringMethodRule field in test class [%s], " + "but SpringMethodRule cannot be used with the SpringJUnit4ClassRunner.", testClass.getName())); } } //创建TestContextManager protected TestContextManager createTestContextManager(Class<?> clazz) { return new TestContextManager(clazz); }
public TestContextManager(Class<?> testClass) { this(BootstrapUtils.resolveTestContextBootstrapper(BootstrapUtils.createBootstrapContext(testClass))); } public TestContextManager(TestContextBootstrapper testContextBootstrapper) { //构建testContext this.testContext = testContextBootstrapper.buildTestContext(); //注册TestExecutionListeners registerTestExecutionListeners(testContextBootstrapper.getTestExecutionListeners()); }
static BootstrapContext createBootstrapContext(Class<?> testClass) { //负责加载和关闭应用程序上下文 CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = createCacheAwareContextLoaderDelegate(); Class<? extends BootstrapContext> clazz = null; try { clazz = (Class<? extends BootstrapContext>) ClassUtils.forName( DEFAULT_BOOTSTRAP_CONTEXT_CLASS_NAME, BootstrapUtils.class.getClassLoader()); Constructor<? extends BootstrapContext> constructor = clazz.getConstructor( Class.class, CacheAwareContextLoaderDelegate.class); if (logger.isDebugEnabled()) { logger.debug(String.format("Instantiating BootstrapContext using constructor [%s]", constructor)); } //加载BootstrapContext return BeanUtils.instantiateClass(constructor, testClass, cacheAwareContextLoaderDelegate); } catch (Throwable ex) { throw new IllegalStateException("Could not load BootstrapContext [" + clazz + "]", ex); } } static TestContextBootstrapper resolveTestContextBootstrapper(BootstrapContext bootstrapContext) { Class<?> testClass = bootstrapContext.getTestClass(); Class<?> clazz = null; try { clazz = resolveExplicitTestContextBootstrapper(testClass); if (clazz == null) { clazz = resolveDefaultTestContextBootstrapper(testClass); } if (logger.isDebugEnabled()) { logger.debug(String.format("Instantiating TestContextBootstrapper for test class [%s] from class [%s]", testClass.getName(), clazz.getName())); } //加载TestContextBootstrapper TestContextBootstrapper testContextBootstrapper = BeanUtils.instantiateClass(clazz, TestContextBootstrapper.class); testContextBootstrapper.setBootstrapContext(bootstrapContext); return testContextBootstrapper; } catch (IllegalStateException ex) { throw ex; } catch (Throwable ex) { throw new IllegalStateException("Could not load TestContextBootstrapper [" + clazz + "]. Specify @BootstrapWith's 'value' attribute or make the default bootstrapper class available.", ex); } }
言归正传,回到
@BeforeClasses
执行的代码,方法就在TestContextManager
中public void beforeTestClass() throws Exception { Class<?> testClass = getTestContext().getTestClass(); if (logger.isTraceEnabled()) { logger.trace("beforeTestClass(): class [" + testClass.getName() + "]"); } getTestContext().updateState(null, null, null); for (TestExecutionListener testExecutionListener : getTestExecutionListeners()) { try { //所有的监听器处理beforeTestClass testExecutionListener.beforeTestClass(getTestContext()); } catch (Throwable ex) { logException(ex, "beforeTestClass", testExecutionListener, testClass); ReflectionUtils.rethrowException(ex); } } }
-
withAfterClasses
//@AfterClass 同上@BeforeClass @Override protected Statement withAfterClasses(Statement statement) { Statement junitAfterClasses = super.withAfterClasses(statement); return new RunAfterTestClassCallbacks(junitAfterClasses, getTestContextManager()); }
-
createTest
@Override protected Object createTest() throws Exception { Object testInstance = super.createTest(); getTestContextManager().prepareTestInstance(testInstance); return testInstance; }
public void prepareTestInstance(Object testInstance) throws Exception { if (logger.isTraceEnabled()) { logger.trace("prepareTestInstance(): instance [" + testInstance + "]"); } //textContex 设置 testInstance getTestContext().updateState(testInstance, null, null); for (TestExecutionListener testExecutionListener : getTestExecutionListeners()) { try { //在测试类实例化后调用监听器prepareTestInstance testExecutionListener.prepareTestInstance(getTestContext()); } catch (Throwable ex) { if (logger.isErrorEnabled()) { logger.error("Caught exception while allowing TestExecutionListener [" + testExecutionListener + "] to prepare test instance [" + testInstance + "]", ex); } ReflectionUtils.rethrowException(ex); } } }
-
runChild
@Override protected void runChild(FrameworkMethod frameworkMethod, RunNotifier notifier) { Description description = describeChild(frameworkMethod); if (isTestMethodIgnored(frameworkMethod)) { notifier.fireTestIgnored(description); } else { Statement statement; //对比父类区别就是增加了异常捕获 try { statement = methodBlock(frameworkMethod); } catch (Throwable ex) { statement = new Fail(ex); } runLeaf(statement, description, notifier); } }
-
methodBlock
@Override protected Statement methodBlock(FrameworkMethod frameworkMethod) { Object testInstance; try { testInstance = new ReflectiveCallable() { @Override protected Object runReflectiveCall() throws Throwable { //对比父类没有重写带参重载方法,父类中带参传入后并没有使用 return createTest(); } }.run(); } catch (Throwable ex) { return new Fail(ex); } Statement statement = methodInvoker(frameworkMethod, testInstance); //对比父类新增statement调用前加载testcontext且不影响junit默认的操作 statement = withBeforeTestExecutionCallbacks(frameworkMethod, testInstance, statement); //对比父类新增statement调用后处理testcontext且不影响junit默认的操作 statement = withAfterTestExecutionCallbacks(frameworkMethod, testInstance, statement); statement = possiblyExpectingExceptions(frameworkMethod, testInstance, statement); statement = withBefores(frameworkMethod, testInstance, statement); statement = withAfters(frameworkMethod, testInstance, statement); //处理@Rule statement = withRulesReflectively(frameworkMethod, testInstance, statement); //处理@Repeat statement = withPotentialRepeat(frameworkMethod, testInstance, statement); statement = withPotentialTimeout(frameworkMethod, testInstance, statement); return statement; }
-
possiblyExpectingExceptions
//对比父类异常getExpectedException从方法中获取 @Override protected Statement possiblyExpectingExceptions(FrameworkMethod frameworkMethod, Object testInstance, Statement next) { Class<? extends Throwable> expectedException = getExpectedException(frameworkMethod); return (expectedException != null ? new ExpectException(next, expectedException) : next); } @Nullable protected Class<? extends Throwable> getExpectedException(FrameworkMethod frameworkMethod) { Test test = frameworkMethod.getAnnotation(Test.class); return (test != null && test.expected() != Test.None.class ? test.expected() : null); }
-
withPotentialTimeout
//对比父类增加了@Timed注解的支持,当然也支持 @Test(timeout=...) ,但两者不能同时使用 @Override // Retain the following warning suppression for deprecation (even if Eclipse // states it is unnecessary) since withPotentialTimeout(FrameworkMethod,Object,Statement) // in BlockJUnit4ClassRunner has been deprecated. @SuppressWarnings("deprecation") protected Statement withPotentialTimeout(FrameworkMethod frameworkMethod, Object testInstance, Statement next) { Statement statement = null; long springTimeout = getSpringTimeout(frameworkMethod); long junitTimeout = getJUnitTimeout(frameworkMethod); if (springTimeout > 0 && junitTimeout > 0) { String msg = String.format("Test method [%s] has been configured with Spring's @Timed(millis=%s) and " + "JUnit's @Test(timeout=%s) annotations, but only one declaration of a 'timeout' is " + "permitted per test method.", frameworkMethod.getMethod(), springTimeout, junitTimeout); logger.error(msg); throw new IllegalStateException(msg); } else if (springTimeout > 0) { statement = new SpringFailOnTimeout(next, springTimeout); } else if (junitTimeout > 0) { statement = FailOnTimeout.builder().withTimeout(junitTimeout, TimeUnit.MILLISECONDS).build(next); } else { statement = next; } return statement; } protected long getJUnitTimeout(FrameworkMethod frameworkMethod) { Test test = frameworkMethod.getAnnotation(Test.class); return (test == null ? 0 : Math.max(0, test.timeout())); } protected long getSpringTimeout(FrameworkMethod frameworkMethod) { return TestAnnotationUtils.getTimeout(frameworkMethod.getMethod()); }
-
withBefores
@Override protected Statement withBefores(FrameworkMethod frameworkMethod, Object testInstance, Statement statement) { Statement junitBefores = super.withBefores(frameworkMethod, testInstance, statement); //@Before 同上文 @BeforeClass return new RunBeforeTestMethodCallbacks(junitBefores, testInstance, frameworkMethod.getMethod(), getTestContextManager()); }
-
withAfters
@Override protected Statement withAfters(FrameworkMethod frameworkMethod, Object testInstance, Statement statement) { Statement junitAfters = super.withAfters(frameworkMethod, testInstance, statement); //@After 同上文 @BeforeClass return new RunAfterTestMethodCallbacks(junitAfters, testInstance, frameworkMethod.getMethod(), getTestContextManager()); }
-
除重写方法以外其他方法
-
除重写方法以外其他方法
SpringJUnit4ClassRunner
除重写方法以外还有一些其他方法,上节中也已经阅读过一些,本节就阅读一下剩余的方法-
static 语句块
static { //断言ClassLoader加载Throwables Assert.state(ClassUtils.isPresent("org.junit.internal.Throwables", SpringJUnit4ClassRunner.class.getClassLoader()), "SpringJUnit4ClassRunner requires JUnit 4.12 or higher."); //找到withRules方法 Method method = ReflectionUtils.findMethod(SpringJUnit4ClassRunner.class, "withRules", FrameworkMethod.class, Object.class, Statement.class); //断言成功找到withRules Assert.state(method != null, "SpringJUnit4ClassRunner requires JUnit 4.12 or higher"); //令withRules方法可访问,做这个处理是因为这个是父类的私有方法 ReflectionUtils.makeAccessible(method); withRulesMethod = method; }
-
isTestMethodIgnored
protected boolean isTestMethodIgnored(FrameworkMethod frameworkMethod) { Method method = frameworkMethod.getMethod(); //增加@IfProfileValue的处理 return (method.isAnnotationPresent(Ignore.class) || !ProfileValueUtils.isTestEnabledInThisEnvironment(method, getTestClass().getJavaClass())); }
-