1. 概述
TestNG 是一个流行的Java测试框架,它是JUnit的替代选择。尽管两者都提供了自己的范式,但它们都包含了一个概念:断言,即如果评估为假,则会停止程序执行并失败的逻辑语句。TestNG中的一个简单断言可能如下所示:
@Test
void testNotNull() {
assertNotNull("My String");
}
但是,如果我们需要在一个测试中执行多个断言呢?在这篇文章中,我们将探讨TestNG的SoftAssert
,这是一种同时执行多个断言的技术。
2. 准备工作
为了我们的练习,我们定义一个简单的Book
类:
public class Book {
private String isbn;
private String title;
private String author;
// Standard getters and setters...
}
我们还可以定义一个接口,它模拟了一个基于ISBN查找Book
的简单服务:
interface BookService {
Book getBook(String isbn);
}
然后,我们可以在单元测试中对该服务进行模拟,这将在稍后定义。这个设置让我们能够以一种现实的方式定义一个可以测试的场景:一个返回可能是null的对象或其成员变量可能是null的服务。现在开始编写针对此场景的单元测试。
3. 基本断言与TestNG的SoftAssert
为了展示SoftAssert
的好处,我们将首先创建一个使用基本TestNG断言的单元测试,并比较我们得到的反馈与使用SoftAssert
的相同测试。
3.1. 使用传统断言
首先,我们将使用assertNotNull()
创建一个测试,该方法接受一个要测试的值和可选的消息:
@Test
void givenBook_whenCheckingFields_thenAssertNotNull() {
Book gatsby = bookService.getBook("9780743273565");
assertNotNull(gatsby.isbn, "ISBN");
assertNotNull(gatsby.title, "title");
assertNotNull(gatsby.author, "author");
}
然后,我们将使用Mockito定义一个模拟的BookService
实现,它返回一个Book
实例:
@BeforeMethod
void setup() {
bookService = mock(BookService.class);
Book book = new Book();
when(bookService.getBook(any())).thenReturn(book);
}
运行测试时,我们可以看到我们忘记设置了isbn
字段:
java.lang.AssertionError: ISBN expected object to not be null
让我们修复这个模拟,再次运行测试:
@BeforeMethod void setup() {
bookService = mock(BookService.class);
Book book = new Book();
book.setIsbn("9780743273565");
when(bookService.getBook(any())).thenReturn(book);
}
现在我们收到了不同的错误:
java.lang.AssertionError: title expected object to not be null
再次,我们在模拟中忘记了初始化一个字段,导致了另一个必要的更改。
正如我们所见,这种测试、修改和重新运行测试的循环不仅令人沮丧,而且耗时。当然,随着类的大小和复杂性的增加,这种影响会成倍增加。在集成测试的情况下,远程部署环境中的失败可能难以或不可能在本地复现。通常,集成测试更复杂,因此执行时间较长。再加上部署测试变更所需的时间,每次额外的测试重跑的循环时间是昂贵的。
幸运的是,我们可以通过使用SoftAssert
来立即不中断程序执行地评估多个断言来避免这个问题。
3.2. 使用SoftAssert
分组断言
让我们更新上面的示例,使用SoftAssert
:
@Test void givenBook_whenCheckingFields_thenAssertNotNull() {
Book gatsby = bookService.getBook("9780743273565");
SoftAssert softAssert = new SoftAssert();
softAssert.assertNotNull(gatsby.isbn, "ISBN");
softAssert.assertNotNull(gatsby.title, "title");
softAssert.assertNotNull(gatsby.author, "author");
softAssert.assertAll();
}
让我们分解一下:
- 首先,我们创建一个
SoftAssert
实例。 - 接下来,我们做出关键改变:我们将断言针对
SoftAssert
实例,而不是使用TestNG的基本assertNonNull()
方法 - 最后,同样重要的是注意我们需要在准备好获取所有断言结果时调用
SoftAssert
实例的assertAll()
方法
现在,如果我们使用原始模拟(其中没有为Book
的任何成员变量值设置值),运行此测试,我们将看到包含所有断言失败的单个错误消息:
java.lang.AssertionError: The following asserts failed:
ISBN expected object to not be null,
title expected object to not be null,
author expected object to not be null
这展示了当一个测试需要多个断言时,使用SoftAssert
是一种良好的实践。
3.3. SoftAssert
的考虑
虽然SoftAssert
设置和使用起来很容易,但有一个重要的注意事项:状态性。由于SoftAssert
内部记录每个断言的失败,它不适合跨多个测试方法共享。因此,我们应该确保在每个测试方法中创建一个新的SoftAssert
实例。
4. 总结
在这篇教程中,我们学习了如何使用TestNG的SoftAssert
进行多个断言,并了解了它如何成为编写具有减少调试时间的清洁测试的有用工具。我们还了解到SoftAssert
是状态性的,实例不应在多个测试之间共享。
如往常一样,所有的代码都可以在GitHub上找到。