关于后端:单元测试运行原理探究

38次阅读

共计 17719 个字符,预计需要花费 45 分钟才能阅读完成。

简介:单元测试是软件开发过程中的重要一环,好的单测能够帮忙咱们更早的发现问题,为零碎的稳固运行提供保障。单测还是很好的阐明文档,咱们往往看单测用例就可能理解到作者对类的设计用意。代码重构时也离不开单测,丰盛的单测用例会使咱们重构代码时信念满满。尽管单测如此重要,然而始终来都不是很分明其运行原理,也不晓得为什么要做这样或那样的配置,这样究竟是不行的,于是筹备花工夫探索下单测原理,并在此记录。前言单元测试是软件开发过程中的重要一环,好的单测能够帮忙咱们更早的发现问题,为零碎的稳固运行提供保障。单测还是很好的阐明文档,咱们往往看单测用例就可能理解到作者对类的设计用意。代码重构时也离不开单测,丰盛的单测用例会使咱们重构代码时信念满满。尽管单测如此重要,然而始终来都不是很分明其运行原理,也不晓得为什么要做这样或那样的配置,这样究竟是不行的,于是筹备花工夫探索下单测原理,并在此记录。当在 IDEA 中 Run 单元测试时产生了什么?

首先,来看一下当咱们间接通过 IDEA 运行单例时,IDEA 帮忙做了哪些事件:将工程源码和测试源码进行编译,输入到了 target 目录通过 java 命令运行 com.intellij.rt.junit.JUnitStarter,参数中指定了 junit 的版本以及单测用例名称 java com.intellij.rt.junit.JUnitStarter -ideVersion5 -junit4 fung.MyTest,test 这里着重追下 JUnitStarter 的代码,该类在 IDEA 提供的 junit-rt.jar 插件包中,具体目录:/Applications/IntelliJ IDEA.app/Contents/plugins/junit/lib/junit-rt.jar。能够将这个包引入到咱们本人的工程项目中,不便浏览源码:

JUnitStarter 的 main 函数 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 框架,同时依据入参填充 listeners
String agentName = processParameters(argList, listeners, name);

// 启动测试
int exitCode = prepareStreamsAndStart(array, agentName, listeners, name[0]);
… 接下来看下 prepareStreamsAndStart 办法运行的时序图,这里以 JUnit4 为例:

当 IDEA 确认好要启动的框架版本后,会通过类的全限定名称反射创立 IdeaTestRunner<?> 的实例。这里以 JUnit4 为例,IDEA 会实例化 com.intellij.junit4.JUnit4IdeaTestRunner 类对象并调用其 startRunnerWithArgs 办法,在该办法中会通过 buildRequest 办法构建 org.junit.runner.Request,通过 getDescription 办法获取 org.junit.runner.Description,最初创立 org.junit.runner.JUnitCore 实例并调用其 run 办法。简而言之就是,IDEA 最终会借助 Junit4 框架的能力启动并运行单测用例,所以接下来有必要对 Junit4 框架的源码做些深刻的探索。Junit4 源码探索 Junit 是一个由 Java 语言编写的单元测试框架,已在业界被宽泛使用,其作者是赫赫有名的 Kent Beck 和 Erich Gamma,前者是《重构:改善既有代码的设计》和《测试驱动开发》的作者,后者则是《设计模式》的作者,Eclipse 之父。Junit4 公布于 2006 年,尽管是老古董了,但其中所蕴含的设计理念和思维却并不过时,有必要认真探索一番。首先咱们还是从一个简略的单测用例开始: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));
}
@Test
public void test1() {System.out.println("test1");
}
@Test
public void test2() {System.out.println("test2");
}
@Test
public void test3() {System.out.println("test3");
}

}这里咱们不再通过 IDEA 的插件启动单元测试,而是间接通过 main 函数,外围代码如下:public static void main(String[] args) {
// 1. 创立 JUnitCore 的实例
JUnitCore runner = new JUnitCore();
// 2. 通过单测类的 Class 对象构建 Request
Request request = Request.aClass(MyTest.class);
// 3. 运行单元测试
Result result = runner.run(request.getRunner());
// 4. 打印后果
System.out.println(JSON.toJSONString(result));
} 着重看下 runner.run(request.getRunner()),先看 run 函的代码:

能够看到最终运行哪种类型的测试流程取决于传入的 runner 实例,即不同的 Runner 决定了不同的运行流程,通过实现类的名字能够大略猜一猜,JUnit4ClassRunner 应该是 JUnit4 根本的测试流程,MockitoJUnitRunner 应该是引入了 Mockito 的能力,SpringJUnit4ClassRunner 应该和 Spring 有些分割,可能会启动 Spring 容器。当初,咱们回过头来看看 runner.run(request.getRunner())中 request.getRunner()的代码: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;
}能够看到 Runner 是基于传入的测试类(testClass)的信息抉择的,这里的规定如下:如果解析失败了,则返回 ErrorReportingRunner 如果测试类上有 @Ignore 注解,则返回 IgnoredClassRunner 如果测试类上有 @RunWith 注解,则应用 @RunWith 的值实例化一个 Runner 返回如果 canUseSuiteMethod=true,则返回 SuiteMethod,其继承自 JUnit38ClassRunner,是比拟晚期的 JUnit 版本了如果 JUnit 版本在 4 之前,则返回 JUnit38ClassRunner 如果下面都不满足,则返回 BlockJUnit4ClassRunner,其示意的是一个规范的 JUnit4 测试模型咱们先前举的那个简略的例子返回的就是 BlockJUnit4ClassRunner,那么就以 BlockJUnit4ClassRunner 为例,看下它的 run 办法是怎么执行的吧。首先会先走到其父类 ParentRunner 中的 run 办法 @Override
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);

}
}这里有必要开展说下 Statement,官网的解释是:Represents one or more actions to be taken at runtime in the course of running a JUnit test suite.Statement 能够简略了解为对可执行办法的封装和形象,如 RunBefores 就是一个 Statement,它封装了所有标记了 @BeforeClass 注解的办法,在运行单例类的用例之前会执行这些办法,运行完后 RunBefores 还会通过 next.evaluate()运行后续的 Statement。这里列举一下常见的 Statement:RunBefores,会先运行 befores 里封装的办法(个别是标记了 @BeforeClass 或 @Before),再运行 next.evaluate()RunAfters,会先运行 next.evaluate(),再运行 afters 里封装的办法(个别是标记了 @AfterClass 或 @After)InvokeMethod,间接运行 testMethod 中封装的办法由此可见,整个单测的运行过程,实际上就是一系列 Statement 的运行过程,以之前的 MyTest 为例,它的 Statement 的执行过程大抵能够详情如下:

还剩一个最初问题,理论被测试方法是如何被运行的呢?答案是反射调用。外围代码如下:@Override
public void evaluate() throws Throwable {
testMethod.invokeExplosively(target);
}
public Object invokeExplosively(final Object target, final Object… params)
throws Throwable {
return new ReflectiveCallable() {

@Override
protected Object runReflectiveCall() throws Throwable {return method.invoke(target, params);
}

}.run();
}至此一个规范 Junit4 的单测用例的执行过程就剖析完了,那么像 Spring 这种须要起容器的单测又是如何运行的呢?接下来就来探索一下。Spring 单测的探索咱们还是以一个简略的例子开始吧 @RunWith(SpringRunner.class)
@ContextConfiguration(locations = { “/spring/spring-mybeans.xml”})
public class SpringRunnerTest {

@Autowired
private MyTestBean myTestBean;
@Test
public void test() {myTestBean.test();
}

}这里先粗滤的概括下运行单测时产生了什么。首先,@RunWith 注解了该测试类,所以 Junit 框架会先用 SpringRunnerTest.class 作为参数创立 SpringRunner 的实例,而后调用 SpringRunner 的 run 办法运行测试,该办法中会启动 Spring 容器,加载 @ContextConfiguration 注解指定的 Bean 配置文件,同时也会解决 @Autowired 注解为 SpringRunnerTest 的实例注入 myTestBean,最初运行 test()测试用例。简言之就是先通过 SpringRunner 启动 Spring 容器,而后运行测试方法。接下来探索一下 SpringRunner 启动 Spring 容器的过程。public final class SpringRunner extends SpringJUnit4ClassRunner {
public SpringRunner(Class<?> clazz) throws InitializationError {

super(clazz);

}
}
public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner {

}SpringRunner 和 SpringJUnit4ClassRunner 理论是等价的,能够认为 SpringRunner 是 SpringJUnit4ClassRunner 的一个别名,这里着重看下 SpringJUnit4ClassRunner 类的实现。SpringJUnit4ClassRunner 继承了 BlockJUnit4ClassRunner,后面着重剖析过 BlockJUnit4ClassRunner,它运行的是一个规范的 JUnit4 测试模型,SpringJUnit4ClassRunner 则是在此基础上做了一些扩大,扩大的内容次要包含:扩大了构造函数,多创立了一个 TestContextManager 实例。扩大了 createTest()办法,会额定调用 TestContextManager 的 prepareTestInstance 办法。扩大了 beforeClass,在执行 @BeforeClass 注解的办法前,会先调用 TestContextManager 的 beforeTestClass 办法。扩大了 before,在执行 @Before 注解的办法前,会先调用 TestContextManager 的 beforeTestMethod 办法。扩大了 afterClass,在执行 @AfterClass 注解的办法之后,会再调用 TestContextManager 的 afterTestClass 办法。扩大了 after,在执行 @After 注解的办法之后,会再调用 TestContextManager 的 after 办法。TestContextManager 是 Spring 测试框架的外围类,官网的解释是:TestContextManager is the main entry point into the Spring TestContext Framework. Specifically, a TestContextManager is responsible for managing a single TestContext.TestContextManager 治理着 TestContext,而 TestContext 则是对 ApplicationContext 的一个再封装,能够把 TestContext 了解为减少了测试相干性能的 Spring 容器。TestContextManager 同时也治理着 TestExecutionListeners,这里应用观察者模式提供了对测试运行过程中的要害节点(如 beforeClass, afterClass 等)的监听能力。所以通过钻研 TestContextManager,TestContext 和 TestExecutionListeners 的相干实现类的代码,就不难发现测试时 Spring 容器的启动机密了。要害代码如下:public class DefaultTestContext implements TestContext {

public ApplicationContext getApplicationContext() {

ApplicationContext context = this.cacheAwareContextLoaderDelegate.loadContext(this.mergedContextConfiguration);
if (context instanceof ConfigurableApplicationContext) {@SuppressWarnings("resource")
  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;

}

}在 DefaultTestContext 的 getApplicationContext 办法中,调用了 cacheAwareContextLoaderDelegate 的 loadContext,最终辗转调到 Context 的 refresh 办法,从而构筑起 Spring 容器上下文。时序图如下:

那么 getApplicationContext 办法又是在哪里被调用的呢?后面介绍过,TestContextManager 扩大了 createTest()办法,会额定调用其 prepareTestInstance 办法。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);
}

}
}prepareTestInstance 办法中会调用所有 TestExecutionListener 的 prepareTestInstance 办法,其中有一个叫做 DependencyInjectionTestExecutionListener 的监听器会调到 TestContext 的 getApplicationContext 办法。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);
}还剩最初一个问题,DependencyInjectionTestExecutionListener 是如何被增加的呢?答案是 spring.factories

至此 Spring 单测的启动过程就探索明确了,接下来看下 SpringBoot 的。SpringBoot 单测的探索一个简略的 SpringBoot 单测例子 @RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class MySpringBootTest {

@Autowired
private MyTestBean myTestBean;
@Test
public void test() {myTestBean.test();
}

}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(SpringBootTestContextBootstrapper.class)
public @interface SpringBootTest {

}粗滤阐明一下,这里还是通过 SpringRunner 的 run 办法启动测试,其中会启动 Spring 容器,而 @SpringBootTest 则提供了启动类,同时通过 @BootstrapWith 提供的 SpringBootTestContextBootstrapper 类丰盛了 TestContext 的能力,使得其反对了 SpringBoot 的一些个性。这里着重探索下 @BootstrapWith 注解以及 SpringBootTestContextBootstrapper。后面在介绍 TestContextManager 时,并没有讲到其构造函数以及 TestContext 的实例化过程,这里将其补上 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());

}

}构建 DefaultTestContext 须要传 3 个参数:testClass,被测试的类元数据 MergedContextConfiguration,封装了申明在测试类上的与测试容器相干的注解,如 @ContextConfiguration, @ActiveProfiles, @TestPropertySourceCacheAwareContextLoaderDelegate,用来 loading 或 closing 容器那么当咱们须要扩大 TestContext 的性能,或者不想用 DefaultTestContext 时,应该怎么办呢?最简略的形式天然是新写一个类实现 TestContextBootstrapper 接口,并覆写 buildTestContext()办法,那么如何通知测试框架要应用新的实现类呢?@BootstrapWith 就派上用场了。这里来看下 BootstrapUtils.resolveTestContextBootstrapper 的代码 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,从而提供定制的 TestContextSpringBootTestContextBootstrapper 就是 TestContextBootstrapper 的实现类,它通过间接继承 AbstractTestContextBootstrapper 类扩大了创立 TestContext 的能力,这些扩大次要包含:将 ContextLoader 替换为了 SpringBootContextLoader 减少了 DefaultTestExecutionListenersPostProcessor 对 TestExecutionListener 进行加强解决减少了对 webApplicationType 的解决接下来看下 SpringBootContextLoader 的相干代码 public class SpringBootContextLoader extends AbstractContextLoader {
@Override
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();
// 设置 mainApplicationClass
application.setMainApplicationClass(config.getTestClass());
// 设置 primarySources
application.addPrimarySources(Arrays.asList(configClasses));
// 增加 configLocations
application.getSources().addAll(Arrays.asList(configLocations));
// 获取 environment
ConfigurableEnvironment 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);
// 获取并设置 initializers
List<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();

}
}能够看到这里构建了 SpringApplication,设置了 mainApplicationClass,设置了 primarySources,设置了 initializers,最终通过 application.run()启动了 SpringBoot 利用。至此 SpringBoot 单测的启动过程也探索明确了,接下来看下 Maven 插件是如何运行单测的。Maven 插件如何运行单测咱们晓得 maven 是通过一系列的插件帮忙咱们实现我的项目开发过程中的构建、测试、打包、部署等动作的,当在 Console 中运行 maven clean test 命令时,maven 会顺次运行以下 goal: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,运行单测咱们扒下 maven-surefire-plugin 插件的代码看一下。首先引入下 maven-surefire-plugin 和 surefire-junit4 包,不便咱们查看代码:<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> 外围代码在 org.apache.maven.plugin.surefire.AbstractSurefireMojo#execute 中,这里就不贴代码了,有趣味的能够本人看下。总之这里会用 JUnit4ProviderInfo 中的信息通过反射实例化 JUnit4Provider 对象,而后调用其 invoke 办法,在改办法中会最终实例化 Runner 并调用其 run 办法。外围代码如下: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 框架的代码。原文链接:https://click.aliyun.com/m/10… 本文为阿里云原创内容,未经容许不得转载。

正文完
 0