7 个很棒的 Java 单元和集成测试库
本文译自:7 Awesome Libraries for Java Unit and Integration Testing
想要改进您的 Java 单元和集成测试?这里有七个库,您可能会发现它们对您的日常工作很有用。
AssertJ
JUnit 带有它自己的一组断言(即assertEquals
),这些断言适用于简单的用例,但在更现实的场景中使用起来非常麻烦。 AssertJ是一个小型库,可为您提供一组流畅的断言,您可以将其用作默认断言的直接替代品。它们不仅适用于核心 Java 类,您还可以使用它们编写针对 XML 或 JSON 文件以及数据库表的断言!
// basic assertions
assertThat(frodo.getName()).isEqualTo("Frodo");
assertThat(frodo).isNotEqualTo(sauron);
// chaining string specific assertions
assertThat(frodo.getName()).startsWith("Fro")
.endsWith("do")
.isEqualToIgnoringCase("frodo");
(注:来自AssertJ的源代码)
Awaitility
测试异步工作流总是很痛苦。例如,一旦您想要确保消息代理接收或发送特定消息,您就会遇到竞争条件问题,因为您的本地测试代码的执行速度比任何异步代码都要快。救援等待:它是一个小型库,可让您以同步方式编写轮询断言!
@Test
public void updatesCustomerStatus() {
// Publish an asynchronous message to a broker (e.g. RabbitMQ):
messageBroker.publishMessage(updateCustomerStatusMessage);
// Awaitility lets you wait until the asynchronous operation completes:
await().atMost(5, SECONDS).until(customerStatusIsUpdated());
...
}
(注:来自Awaitility的源代码)
Mockito
在单元测试中,有时您需要确保用模拟替换部分功能。Mockito是一个经过实战检验的库,可以做到这一点。您可以创建模拟、配置它们,并针对这些模拟编写各种断言。最重要的是,Mockito 还与大量第三方库(从 JUnit 到 Spring Boot)很好地集成。
// mock creation
List mockedList = mock(List.class);
// or even simpler with Mockito 4.10.0+
// List mockedList = mock();
// using mock object - it does not throw any "unexpected interaction" exception
mockedList.add("one");
mockedList.clear();
// selective, explicit, highly readable verification
verify(mockedList).add("one");
verify(mockedList).clear();
(注意:来自Mockito的源代码)
Wiser
使您的代码尽可能接近生产,而不是对所有内容都使用 mock 是一种可行的策略。例如,当您想要发送电子邮件时,您既不需要完全模拟您的电子邮件代码,也不需要实际通过 Gmail 或 Amazon SES 发送它们。相反,您可以启动一个名为 Wiser 的小型嵌入式 Java SMTP 服务器。
Wiser wiser = new Wiser();
wiser.setPort(2500); // Default is 25
wiser.start();
现在您可以使用 Java 的 SMTP API 向 Wiser 发送电子邮件,并要求 Wiser 向您显示它收到的消息。
for (WiserMessage message : wiser.getMessages())
{
String envelopeSender = message.getEnvelopeSender();
String envelopeReceiver = message.getEnvelopeReceiver();
MimeMessage mess = message.getMimeMessage();
// now do something fun!
}
(注:源代码来自GitHub 上的 Wiser)
Memoryfilesystem
如果你编写一个严重依赖文件的系统,问题总是:“你如何测试它?” 文件系统访问有点慢,而且很脆弱,尤其是当您的开发人员在不同的操作系统上工作时。内存文件系统来拯救!它允许您针对完全存在于内存中的文件系统编写测试,但仍可以模拟特定于操作系统的语义,从 Windows 到 macOS 和 Linux。
try (FileSystem fileSystem = MemoryFileSystemBuilder.newEmpty().build()) {
Path p = fileSystem.getPath("p");
System.out.println(Files.exists(p));
}
(注:源代码来自GitHub 上的 Memoryfilesystem)
WireMock
如何在测试中处理不稳定的第 3 方 REST 服务或 API?简单的!使用 WireMock。它允许您使用非常简单的 DSL 创建任何第三方 API 的成熟模拟。您不仅可以指定您模拟的 API 将返回的特定响应,甚至可以将随机延迟和其他未指定的行为注入您的服务器或进行一些混沌猴子工程。
// The static DSL will be automatically configured for you
stubFor(get("/static-dsl").willReturn(ok()));
// Instance DSL can be obtained from the runtime info parameter
WireMock wireMock = wmRuntimeInfo.getWireMock();
wireMock.register(get("/instance-dsl").willReturn(ok()));
// Info such as port numbers is also available
int port = wmRuntimeInfo.getHttpPort();
(注意:来自WireMock的源代码)
Testcontainers
对数据库、邮件服务器或消息队列使用模拟或嵌入式替代品非常好,但没有什么比使用真实的东西更好的了。Testcontainers来了:一个小型库,允许您启动和关闭测试所需的任何Docker容器(以及软件)。这意味着您的测试环境可以尽可能接近您的生产环境。
@Testcontainers
class MixedLifecycleTests {
// will be shared between test methods
@Container
private static final MySQLContainer MY_SQL_CONTAINER = new MySQLContainer();
// will be started before and stopped after each test method
@Container
private PostgreSQLContainer postgresqlContainer = new PostgreSQLContainer()
.withDatabaseName("foo")
.withUsername("foo")
.withPassword("secret");
(注意:来自Testcontainers的源代码)