读书笔记 -- Junit 实战(3rd)Ch04 Junit4 向 Junit5 迁移
4.1 Junit4 向 Junit5 跨越
1. 基础包:
Junit Jupiter 的类和注解都定义在新的 org.junit.jupiter 基础包中;
Junit 4 的类和注解都定义在新的 org.junit 基础包中;
2. Junit4 向 Junit5 迁移的步骤
- 替换所需的依赖项;
- 替换注解,并引入新的注解;
- 替换测试类和方法;
- 用Junit 5 扩展模型替换 Junit4 规则和运行器;
4.2 所需的依赖项
<!-- Junit4 的依赖项 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency>
<!-- Junit5 处理 遗留的Junit4 测试的依赖项 --> <dependency> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> <version>5.6.0</version> <scope>test</scope> </dependency>
<!-- Junit 5 的两个基础依赖:api、engine -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
4.3 注解、类和方法
注解 | 断言 | 假设 | |||||
Junit4 | Junit5 | Junit4 | Junit5 | Junit4 | Junit5 | ||
@BeforeClass、@AfterClass | @BeforeAll、@AfterAll | Assert 类 | Assertions 类 | Assume 类 | Assumptions 类 | ||
@Before、@After | @BeforeEach、@AfterEach | 第一个参数是可选断言消息 | 最后一个参数是可选断言消息 | assumeNotNull 和 AssumeNoException | 移除了 assumeNotNull 和 AssumeNoException | ||
@Ignore | @Disable | assertThat() |
assertThat() 被移除, 对应的新方法是 assertAll 和 AssertThrows |
||||
@Category | @Tag |
1. 注解:
Junit4:
- 1. @BeforeClass 和 @AfterClass 注解的方法必须是 public static
- 2. @Before 和 @After 注解的方法必须是 public
- 3. @Test 注解的方法必须是public 的
JUnit5:
- @BeforeAll 和 @AfterAll 注解的方法是 static。或者,用 @TestInstance(TestInstance.Lifecycle.PER_CLASS) 标注整个测试类
2. 分类和标记:
Junit4 的分类:
// Step1:方法上 或类上 标注 @Category // 方法上标注 @Category(IndividualTests.class) @Test public void testCustomer() {} // 类上标注 @Category({IndividualTests.class, RepositoryTests.class}) public class JUnit4CustomersRepositoryTest {} // Step2:给每个标注的分类建立一个接口 public interface IndividualTests {} public interface RepositoryTests {} // Step3:组建一个 Suite, @RunWith(Categories.class) @Categories.IncludeCategory(IndividualTests.class) @Suite.SuiteClasses({JUnit4CustomerTest.class, JUnit4CustomersRepositoryTest.class}) public class JUnit4IndividualTestsSuite { } /** * Junit4 最后形成测试组件: * 1. @RunWith(Categories.class),告知 JUnit:JUnit4IndividualTestsSuite 需要使用这个特定的运行器运行测试 * 2. @Categories.IncludeCategory(),注解标注的测试类别 * 3. @Suite.SuiteClasses(),在类中查找这些被标注的测试 */
Junit5 的标记:
// Step1: @Tag() 标注在类或方法上 @Tag("individual") public class JUnit5CustomerTest {} // Step2: > cd C:\Users\xxx\IdeaProjects\trunk\Junit5InAction\ch04 // pom.xml 所在目录 > mvn clean test -Dgroups=individual 或者,pom.xml 添加 configuration 标识 <configuration>--> <groups>individual</groups>--> <excludedGroups>repository</excludedGroups>--> </configuration>--> ,然后 > cd C:\Users\xxx\IdeaProjects\trunk\Junit5InAction\ch04 // pom.xml 所在目录 > mvn clean test
3. 规则与扩展
// Junit4:需要 @Rule 注解在 ExpectedException,然后在方法判断时,通过该字段保留异常类型和消息 public class JUnit4RuleExceptionTester { // @Rule 必须应用于非静态公有字段或非静态公有方法 // 要向运行的测试添加行为,必须在 TestRule 字段上使用 @Rule 注解 @Rule // ExpectedException.none() 工厂方法简单地创建了一个未配置的 ExpectedException public ExpectedException expectedException = ExpectedException.none(); private Calculator calculator = new Calculator(); @Test public void expectIllegalArgumentException() { // ExpectedException 被配置为在调用 sqrt() 抛出异常前,保持异常类型和消息 // !! 所以,如果将 sqrt() 方法放在 ExpectedException 则会出现异常报错 expectedException.expect(IllegalArgumentException.class); expectedException.expectMessage("Cannot extract the square root of a negative value"); calculator.sqrt(-1); } }
// Juit5: 直接通过 Throwable 获取异常,进行判断 public class JUnit5ExceptionTester { private Calculator calculator = new Calculator(); @Test public void expectIllegalArgumentException() { Throwable throwable = assertThrows(IllegalArgumentException.class, () -> calculator.sqrt(-1)); assertEquals("Cannot extract the square root of a negative value", throwable.getMessage()); } }
4. 自定义规则
// Junit4: public class JUnit4CustomRuleTester { @Rule // 类字段公有 public CustomRule myRule = new CustomRule(); /**Output: * CustomStatement myCustomRuleTest has started * Call of a test method * CustomStatement myCustomRuleTest has finished * * 解析:通过 @Rule 注释 CustomRule,其将加载该 Rule * 通过 CustomRule -> CustomStatement#evaluate 可以看出,先有一个 printout,然后 base.evaluate(),再由一个 printout */ @Test public void myCustomRuleTest() { System.out.println("Call of a test method"); } } public class CustomRule implements TestRule { private Statement base; private Description description; @Override public Statement apply(Statement base, Description description) { this.base = base; this.description = description; // 需要将这两个参数传递出去,所以 CustomStatement 需要创建一个完全参数的构造器 return new CustomStatement(base, description); } } @AllArgsConstructor public class CustomStatement extends Statement { private Statement base; private Description description; @Override public void evaluate() throws Throwable { System.out.println(this.getClass().getSimpleName() + " " + description.getMethodName() + " has started"); try { base.evaluate(); } finally { System.out.println(this.getClass().getSimpleName() + " " + description.getMethodName() + " has finished"); } } }
// Junit5: // 自定义扩展,覆盖 BeforeEach 和 AfterEach 方法 public class CustomExtension implements AfterEachCallback, BeforeEachCallback { @Override public void beforeEach(ExtensionContext extensionContext) throws Exception { System.out.println(this.getClass().getSimpleName() + " " + extensionContext.getDisplayName() + " has started"); } @Override public void afterEach(ExtensionContext extensionContext) throws Exception { System.out.println(this.getClass().getSimpleName() + " " + extensionContext.getDisplayName() + " has finished"); } } @ExtendWith(CustomExtension.class) // 使用 CustomExtension 类扩展 JUnit5CustomExtensionTester 类 public class JUnit5CustomExtensionTester { @Test public void myCustomRuleTest() { System.out.println("Call of a test method"); } }
对于已经创建的扩展,Junit4 向 Junit5 的迁移方式是:
- 要迁移 Mockito 测试,需在测试类中将 @RunWith(MockitoJUnitRunner.class) 替换成 @ExtendWith(MockitoExtension.class);
- 要迁移 Spring 测试,需在测试类中将 @RunWith(SpringJUnit4Runner.class) 替换成 @ExtendWith(SpringExtension.class);