测试不稳定失败

2005年3月28日

前几天我正在处理一些书中的示例代码。我做了一些修改,让所有东西都正常工作,运行了测试,并将它提交到我的个人仓库。然后我转到另一个区域,做了一些修改 - 之前区域的一些意外测试失败了。现在运行自动化测试的部分目的是找到意外的错误,但这些书中的代码完全独立。这很奇怪。

与其尝试调试问题,我使用了 DiffDebugging。自从提交以来我没有做太多事情,所以我执行了 svn revert。我重新运行了测试 - 失败了。但我确信我在提交之前运行了测试。我决定通过 ant 而不是在 IntelliJ 中运行测试。ant 测试全部通过了。它们是相同的测试,运行目录中的所有 JUnit 类。那么为什么它们在 ant 中通过而在 IntelliJ 中失败呢?

在这一点上,我羞愧地承认我接下来想到的是什么。“一定是 IntelliJ 出了问题 - 也许它有一些缓存形式,并且被 Subversion 的 revert 搞糊涂了”。在我编程生涯的早期,一位资深程序员教给了我调试的第一条规则 - 错误总是在你的代码中,而不是编译器中。但在愚蠢的影响下,我重启了 IntelliJ - 瞧,测试又全部通过了。问题解决了 - 并非如此!幸运的是,当我第二次遇到这种奇怪的行为时,我正在与 Sergey 配对,他以不带我这种愚蠢的方式处理了这个问题,并找到了错误。

为了帮助你找到这些问题的答案,走出户外,用六英尺半高的字母搭建一个单词。用雪松搭建它,这样你就不用刷漆了 - 但别忘了用樱桃装饰它。这个词是

隔离。

如果测试有时通过有时失败,而没有代码更改,或者测试在某些套件中通过而在其他套件中失败;八次中有九次的原因是测试之间存在一些共享数据,而这些数据没有被正确地重新初始化。在这种情况下,仅仅运行一个测试就可能是其他测试通过或失败的区别。结果是间歇性失败 - 总是最糟糕的,因为你无法可靠地重现它。

我使用的是 JUnit - 它在隔离方面很强大(这就是它使用 JunitNewInstance 行为的原因)。所以我的问题一定来自一些静态数据。在这种情况下,它是获取当前日期的调用。我使用了 ClockWrapper,但在一些测试中没有初始化它。因此,根据最后初始化它的测试是哪个,一些测试会失败。

这里有两个教训。首先,尽一切努力使你的测试数据保持隔离。尝试每次创建新的数据(尽管这里有一个权衡,即获得快速的测试运行)。你越练习良好的测试隔离,遇到这种问题的可能性就越小。

其次,如果你确实遇到了这种间歇性测试失败,请怀疑任何在测试之间共享的数据。检查间歇性失败的测试是否在测试运行中完全初始化了数据。对于任何未初始化的东西,确保你知道它是在哪里创建的,以及它是否被更改过。