Fast Tests Help Humans, Deep Tests Help Servers

The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:

为了揭示更复杂的错误,自动化测试被转化为涉及外部资源的集成测试,而不是使用模拟。尽管这种方法提高了测试覆盖率,但却减慢了整个构建流程。这损害了自动化测试的本质,它们旨在成为一个安全网,帮助程序员安全地编辑代码。将测试分为“快速”和“深度”,然后允许人类运行前者,而服务器运行后者,可能是解决这个问题的好办法。

考虑以下具有简单toString()静态方法的Java代码:

它逐字节读取stream,将它们附加到缓冲区,并将缓冲区返回给客户端。以下是验证功能的JUnit5测试:

到目前为止,一切都很好。测试工作正常,方法似乎正确。而且,测试完成非常迅速——在我的笔记本电脑上只需5毫秒。然而,经过仔细检查,我们可以发现方法中存在一个错误:它没有关闭输入流。这个问题对测试没有影响,因为输入流在内存中,并没有持有可能泄漏的有价值的资源。然而,如果我们引入一个新的测试,它将暴露这个问题。

当我运行这个测试时,我会收到一个FileNotFoundException的错误,错误信息显示为Too many open files。如果我将for循环的上限减少到10000,错误就消失了。这绝对是因为Mac OS X上的最大打开文件数为12,288。然而,在Ubuntu上,这个限制被设置为65536。因此,如果我在Ubuntu上运行测试,就不会发现错误。我相信你知道如何修复toString()方法中的这个错误。

显然,第二个测试比第一个测试要慢得多,在我的笔记本上需要650毫秒(慢了130倍!)。这只是一个帮助检测错误但耗时的测试的例子。通常,集成测试对性能产生负面影响,因为它们涉及到慢速的“外部”资源。第二个测试使用的文件系统就是这样一个外部资源。

当一个年轻项目中只有几个测试方法时,650毫秒可能不成问题。然而,随着测试数量的增加,慢速测试很快就会成为一个问题,因为整体构建时间变长,让程序员感到沮丧。自动化测试原本是为了帮助程序员,却变成了一个阻碍。如果一个程序员在每次代码更改后都必须等待几分钟来确保没有出错,那么就会产生沮丧。通常,受挫的程序员可能会删除那些慢速测试。

不用说删除慢速测试不是解决方案。那么,解决方案是什么呢?加快它们的速度?并不完全正确。几乎总是很具挑战性,如果不是不可能的话,要使集成测试更快,因为它们本质上是慢的。唯一能加快它们的方法就是模拟那些慢速的外部资源。但是这些资源是专门用来检测单元测试可能忽略的错误的。例如,在我们的情况下,如果我们模拟输入流,第二个测试将无法发现错误。因此,第二个(集成)测试必须是慢的才有价值。

将测试分类为fast(快速)和deep(深入)可能是一个解决方案。第一类包括尽可能多地模拟,并且运行时间不超过20毫秒的测试。第二类包括更深入地探测,以发现可能被更快的测试忽视的隐匿错误。往往,单元测试属于第一类,而集成测试属于第二类。我认为“单元测试与集成测试”的区分是误导性的。使用“快速与深入”更清晰,因为一个测试属于哪个类别是显而易见的。如果一个测试运行时间不到20毫秒,它就是快速的;否则,它就是深入的。

一旦将测试标记为快速或深入,它们应该在两种不同的情景下运行:程序员在编码过程中运行快速测试,而服务器在软件构建和/或发布阶段执行深入测试。在JUnit5中,可以使用@Tag注解来实现这种分类。

在大多数情况下,快速测试会检测出明显的错误,给程序员在编辑代码时带来信心。在极少数情况下,快速测试无法识别出某些错误,深层测试将会捕捉到它们。只有在这种情况下,程序员才会在他们的笔记本电脑上运行慢速测试。

以下是如何配置pom.xml以默认打开“快速”测试的方法:

在CI环境中,必须使用以下标志启动Maven:

一个程序员也可以在自己的笔记本电脑上使用相同的命令行标志运行“慢速”测试。然而,这通常只会在服务器发出红色信号时进行。

顺便说一下,toString() 方法还有另一个错误,既不被第一个测试也不被第二个测试检测到。你能找出这个错误吗?你能设计一个能暴露这个错误的测试吗?你会将这个测试归类为“快速”还是“深度”?

Translated by ChatGPT gpt-3.5-turbo/42 on 2023-12-27 at 13:44

sixnines availability badge   GitHub stars