Loading... ## 每日一听 <div class='handsome_aplayer player-content' data-preload="auto" data-autoplay="false" data-listMaxHeight="340px" data-order="list"> <div class="handsomePlayer-tip-loading"><span></span> <span></span> <span></span> <span></span><span></span></div><div class="handsome_aplayer_music" data-id="1368754688" data-server="netease" data-type="song" data-auth="85fe107f4ee2637f4f006a14af33b373"></div> </div> ---------- # 1. 概述 ## 1.1. 单元测试的概念 > 单元测试 (unit testing) ,是指对软件中的最小可测试单元进行检查和验证。 > 在Java中单元测试的最小单元是类。 单元测试是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。 执行单元测试,就是为了证明这 段代码的行为和我们期望是否一致。 ## 1.2. 单元测试的好处 - 编码完成就可以立刻测试,尽早发现问题 - 将测试保存成为了代码,可以随时快速执行 - 可以嵌入持续集成流水线,自动为每次代码修改保驾护航 --- # 2. JUnit ## 2.1. 概述 > JUnit 是一个简单的开源框架,用于编写和运行可重复的测试。 > 它是用于单元测试框架的 xUnit 架构的一个实例。 JUnit 功能包括: - 用于测试预期结果的断言 - 用于共享公共测试数据的测试装置 - 用于运行测试的测试运行器 ### 2.1.1. 项目结构  **src** - `src/main/java` 此文件夹包含 Java 源代码包和类 - `src/main/resources` 此文件夹包含非 Java 资源,例如属性文件和 Spring 配置 **test** - `src/test/java` 此文件夹包含测试源代码包和类 - `src/test/resources` 此文件夹包含非 Java 资源,例如属性文件和 Spring 配置 ## 2.2. JUnit4 ### 2.2.1. 引入依赖 #### 2.2.1.1. 在 Maven 项目中使用 在 `dependencies` 列表添加 JUnit4 依赖: ```xml </dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> </dependencies> ``` #### 2.2.1.2. 在 Gradle 项目中使用 在 `build.gradle` 文件中添加 `JUnit4` 依赖: ```groovy dependencies { testImplementation("junit:junit:4.13.2") } ``` ### 2.2.2. 注解 #### 2.2.2.1. @RunWith 放在测试类名之前,用来确定这个类怎么运行的。 也可以不标注,会使用默认运行器。 #### 2.2.2.2. @Test > 测试方法 ```java @Test public void test() { System.out.println("Test......"); } ``` 运行结果: > Test...... **参数 timeout:** 配置超时时间,单位为毫秒。 测试用例在超时期限之前还没有完成时,将会抛出 `TestTimedOutException` 异常。 ```java @Test(timeout = 1000) public void test() { while (true) ; } ``` 运行结果: > org.junit.runners.model.TestTimedOutException: test timed out after 1000 milliseconds **参数 expected:** 检查方法是否抛出特定的异常。 测试用例未抛出指定异常,将会抛出 `AssertionError` 异常。 ```java @Test(expected = RuntimeException.class) public void test01() { System.out.println("Test01......"); } ``` 运行结果: > Test01...... > java.lang.AssertionError: Expected exception: java.lang.RuntimeException #### 2.2.2.3. @Before和@After > 在测试方法运行之前/之后运行(每个测试方法之前都会执行一次) ```java @Before public void beforeTest() { System.out.println("Before......"); } @Test public void test01() { System.out.println("Test01......"); } @Test public void test02() { System.out.println("Test02......"); } @After public void afterTest() { System.out.println("After......"); } ``` 运行结果: > Before...... > Test01...... > After...... > Before...... > Test02...... > After...... #### 2.2.2.4. @BeforeClass和@AfterClass > 全局只会执行一次,而且是第一个/最后一个运行 ```java @BeforeClass public static void beforeTest() { System.out.println("BeforeClass......"); } @Test public void test01() { System.out.println("Test01......"); } @Test public void test02() { System.out.println("Test02......"); } @AfterClass public static void afterTest() { System.out.println("AfterClass......"); } ``` 运行结果: > BeforeClass...... > Test01...... > Test02...... > AfterClass...... > PS:@BeforeClass @Before @After @AfterClass这些注解标注的方法又称测试的Fixture。 #### 2.2.2.5. @Ignore 忽略不能运行的测试用例 ```java @Test public void test01() { System.out.println("Test01......"); } @Test @Ignore public void test02() { System.out.println("Test02......"); throw new RuntimeException("Test02 Error."); } ``` 运行结果: > Test01...... #### 2.2.2.6. @Parameters > 用于使用参数化功能 创建参数化测试需要遵循五个步骤: 1. 使用@RunWith(Parameterized.class)注释测试类。 2. 创建一个使用@Parameters注释的公共静态方法,该方法返回一个对象集合作为测试数据集。 3. 创建一个公共构造函数,它接受相当于一行“测试数据”的内容。 4. 为测试数据的每个“列”创建一个实例变量。 5. 使用实例变量作为测试数据的来源创建测试用例。 **前置操作:** ```java // 使用 @RunWith(Parameterized.class) 注释测试类 @RunWith(Parameterized.class) public class DemoTest { /** * 用户实体 */ @Data @NoArgsConstructor @AllArgsConstructor static class User { private Long id; private String name; } /** * 用户列表 */ private static List<User> users = new ArrayList<>(); /** * 初始化用户信息 */ @BeforeClass public static void initUser() { User user1 = new User(1L, "野原新之助"); User user2 = new User(2L, "野原广志"); User user3 = new User(3L, "野原美冴"); users.add(user1); users.add(user2); users.add(user3); } /** * 获取用户信息 * * @param id id * @return {@link String} */ private String getNameById(Long id) { return users.stream() .filter(user -> user.getId().equals(id)) .map(User::getName) .findAny() .orElseGet(null); } } ``` **注入step2:** - 构造器注入: ```java /** * 所需参数 */ private final Long parameter; /** * 预期结果 */ private final String expected; /** * 创建一个公共构造函数 * 它接受相当于一行测试数据的内容 * * @param parameter 所需参数 * @param expected 预期结果 */ public DemoTest(Long parameter, String expected) { this.parameter = parameter; this.expected = expected; } ``` - **注解注入(修饰符必须是 **`**public**`** ):** ```java /** * 所需参数 */ @Parameter public Long parameter; /** * 预期结果 */ @Parameter(1) public String expected; ``` 构造参数: ```java /** * 创建一个使用 @Parameters注释的公共静态方法 * 该方法返回一个对象集合作为测试数据集 */ @Parameters public static Collection<Object[]> data() { return Arrays.asList(new Object[][]{ {1L, "野原新之助"}, {2L, "野原美冴"} }); } ``` 运行测试: ```java /** * 测试 */ @Test public void test() { String result = getNameById(parameter); assertEquals(expected, result); } ``` 运行结果: > parameter: 1 > expected: 野原新之助 > parameter: 2 > expected: 野原美冴 > > junit.framework.ComparisonFailure: > 预期:野原美冴 > 实际:野原广志 #### 2.2.1.7. @SuiteClasses > 用于套件测试 ```java public class Demo01Test { @Test public void test() { System.out.println("Demo01Test..."); } } public class Demo02Test { @Test public void test() { System.out.println("Demo02Test..."); } } /** * 如果是需要多个单元测试类整合测试,使用一个 Runner 进行异步测试 * 只需要把相关的 class 放入到 SuiteClasses{} 中即可 * 如 Demo01Test.class 和 Demo02Test.class 都是写好的测试类 * 此类的作用是整合测试也称 打包测试,可以把之前所有的写好的test class类进行集成 */ @RunWith(Suite.class) @SuiteClasses({Demo01Test.class, Demo02Test.class}) public class DemoAllTest { } ``` 运行结果: > Demo01Test... > Demo02Test... ### 2.2.3. 断言 #### 2.2.3.1. 概述 JUnit 为所有原语类型、对象和数组(原语或对象)提供重载断言方法。 参数顺序为预期值后接实际值。或者,第一个参数可以是失败时输出的字符串消息。 有一个稍有不同的断言,`assertThat` 具有可选失败消息的参数、实际值和 `Matcher` 对象。 请注意,与其他 `assert` 方法相比,它的预期值和实际值是相反的(实际值后接预期值)。 #### 2.2.3.2. assertEquals > 断言两个对象数组相等。 > 如果不是,则会引发AssertionError。 > 如果期望和实际为空,则认为它们相等。 **语法:** `void assertArrayEquals(String message, Object[] expecteds, Object[] actuals)` `message` AssertionError 的错误信息(可选) `expecteds` 具有期望值的对象数组或数组数组(多维数组) `actuals` 具有实际值的对象数组或数组数组(多维数组) **示例:** ```java String[] array1 = new String[]{"野原美冴", "野原广志"}; String[] array2 = new String[]{"野原新之助", "野原广志"}; assertArrayEquals("my error", array1, array2); ``` 运行结果: > my error: arrays first differed at element [0]; > 预期:野原美冴 > 实际:野原新之助 --- > 本笔记参考于网上各类文章整理 > 如有侵权,请联系作者 [马铃薯头](mailto:m@xlsea.cn?subject=文章侵权告知) 删除 最后修改:2023 年 03 月 02 日 © 允许规范转载 赞 0