将工程源码和测试源码进行编译,输出到了target目录
通过java命令运行com.intellij.rt.junit.JUnitStarter,参数中指定了junit的版本以及单测用例名称
java com.intellij.rt.junit.JUnitStarter -ideVersion5 -junit4 fung.MyTest,test
public static void main(String[] args) {List<String> argList = new ArrayList(Arrays.asList(args));ArrayList<String> listeners = new ArrayList();String[] name = new String[1];String agentName = processParameters(argList, listeners, name);if (!"com.intellij.junit5.JUnit5IdeaTestRunner".equals(agentName) && !canWorkWithJUnitVersion(System.err, agentName)) {System.exit(-3);}if (!checkVersion(args, System.err)) {System.exit(-3);}String[] array = (String[])argList.toArray(new String[0]);int exitCode = prepareStreamsAndStart(array, agentName, listeners, name[0]);System.exit(exitCode);}
...// 处理参数,主要用来确定使用哪个版本的junit框架,同时根据入参填充listenersString agentName = processParameters(argList, listeners, name);...// 启动测试int exitCode = prepareStreamsAndStart(array, agentName, listeners, name[0]);...
public class MyTest {public static void main(String[] args) {JUnitCore runner = new JUnitCore();Request request = Request.aClass(MyTest.class);Result result = runner.run(request.getRunner());System.out.println(JSON.toJSONString(result));}@Testpublic void test1() {System.out.println("test1");}@Testpublic void test2() {System.out.println("test2");}@Testpublic void test3() {System.out.println("test3");}}
public static void main(String[] args) {// 1. 创建JUnitCore的实例JUnitCore runner = new JUnitCore();// 2. 通过单测类的Class对象构建RequestRequest request = Request.aClass(MyTest.class);// 3. 运行单元测试Result result = runner.run(request.getRunner());// 4. 打印结果System.out.println(JSON.toJSONString(result));}
public Runner getRunner() {if (runner == null) {synchronized (runnerLock) {if (runner == null) {runner = new AllDefaultPossibilitiesBuilder(canUseSuiteMethod).safeRunnerForClass(fTestClass);}}}return runner;}
public Runner safeRunnerForClass(Class<?> testClass) {try {return runnerForClass(testClass);} catch (Throwable e) {return new ErrorReportingRunner(testClass, e);}}
public Runner runnerForClass(Class<?> testClass) throws Throwable {List<RunnerBuilder> builders = Arrays.asList(ignoredBuilder(),annotatedBuilder(),suiteMethodBuilder(),junit3Builder(),junit4Builder());for (RunnerBuilder each : builders) {Runner runner = each.safeRunnerForClass(testClass);if (runner != null) {return runner;}}return null;}
如果解析失败了,则返回ErrorReportingRunner
如果测试类上有@Ignore注解,则返回IgnoredClassRunner
如果测试类上有@RunWith注解,则使用@RunWith的值实例化一个Runner返回
如果canUseSuiteMethod=true,则返回SuiteMethod,其继承自JUnit38ClassRunner,是比较早期的JUnit版本了
如果JUnit版本在4之前,则返回JUnit38ClassRunner
如果上面都不满足,则返回BlockJUnit4ClassRunner,其表示的是一个标准的JUnit4测试模型
public void run(final RunNotifier notifier) {EachTestNotifier testNotifier = new EachTestNotifier(notifier,getDescription());try {Statement statement = classBlock(notifier);statement.evaluate();} catch (AssumptionViolatedException e) {testNotifier.addFailedAssumption(e);} catch (StoppedByUserException e) {throw e;} catch (Throwable e) {testNotifier.addFailure(e);}}
RunBefores,会先运行befores里封装的方法(一般是标记了@BeforeClass或@Before),再运行next.evaluate()
RunAfters,会先运行next.evaluate(),再运行afters里封装的方法(一般是标记了@AfterClass或@After)
InvokeMethod,直接运行testMethod中封装的方法
public void evaluate() throws Throwable {testMethod.invokeExplosively(target);}
public Object invokeExplosively(final Object target, final Object... params)throws Throwable {return new ReflectiveCallable() {protected Object runReflectiveCall() throws Throwable {return method.invoke(target, params);}}.run();}
(SpringRunner.class)(locations = { "/spring/spring-mybeans.xml" })public class SpringRunnerTest {private MyTestBean myTestBean;public void test() {myTestBean.test();}}
public final class SpringRunner extends SpringJUnit4ClassRunner {public SpringRunner(Class<?> clazz) throws InitializationError {super(clazz);}}
public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner {...}
扩展了构造函数,多创建了一个TestContextManager实例。
扩展了createTest()方法,会额外调用TestContextManager的prepareTestInstance方法。
扩展了beforeClass,在执行@BeforeClass注解的方法前,会先调用TestContextManager的beforeTestClass方法。
扩展了before,在执行@Before注解的方法前,会先调用TestContextManager的beforeTestMethod方法。
扩展了afterClass,在执行@AfterClass注解的方法之后,会再调用TestContextManager的afterTestClass方法。
扩展了after,在执行@After注解的方法之后,会再调用TestContextManager的after方法。
public class DefaultTestContext implements TestContext {...public ApplicationContext getApplicationContext() {ApplicationContext context = this.cacheAwareContextLoaderDelegate.loadContext(this.mergedContextConfiguration);if (context instanceof ConfigurableApplicationContext) {ConfigurableApplicationContext cac = (ConfigurableApplicationContext) context;Assert.state(cac.isActive(), () ->"The ApplicationContext loaded for [" + this.mergedContextConfiguration +"] is not active. This may be due to one of the following reasons: " +"1) the context was closed programmatically by user code; " +"2) the context was closed during parallel test execution either " +"according to @DirtiesContext semantics or due to automatic eviction " +"from the ContextCache due to a maximum cache size policy.");}return context;}...}
public void prepareTestInstance(Object testInstance) throws Exception {if (logger.isTraceEnabled()) {logger.trace("prepareTestInstance(): instance [" + testInstance + "]");}getTestContext().updateState(testInstance, null, null);for (TestExecutionListener testExecutionListener : getTestExecutionListeners()) {try {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);}}}
public void prepareTestInstance(TestContext testContext) throws Exception {if (logger.isDebugEnabled()) {logger.debug("Performing dependency injection for test context [" + testContext + "].");}injectDependencies(testContext);}
protected void injectDependencies(TestContext testContext) throws Exception {Object bean = testContext.getTestInstance();Class<?> clazz = testContext.getTestClass();// 这里调用TestContext的getApplicationContext方法,构建Spring容器AutowireCapableBeanFactory beanFactory = testContext.getApplicationContext().getAutowireCapableBeanFactory();beanFactory.autowireBeanProperties(bean, AutowireCapableBeanFactory.AUTOWIRE_NO, false);beanFactory.initializeBean(bean, clazz.getName() + AutowireCapableBeanFactory.ORIGINAL_INSTANCE_SUFFIX);testContext.removeAttribute(REINJECT_DEPENDENCIES_ATTRIBUTE);}
(SpringRunner.class)(classes = Application.class)public class MySpringBootTest {private MyTestBean myTestBean;public void test() {myTestBean.test();}}
public SpringBootTest {...}
public TestContextManager(Class<?> testClass) {this(BootstrapUtils.resolveTestContextBootstrapper(BootstrapUtils.createBootstrapContext(testClass)));}
public TestContextManager(TestContextBootstrapper testContextBootstrapper) {this.testContext = testContextBootstrapper.buildTestContext();registerTestExecutionListeners(testContextBootstrapper.getTestExecutionListeners());}
public abstract class AbstractTestContextBootstrapper implements TestContextBootstrapper {...public TestContext buildTestContext() {return new DefaultTestContext(getBootstrapContext().getTestClass(), buildMergedContextConfiguration(),getCacheAwareContextLoaderDelegate());}...}
testClass,被测试的类元数据
MergedContextConfiguration,封装了声明在测试类上的与测试容器相关的注解,如@ContextConfiguration, @ActiveProfiles, @TestPropertySource
CacheAwareContextLoaderDelegate,用来loading或closing容器
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 =BeanUtils.instantiateClass(clazz, TestContextBootstrapper.class);testContextBootstrapper.setBootstrapContext(bootstrapContext);return testContextBootstrapper;}...}
private static Class<?> resolveExplicitTestContextBootstrapper(Class<?> testClass) {Set<BootstrapWith> annotations = AnnotatedElementUtils.findAllMergedAnnotations(testClass, BootstrapWith.class);if (annotations.isEmpty()) {return null;}if (annotations.size() == 1) {return annotations.iterator().next().value();}// 获取@BootstrapWith注解的值BootstrapWith bootstrapWith = testClass.getDeclaredAnnotation(BootstrapWith.class);if (bootstrapWith != null) {return bootstrapWith.value();}throw new IllegalStateException(String.format("Configuration error: found multiple declarations of @BootstrapWith for test class [%s]: %s",testClass.getName(), annotations));}
这里会通过@BootstrapWith注解的值,实例化定制的TestContextBootstrapper,从而提供定制的TestContext
将ContextLoader替换为了SpringBootContextLoader
增加了DefaultTestExecutionListenersPostProcessor对TestExecutionListener进行增强处理
增加了对webApplicationType的处理
public class SpringBootContextLoader extends AbstractContextLoader {public ApplicationContext loadContext(MergedContextConfiguration config)throws Exception {Class<?>[] configClasses = config.getClasses();String[] configLocations = config.getLocations();Assert.state(!ObjectUtils.isEmpty(configClasses)|| !ObjectUtils.isEmpty(configLocations),() -> "No configuration classes "+ "or locations found in @SpringApplicationConfiguration. "+ "For default configuration detection to work you need "+ "Spring 4.0.3 or better (found " + SpringVersion.getVersion()+ ").");SpringApplication application = getSpringApplication();// 设置mainApplicationClassapplication.setMainApplicationClass(config.getTestClass());// 设置primarySourcesapplication.addPrimarySources(Arrays.asList(configClasses));// 添加configLocationsapplication.getSources().addAll(Arrays.asList(configLocations));// 获取environmentConfigurableEnvironment environment = getEnvironment();if (!ObjectUtils.isEmpty(config.getActiveProfiles())) {setActiveProfiles(environment, config.getActiveProfiles());}ResourceLoader resourceLoader = (application.getResourceLoader() != null)? application.getResourceLoader(): new DefaultResourceLoader(getClass().getClassLoader());TestPropertySourceUtils.addPropertiesFilesToEnvironment(environment,resourceLoader, config.getPropertySourceLocations());TestPropertySourceUtils.addInlinedPropertiesToEnvironment(environment,getInlinedProperties(config));application.setEnvironment(environment);// 获取并设置initializersList<ApplicationContextInitializer<?>> initializers = getInitializers(config,application);if (config instanceof WebMergedContextConfiguration) {application.setWebApplicationType(WebApplicationType.SERVLET);if (!isEmbeddedWebEnvironment(config)) {new WebConfigurer().configure(config, application, initializers);}}else if (config instanceof ReactiveWebMergedContextConfiguration) {application.setWebApplicationType(WebApplicationType.REACTIVE);if (!isEmbeddedWebEnvironment(config)) {new ReactiveWebConfigurer().configure(application);}}else {application.setWebApplicationType(WebApplicationType.NONE);}application.setInitializers(initializers);// 运行SpringBoot应用return application.run();}}
maven-clean-plugin:2.5:clean,用于清理target目录
maven-resources-plugin:2.6:resources,将主工程目录下的资源文件移动到target目录下的classes目录中
maven-compiler-plugin:3.1:compile,将主工程目录下的java源码编译为字节码,并移动到target目录下的classes目录中
maven-resources-plugin:2.6:testResources,将测试工程目录下的资源文件移动到target目录下的test-classes目录中
maven-compiler-plugin:3.1:testCompile,将测试工程目录下的java源码编译为字节码,并移动到target目录下的classes目录中
maven-surefire-plugin:2.12.4:test,运行单测
<dependency><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><version>2.9</version></dependency><dependency><groupId>org.apache.maven.surefire</groupId><artifactId>surefire-junit4</artifactId><version>3.0.0-M7</version></dependency>
private static void execute( Class<?> testClass, Notifier notifier, Filter filter ){final int classModifiers = testClass.getModifiers();if ( !isAbstract( classModifiers ) && !isInterface( classModifiers ) ){Request request = aClass( testClass );if ( filter != null ){request = request.filterWith( filter );}Runner runner = request.getRunner();if ( countTestsInRunner( runner.getDescription() ) != 0 ){runner.run( notifier );}}}
通过IDEA直接运行单测时,会通过JUnitStarter的main方法作为入口,最终调用Junit运行单元测试。
Junit4将@Before、@Test、@After这些注解打标的方法都抽象成了Statement,整个单测的运行过程,实际上就是一系列Statement的运行过程。方法的调用是通过反射的方式实现的。
借助于@RunWith(SpringRunner.class)注解,测试框架会运行SpringRunner实例的run方法,通过TestContextManager创建TestContext,并启动Spring容器。SpringRunner和SpringJUnit4ClassRunner实际上是等价的。
借助于@SpringBootTest和@BootstrapWith(SpringBootTestContextBootstrapper.class)注解,测试框架通过SpringBootTestContextBootstrapper增强了TestContext,达到了启动SpringBoot应用的目的。
Maven通过运行maven-surefire-plugin:2.12.4:test启动单元测试,其核心是通过JUnit4Provider调用了JUnit框架的代码。
推荐阅读
ModelScope开源模型社区评测专场重磅来袭
发布你对ModelScope开源模型社区的体验评测,免费使用模型库搭建属于你的小应用,有机会获得AirPods和阿里云定制礼品等。
点击阅读原文查看详情。