Junit源码阅读_springrunner

2021-07-27
halley.fang

SpringRunner 继承 SpringJUnit4ClassRunnerSpringJUnit4ClassRunner 继承 BlockJUnit4ClassRunner,本文中将阅读 SpringJUnit4ClassRunner 的源码

SpringRunner 源码阅读

继承BlockJUnit4ClassRunner

  1. 继承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 重写了哪些方法:

    1. 重写 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 进行详细探讨了,等后面需要了再研究一下。

    2. 重写run

      @Override
      public void run(RunNotifier notifier) {
        //同上上文getDescription描述
        if (!ProfileValueUtils.isTestEnabledInThisEnvironment(getTestClass().getJavaClass())) {
          notifier.fireTestIgnored(getDescription());
          return;
        }
        super.run(notifier);
      }
    3. 重写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);
      			}
      		}
      	}
    4. withAfterClasses

      //@AfterClass 同上@BeforeClass
      @Override
      protected Statement withAfterClasses(Statement statement) {
        Statement junitAfterClasses = super.withAfterClasses(statement);
        return new RunAfterTestClassCallbacks(junitAfterClasses, getTestContextManager());
      }
    5. 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);
          }
        }
      }
    6. 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);
        }
      }
    7. 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;
      	}
    8. 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);
      }
    9. 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());
      }
    10. 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());
      }
    11. 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());
      }

除重写方法以外其他方法

  1. 除重写方法以外其他方法

    SpringJUnit4ClassRunner 除重写方法以外还有一些其他方法,上节中也已经阅读过一些,本节就阅读一下剩余的方法

    1. 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;
      }
    2. isTestMethodIgnored

      protected boolean isTestMethodIgnored(FrameworkMethod frameworkMethod) {
        Method method = frameworkMethod.getMethod();
        //增加@IfProfileValue的处理
        return (method.isAnnotationPresent(Ignore.class) ||
            !ProfileValueUtils.isTestEnabledInThisEnvironment(method, getTestClass().getJavaClass()));
      }

testContext


Similar Posts

Comments

Table of contents