什么是单元测试
- 定义:单元测试是对软件中的最小可测试单元(通常是一个函数或方法)进行的验证性测试,旨在确保其按预期工作。
- 目的:通过测试单元代码来发现并修复错误,提高代码的可靠性和维护性。
“单元”的大小或范围,并没有一个明确的标准,“单元”可以是一个函数、方法、类、功能模块或者子系统。单元测试一般是有开发人员或测试人员来做。单元测试通常和白盒测试联系到一起,如果单从概念上来讲两者是有区别的,不过我们通常所说的“单元测试”和“白盒测试”都认为是和代码有关系的,单元测试和白盒测试就是对开发人员所编写的代码进行测试。
测试工具
为什么需要测试工具?
我们就可以通过运行main()方法来运行测试代码。 不过,使用main()方法测试有很多缺点:
- 只能有一个main()方法,不能把测试代码分离。
- 没有打印出测试结果和期望结果。
- 很难编写一组通用的测试代码。
因此,我们需要一种测试框架,帮助我们编写测试。
常用的单元测试工具有哪些?
- Java:JUnit、TestNG
- Python:UintTest、pyTest
JUnit
介绍
JUnit是一个开源的Java语言的单元测试框架,专门针对Java设计,使用最广泛。JUnit是事实上的单元测试的标准框架,任何Java开发者都应当学习并使用JUnit编写单元测试。
使用JUnit编写单元测试的好处在于,我们可以非常简单地组织测试代码,并随时运行它们,JUnit就会给出成功的测试和失败的测试,还可以生成测试报告,不仅包含测试的成功率,还可以统计测试的代码覆盖率,即被测试的代码本身有多少经过了测试。对于高质量的代码来说,测试覆盖率应该在80%以上。
此外,几乎所有的IDE工具都集成了JUnit,这样我们就可以直接在IDE中编写并运行JUnit测试。JUnit目前最新版本是5。
JUnit 4和JUnit 5的主要区别
- 架构:JUnit 4将所有内容捆绑到单个jar文件中。 Junit 5由3个子项目组成,即JUnit Platform,JUnit Jupiter和JUnit Vintage。
- 注释:
- JDK支持:Junit4需要Java5+版本 ,Junit5需要Java8+版本
- 断言标准:
如何使用Junit
要使用JUnit,首先需要将JUnit库添加到您的项目中。可以通过以下两种方式之一进行安装
- 方法一:手动下载并添加JUnit库
- 访问JUnit的官方网站:https://junit.org/junit5/。
-
- 下载JUnit的JAR文件(通常是junit-platform-console-standalone.jar)。
- 将下载的JAR文件添加到您的项目中的类路径中。
- 方法二:使用构建工具(如Maven或Gradle)
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
一旦您的项目配置好了JUnit,就可以开始编写测试用例了。JUnit使用注解来标识测试方法,以下是一个简单的示例:
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class MyMathTest {
@Test
public void testAdd() {
MyMath math = new MyMath();
int result = math.add(2, 3);
assertEquals(5, result);
}
}
常用的注解
- @Test:用于标识测试方法。JUnit将执行所有带有@Test注解的方法,并报告测试结果。
- @Before 和 @After:用于在测试方法之前和之后执行一些设置或清理工作。这对于准备测试环境和资源的初始化非常有用。
- @BeforeEach 和 @AfterEach:与@Before和@After类似,但它们在每个测试方法之前和之后执行,而不是在测试类级别执行。
- @BeforeAll 和 @AfterAll:@BeforeAll和@AfterAll注解用于在测试类中的所有测试方法之前和之后执行一次。通常用于执行全局初始化和清理工作。取代了JUnit 4的@BeforeClass
- @Disabled:用于禁用测试方法。被标记为@Disabled的测试方法不会被执行。与JUnit 4的@Ignore类似。
其他注解
除了上面介绍的常用JUnit注解之外,JUnit 5还提供了一些其他有用的注解,可以用于测试和测试方法的各种配置和控制。
- @Timeout注解用于指定测试方法的超时时间。如果测试方法执行时间超过指定的超时时间,测试将被标记为失败。
- @RepeatedTest注解用于重复运行相同的测试方法多次。
- @Tag注解用于为测试类或测试方法添加标签。标签可以用于组织和筛选测试,例如只运行特定标签的测试。
- @DisplayName注解用于为测试类或测试方法指定自定义的显示名称,用于更清晰地描述测试的目的。
- @ExtendWith注解用于指定扩展,扩展是JUnit 5中的插件机制,可以扩展测试框架的功能。常见的扩展包括参数解析、条件测试、测试拦截等。
常用断言
断言(Assertion)是测试代码中用于验证某个条件是否为真或某个结果是否符合预期的机制。在单元测试中,断言用于确保代码按预期运行,如果断言失败,测试将报告错误并指出问题所在。
JUnit 5 常断言
-
assertEquals(expected, actual)
:检查expected
和actual
是否相等。如果不相等,测试失败。assertEquals(5, calculator.add(2, 3));
-
assertNotEquals(expected, actual)
:检查expected
和actual
是否不相等。如果相等,测试失败。assertNotEquals(4, calculator.add(2, 3));
-
assertTrue(condition)
:检查condition
是否为true
。如果为false
,测试失败。assertTrue(calculator.isPositive(5));
-
assertFalse(condition)
:检查condition
是否为false
。如果为true
,测试失败。assertFalse(calculator.isNegative(5));
-
assertNull(object)
:检查object
是否为null
。如果不是null
,测试失败。assertNull(calculator.getResult());
-
assertNotNull(object)
:检查object
是否不为null
。如果为null
,测试失败。assertNotNull(calculator.getResult());
-
assertArrayEquals(expectedArray, actualArray)
:检查expectedArray
和actualArray
是否相等。如果不相等,测试失败。assertArrayEquals(new int[]{1, 2, 3}, calculator.getArray());
-
assertThrows(expectedType, executable)
:检查executable
执行时是否抛出expectedType
类型的异常。如果没有抛出或抛出不同类型的异常,测试失败。assertThrows(IllegalArgumentException.class, () -> calculator.divide(1, 0));
参数化测试
参数化测试(Parameterized Testing)是一种测试方法,它允许你用不同的输入值来执行相同的测试逻辑,以验证代码在不同输入条件下的行为。这样可以减少代码重复,增加测试覆盖面。
- JUnit 4 使用@RunWith(Parameterized.class) 来实现参数化测试。
- JUnit 5 使用 @ParameterizedTest 和不同的数据源注解(如 @ValueSource、@CsvSource、@MethodSource)来实现参数化测试。
@RunWith(Parameterized.class)
public class CalculatorTest {
private int a; // 第一个数字
private int b; // 第二个数字
private int expectedSum; // 预期的结果
// 构造函数,用于传入测试参数
public CalculatorTest(int a, int b, int expectedSum) {
this.a = a;
this.b = b;
this.expectedSum = expectedSum;
}
// 提供测试数据
@Parameterized.Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] {
{1, 1, 2},
{2, 3, 5},
{3, 5, 8},
{4, 7, 11}
});
}
// 测试方法
@Test
public void testAdd() {
Calculator calculator = new Calculator();
assertEquals(expectedSum, calculator.add(a, b)); // 验证计算结果是否正确
}
}
@ParameterizedTest
@CsvSource({
"1, 1, 2",
"2, 3, 5",
"3, 5, 8",
"4, 7, 11"
})
public void testAdd(int a, int b, int expectedSum) {
Calculator calculator = new Calculator();
assertEquals(expectedSum, calculator.add(a, b)); // 验证计算结果是否正确
}
两个重点
- 声明参数化测试
- 声明参数化测试数据源
测试套件
概念
测试套件(Test Suite)是一个组织和管理测试用例的集合。它将多个测试用例组合在一起,以便一次性执行和管理。这是一种方便的方式来组织和运行测试,特别是在面对大量测试用例时。
优点或者作用
- 集中管理:通过将相关的测试用例组织到一个测试套件中,可以方便地集中管理这些测试用例。这使得测试的维护和更新变得更为高效。
- 统一执行:测试套件允许你一次性执行一组相关的测试用例,而不是逐个运行。这对于验证某个特定功能模块的完整性特别有用。
- 提高效率:通过将测试用例分组到测试套件中,可以减少运行测试的时间。测试套件可以在不同的阶段(如开发、集成或回归测试)中重复使用。
- 组织结构清晰:测试套件可以帮助建立清晰的测试结构,使得测试结果更容易理解和分析。它可以根据功能模块、测试类型等进行组织。
- 支持回归测试:在软件开发过程中,测试套件可以用于回归测试,确保新代码的引入不会破坏现有功能
Junit4和5中如何创建测试套件
//Juint4
@RunWith(Suite.class)
@Suite.SuiteClasses({
TestClass1.class,
TestClass2.class
})
public class TestSuite {
// 这个类没有代码,作为测试套件的标识
}
//Juint4
@Suite
@SelectClasses({
TestClass1.class,
TestClass2.class
})
public class TestSuite {
// 这个类没有代码,作为测试套件的标识
}