每日一听


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 依赖:

</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 依赖:

dependencies {
    testImplementation("junit:junit:4.13.2")
}

2.2.2. 注解

2.2.2.1. @RunWith

放在测试类名之前,用来确定这个类怎么运行的。
也可以不标注,会使用默认运行器。

2.2.2.2. @Test

测试方法
@Test
public void test() {
    System.out.println("Test......");
}

运行结果:

Test......

参数 timeout:
配置超时时间,单位为毫秒。
测试用例在超时期限之前还没有完成时,将会抛出 TestTimedOutException 异常。

@Test(timeout = 1000)
public void test() {
    while (true) ;
}

运行结果:

org.junit.runners.model.TestTimedOutException: test timed out after 1000 milliseconds

参数 expected:
检查方法是否抛出特定的异常。
测试用例未抛出指定异常,将会抛出 AssertionError 异常。

@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

在测试方法运行之前/之后运行(每个测试方法之前都会执行一次)
@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

全局只会执行一次,而且是第一个/最后一个运行
@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

忽略不能运行的测试用例

@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. 使用实例变量作为测试数据的来源创建测试用例。

前置操作:

// 使用 @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:

  • 构造器注入:
/**
 * 所需参数
 */
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** ):
/**
 * 所需参数
 */
@Parameter
public Long parameter;

/**
 * 预期结果
 */
@Parameter(1)
public String expected;

构造参数:

/**
 * 创建一个使用 @Parameters注释的公共静态方法
 * 该方法返回一个对象集合作为测试数据集
 */
@Parameters
public static Collection<Object[]> data() {
    return Arrays.asList(new Object[][]{
            {1L, "野原新之助"},
            {2L, "野原美冴"}
    });
}

运行测试:

/**
 * 测试
 */
@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

用于套件测试
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 具有实际值的对象数组或数组数组(多维数组)
示例:

String[] array1 = new String[]{"野原美冴", "野原广志"};
String[] array2 = new String[]{"野原新之助", "野原广志"};
assertArrayEquals("my error", array1, array2);

运行结果:

my error: arrays first differed at element [0];
预期:野原美冴
实际:野原新之助

本笔记参考于网上各类文章整理
如有侵权,请联系作者 马铃薯头 删除
最后修改:2023 年 03 月 02 日
温柔的好天气总是和我一样,帅的鸭皮!