每日一听
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
用于使用参数化功能
创建参数化测试需要遵循五个步骤:
- 使用@RunWith(Parameterized.class)注释测试类。
- 创建一个使用@Parameters注释的公共静态方法,该方法返回一个对象集合作为测试数据集。
- 创建一个公共构造函数,它接受相当于一行“测试数据”的内容。
- 为测试数据的每个“列”创建一个实例变量。
- 使用实例变量作为测试数据的来源创建测试用例。
前置操作:
// 使用 @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];
预期:野原美冴
实际:野原新之助
本笔记参考于网上各类文章整理
如有侵权,请联系作者 马铃薯头 删除