停止重复运行你的测试
引言
测试通常是你开发过程中运行时间最长的操作。不必要地运行它们是终极的时间浪费。Gradle通过其构建缓存和增量构建功能帮助你避免这种成本。它知道你的任何测试输入,如代码、依赖项或系统属性何时发生变化。如果一切保持不变,Gradle将跳过测试运行,为你节省大量时间。
所以你可以想象,当我在StackOverflow上看到这样的代码片段时,我有多么绝望
tasks.withType(Test) {
outputs.upToDateWhen { false }
}
让我们谈谈这意味着什么,以及为什么它是一个糟糕的主意。
传达意图 #
上面的代码片段只是说“永不复用此测试的输出”。但为什么呢?是因为存在一些Gradle不知道的隐藏输入吗?还是因为测试产生随机输出?读者无法判断。
确定性测试无需重复运行 #
“精神错乱就是一遍又一遍地做同样的事情,却期望得到不同的结果”
- 不是阿尔伯特·爱因斯坦说的
你的绝大多数测试都应该是确定性的,即在给定相同输入的情况下,它们应该产生相同的结果。如果不是这样,你的项目就处于严重问题之中。停止阅读本文,开始修复你的代码!
重复运行确定性测试是浪费团队时间。
非确定性测试 #
在某些情况下,即使代码没有改变,你也可能希望重复运行*一些*测试。在这些情况下,你应该正确地建模额外的输入。告诉Gradle究竟是什么使你的测试非确定性。
随机化测试 #
有些测试使用随机化来提高软件质量。
通过将随机种子作为任务的输入,使这种随机化变得明确
task randomizedTest(type: Test) {
systemProperty "random.testing.seed", new Random().nextInt()
}
这将强制Gradle总是重新运行该测试,因为它将总是有一个不同的种子。更好的是,你可以使种子可由用户配置,以便在本地重现构建服务器上发现的错误。
系统集成测试 #
系统集成测试根据其他团队控制的其他应用程序的真实版本来验证你的应用程序。它们即使你没有改变任何东西也可能会失败,例如,因为另一个团队破坏了一个API。它们也往往是你代码库中最慢的测试之一,所以你不想仅仅因为你改变了一些文档就重新运行它们。一个好的折衷方案可能是至少每天检查一次集成,即使你这边没有任何改变。
将此间隔作为测试输入的一部分
task systemIntegrationTest(type: Test) {
inputs.property "integration.date", LocalDate.now()
}
然后,你可以设置一个自动化构建,在每个人开始工作之前,在早上运行这个测试,并将其结果推送到共享构建缓存。当你的团队来上班时,测试结果将从缓存中下载,当天就不需要重新运行测试了。然后,他们可以将节省的时间用于更具生产力的任务,例如修复错误或开发新功能。
不稳定测试 #
有时你会遇到一个只在10次中有1次使测试失败的bug。为了分析情况,即使测试之前成功了,你也希望Gradle重新运行测试。在这种情况下,使用我上面展示的随机输入方法是合理的。然而,我发现将不稳定测试包装在一个无限循环中更有效。这样我就可以让调试器在IDE中运行,甚至在不重启的情况下进行小的即时更改。
正确地建模你的测试 #
正确地建模你的需求对你的构建逻辑和生产代码同样重要。正确地做到这一点将使你的构建更快,你的团队更高效。
java
插件内置的 test
任务是为确定性且快速的单元测试而设计的。将所有测试都放入这个任务中并在每次构建时重新运行,仅仅因为其中一些是非确定性的,这可能看起来很方便。这种懒惰的方法会为你节省几分钟的思考和编码时间,但长期的成本是巨大的,每天都会拖慢你团队中的每个人。
相反,为不同类型的测试创建额外的 Test
任务,例如功能测试、性能测试或随机测试。对于每个任务,考虑它何时需要重新运行,并将其建模为任务的输入。
别浪费时间了。