最近在读 Spring in Action 4th,书中包含不少单元测试的样例,基础格式:(示例语言 Kotlin )

1
2
3
4
5
6
7
8
9
10
package springinaction

import ...

@RunWith(SpringJUnit4ClassRunner::class)
@ContextConfiguration(classes = [JavaConfig::class])
class ...Test {
@Test
fun testConfig()
}

在 Spring 的项目开发中,单元测试是常见的临时测试代码模块的方式,以上格式主要是包含两个类级注解 @RunWith@ContextConfiguration,其中 @RunWith 如其名,意思是让当前的单元测试以怎样的环境运行,这里使用Spring的 SpringJUnit4ClassRunner,用于在测试时自动创建Spring的应用上下文 (Context 我更偏向于翻译成运行环境),该 Context 具体做怎样的配置在于 @ContextConfiguration 注解中所传入的基于Java的配置类,这个类应至少标上 @Configuration 注解,例如:

1
2
3
4
5
6
7
8
9
10
11
package springinaction

import ...

@Configuration
@ComponentScan
class ...Config {

@Bean
fun createJaveBean() : JaveBean = JavaBean()
}

其中,@ComponentScan 注解不是必需的,其作用是查找与本类在同一 package 下且通过注解方式注入 Spring 容器的 JavaBean (未提供参数,则默认在本类所在的package中扫描) 并将其创建添加至容器。

为节省配置Spring依赖的时间,我使用 Spring BootGradle 构建项目以做示例实现,但在项目构建结束后无法找到 @RunWith 注解,虽说在Spring Boot中可使用 @SpringBootTest 注解作为代替,但是我仍旧想解决 @RunWith 无法找到的问题,Google一下并未找到解决方案,或说大多解决方案是添加 spring-boot-test-starter 相关依赖,但这不是问题的原因。

于是,查找官方文档,在JUnit5的 User Guide 中,关于 Migrating from JUnit4 章节中找到如下描述:

3.2. Migration Tips

The following are topics that you should be aware of when migrating existing JUnit 4 tests to JUnit Jupiter.

  • Annotations reside in the org.junit.jupiter.api package.
  • Assertions reside in org.junit.jupiter.api.Assertions.
    • Note that you may continue to use assertion methods from org.junit.Assert or any other assertion library such as AssertJ, Hamcrest, Truth, etc.
  • Assumptions reside in org.junit.jupiter.api.Assumptions.
    • Note that JUnit Jupiter 5.4 and later versions support methods from JUnit 4’s org.junit.Assume class for assumptions. Specifically, JUnit Jupiter supports JUnit 4’s AssumptionViolatedException to signal that a test should be aborted instead of marked as a failure.
  • @Before and @After no longer exist; use @BeforeEach and @AfterEach instead.
  • @BeforeClass and @AfterClass no longer exist; use @BeforeAll and @AfterAll instead.
  • @Ignore no longer exists: use @Disabled or one of the other built-in execution conditions instead
  • @Category no longer exists; use @Tag instead.
  • @RunWith no longer exists; superseded by @ExtendWith.
  • @Rule and @ClassRule no longer exist; superseded by @ExtendWith and @RegisterExtension

之前的注解类现在位于 org.junit.jupiter.api包中,经查看,在新的 JUnit5 中此前 @RunWith 注解所在的 org.junit.runner 包已经不存在,且文档中提到 @RunWith no longer exists; superseded by @ExtendWith. ,即 @RunWith 将由 @ExtendWith 替代。至此,这个问题解决了,只要替换下注解节即可。

但实际上 @ExtendWith@RunWith 有所区别,@ExtendWith 无法接收 SpringJUnit4ClassRunner::class 作为参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Repeatable(Extensions.class)
@API(status = STABLE, since = "5.0")
public @interface ExtendWith {

/**
* An array of one or more {@link Extension} classes to register.
*/
Class<? extends Extension>[] value();

}

从源码可以看到它接收任意一个实现了 Extension 接口的Java类 数组 ,那么为了得到与 @RunWith(SpringJUnit4ClassRunner::class) 等价的效果,需要在 @ExtendWith 注解中传入 SpringExtension::class,即 @ExtendWith(SpringExtension::class) (传入单个值可不以数组形式)。为了更加理解 Extension 的作用,我查看源码,在 SpringExtension 的类注释中看到以下描述:

1
2
3
4
5
6
7
8
/**
* {@code SpringExtension} integrates the <em>Spring TestContext Framework</em>
* into JUnit 5's <em>Jupiter</em> programming model.
*
* <p>To use this extension, simply annotate a JUnit Jupiter based test class with
* {@code @ExtendWith(SpringExtension.class)}, {@code @SpringJUnitConfig}, or
* {@code @SpringJUnitWebConfig}.
*

即,SpringExtension将Spring的测试上下文框架集成到JUnit 5的Jupiter编程模型中。

对于 JUnit Jupiter,官方文档是这样解释的:

JUnit Jupiter is the combination of the new programming model and extension model for writing tests and extensions in JUnit 5. The Jupiter sub-project provides a TestEngine for running Jupiter based tests on the platform.

它是新的 编程模型扩展模型 的结合,以我自己的理解方式,新的编程模型,即在新的 JUnit5 中较 Junit4 新的改进,很表面的变化可以说包含了一些新的用于测试的注解与注解更名,而大的变化在于与扩展模型的结合,在 JUnit4.0 中,对一个测试类而言,只有一种扩展JUnit的方式,即创建一个新的Runner,然后在测试类上添加 @RunWith(MyRunner::class)注解,也就是形如前文描述的那样:

1
2
3
4
5
6
@RunWith(SpringJUnit4ClassRunner::class)
@ContextConfiguration(classes = [JavaConfig::class])
class ...Test {
@Test
fun testConfig()
}

因为仅有一种扩展方式,因此

待补充。