读书笔记 -- Junit 实战(3rd)Ch01 起步、Ch02 核心
Ch01 Junit 起步
1. 框架:
是一个应用程序的半成品。提供一个可复用的公共结构,可以在多个应用程序间共享。
2. Junit 特性:
- 针对每个单元测试,分离测试类实例和类加载器实例,以免产生副作用;
- 使用 Junit 注解提供资源初始化和清理方法:@BeforeEach, @BeforeAll, @AfterEach, @AfterAll (Junit 5 开始),以及 @Before, @BeforeClass, @After, @AfterClass (Junit 4及以下版本);
- 提供多种断言方法,使检查测试结果变得更容易;
- 提供与 Maven 和 Gradle 等流行工具的集成,以及与 Eclipse、NetBeans、IntelliJ 等 IDE 的集成;
*** 测试类:最好以 "Test" 结尾,IDE可以直接运行,
命令行 "mvn clean test" 或者 "mvn clean install" 将运行:类后缀为 "Test" + 方法有 @Test 注解的方法
相对来说,"mvn clean install" 比 "mvn clean test" 多生成了 jar 包(通过两者的命令导出到 log 进行比较)。
3. surefire 插件
1)没有 surefire 插件的运行结果
2)有 surefire 插件的结果
Ch02 Junit 核心
2.1 核心注解
几个重要概念:
1)测试类:可以是顶级类、静态成员类或使用 @Nested 注解的包含一个多或多个测试方法的内部类。
测试类不能是抽象的,必须有单一的构造方法。构造方法必须不带参数,或所带参数能通过依赖注入在运行时动态解析。
测试类允许是私有的(Junit5上,Junit 4 要求是公有类)。
2)测试方法:用 @Test、@RepeatedTest、@ParameterizedTest、@TestFactory、@TestTemplate 等注解的实例方法。测试方法不能是抽象的,返回值类型必须是 void。
3)生命周期方法:用 @BeforeAll、@AfterAll、@BeforeEach、@AfterEach 等注解的方法。
@BeforeAll # 所有测试运行前运行一次,除非测试类用 @TestInstance(Lifecycle.PER_CLASS)注解 static void setUpClass() {} @BeforeEach # 每次测试运行前运行 void setUp() {} @Test # 运行测试用例 void testRegularWork() {} @Test # 运行测试用例 void testAdditionalWork() {} @AfterEach # 每次测试运行后运行 void tearDown() {} @AfterAll # 所有测试运行后只运行一次,除非测试类用 @TestInstance(Lifecycle.PER_CLASS)注解 static void tearDownClass() {}
@Test 注解的方法的注意事项:
- JUnit 为每个@Test 注解的方法创建测试类的一个实例;
- 测试结果与运行顺序无关(所以要求测试方法不能依赖);
- 不能跨测试方法重用实例变量值;
其他注解:
注解 | 注解类型 | 目的 |
@DisplayName |
类、方法 | declare a custom display name for the annotated test class or test method. |
@Disabled | 类、方法 | signal that the annotated test class or test method is currently disabled and should not be executed. |
@Nested | 类 |
signal that the annotated class is a nested, non-static test class (i.e., an inner class) that can share setup and state with an instance of its enclosing class 主测试类和嵌套测试类紧密耦合。在嵌套测试类上注解 @Nested |
在过滤测试用例的两个方式:
# 1. @tag 标签 在 类或方法上,然后通过 maven 命令 @Tag("individual") public class CustomerTest { ... } > cd C:\Users\xxx\IdeaProjects\trunk\JunitInAction\ch02 // pom.xml 所在目录 > mvn clean test -Dgroups=individual # 2. 通过插件 surefire <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.0.0</version> <configuration>--> <groups>individual</groups>--> <excludedGroups>repository</excludedGroups>--> </configuration>--> </plugin> </plugins> </build> > cd C:\Users\xxx\IdeaProjects\trunk\JunitInAction\ch02 // pom.xml 所在目录 > mvn clean test 其他详细可参考: https://zhuanlan.zhihu.com/p/353017791?utm_id=0
2.4 断言
assertAll:在 JUnit5 中,将检查所有的断言,即使一些失败。在 JUint4 中不是,其中一个断言失败,后面的将不会执行。
assertTimeout:用于等待可运行对象完成:后面的可运行对象会执行完成,然后再判断超时多少
assertTimeoutPreemptively:超时后停止运行可运行对象。超过了设置的 timeout,则后面的可运行对象将不会执行。
2.5 假设
JUnit5 包含一组假设方法,适合与 Java8 的 Lambda 表达式一起使用。
@Test void testNoJobToRun() { assumingThat( () -> environment.getJavaVersion().equals(EXPECTED_JAVA_VERSION), () -> assertFalse(systemUnderTest.hasJobToRun()) ); }
2.6 JUnit5 的依赖注入
// 如果构造/普通 方法的参数是 TestInfo,那么 TestInfoParameterResolver 将提供该类型的一个实例。 // TestInfo 是一个类,其对象用于将当前运行的测试或容器的信息注入 @Test、@BeforeEach、@AfterEach、@BeforeAll、@AfterAll 等标注的方法中。 @Test void testGetNameOfTheMethod(TestInfo testInfo) { assertEquals("testGetNameOfTheMethod(TestInfo)", testInfo.getDisplayName()); } // 如果构造/普通 方法的参数是 TestReporter,那么 TestReporterParameterResolver 将提供该类型的一个实例。 // TestReporter 的参数可以注入 @Test、@BeforeEach、@AfterEach 等标注的方法中。 @Test void testReportKeyValuePair(TestReporter testReporter) { testReporter.publishEntry("Key", "Value"); }
2.7 重复测试
public class RepeatedTestsTest { // 在Java开发中,有时我们需要在多个类之间共享数据,而静态变量是一种常用的方式。 // 声明为 static,该变量可以在其他类中访问,并且在整个应用程序的生命周期内只会有一个实例。 private static List<Integer> integerList = new ArrayList<>(); @RepeatedTest(value = 5, name = "the list contains {currentRepetition} element(s), the set contains 1 element") // RepetitionInfo:可以记录当前的循环信息 // TestReporter:将循环信息存入 key-value void testAddingToCollections(TestReporter testReporter, RepetitionInfo repetitionInfo) { integerList.add(repetitionInfo.getCurrentRepetition()); // 打印对象地址,反映其是否每次都是创建一个新的对象 System.out.println(System.identityHashCode(testReporter)); testReporter.publishEntry("Repetition number", String.valueOf(repetitionInfo.getCurrentRepetition())); assertEquals(repetitionInfo.getCurrentRepetition(), integerList.size()); } }
2.8 参数化测试
// 1. @ParameterizedTest + @ValueSource,实现不同参数的参数化运行 @ParameterizedTest @ValueSource(strings = {"Check three parameters", "Junit in Action"}) void testWordsInSentence(String sentence) { assertEquals(3, wordCounter.countWords(sentence)); } // 2. @ParameterizedTest + @EnumSource,实现不同参数的参数化运行 @ParameterizedTest @EnumSource(Sentences.class) // 将 整个 Sentences.class 指定为枚举源,因此执行 3 次 void testWordsInSentence(Sentences sentences) { assertEquals(3, wordCounter.countWords(sentences.value())); } // 3. @ParameterizedTest + @CsvSource @ParameterizedTest @CsvSource({"2, Unit testing", "3, Junit in Action", "4, Write solid Java code"}) // csv 的每一行,将第一个值赋给 expected,第二个值赋给 sentence void testWordsInSentence(int expected, String sentence) { assertEquals(expected, wordCounter.countWords(sentence)); } // 4. @ParameterizedTest + @CsvFileSource @ParameterizedTest @CsvFileSource(resources = "/word_counter.csv") void testWordsInSentence(int expected, String sentence) { assertEquals(expected, wordCounter.countWords(sentence)); }
2.9 动态测试
JUnit5 引入了新的动态编程模型,可以在运行时生成测试。必须用 @TestFactory 注解,其注解的方法不是常规测试,而是一个生成测试的工厂。