没有断言的单元测试
有时候,我遇到了一个没有断言任何内容的单元测试。我今天早上遇到的一个特定示例是测试是否在满足条件时写入了日志文件。假设是如果没有错误抛出,则测试通过。
我个人对此没有问题,但是编写没有任何断言的单元测试似乎有点"代码异味"。
只是想知道人们对此有何看法?
解决方案
从某种意义上讲,我们是在隐式断言该代码不会引发异常。当然,实际抓取文件并找到合适的行会更有价值,但是我想总有没有总比没有好。
总的来说,我认为这是在集成测试中发生的,仅仅是成功完成某件事就足够了。在这种情况下,我很酷。
我想如果我在单元测试中一遍又一遍地看到它,我会对测试真正有用的情况感到好奇。
编辑:在OP给出的示例中,有一些可测试的结果(日志文件结果),因此假设如果未引发任何错误,则它的工作很懒。
这只是一个非常小的测试,因此应记录在案。它仅验证它在运行时不会爆炸。像这样的测试最糟糕的部分是它们呈现出错误的安全感。代码覆盖率将提高,但这是虚幻的。难闻的气味。
这可能是一个很好的实用解决方案,尤其是如果替代方法完全没有测试的话。
问题是,如果所有调用的功能均为无操作,则测试将通过。但是有时候,要验证副作用是否符合预期是不可行的。在理想的世界中,将有足够的时间为每项测试写支票……但我不住在那儿。
我使用此模式的另一个地方是将一些性能测试嵌入到单元测试中,因为这是让它们在每个构建中运行的简便方法。测试没有断言任何内容,而是测量测试花费了多长时间并记录下来。
我必须承认,我从未编写过可以验证我正确记录的单元测试。但是我确实对此进行了思考,并遇到了有关如何使用JUnit和Log4J进行讨论的讨论。它不太漂亮,但看起来像是可以用的。
这将是官方的方法:
// Act Exception ex = Record.Exception(() => someCode()); // Assert Assert.Null(ex);
这样的测试闻起来。它应该检查文件是否已写入,至少修改的时间可能已更新。
我已经看到很多这样编写的测试,最终根本没有测试任何东西,即代码不起作用,但是也没有崩溃。
如果我们有一个明确的要求,即被测试的代码不要抛出异常,并且我们想明确地指出这一事实(作为要求文档进行测试),那么我将执行以下操作:
try { unitUnderTest.DoWork() } catch { Assert.Fail("code should never throw exceptions but failed with ...") }
...但是这对我来说还是有点难闻,可能是因为它试图证明是负面的。
测试应该始终断言某些东西,否则我们将证明什么,以及如何始终如一地重现代码可正常工作的证据?
我们一直在这样做。我们使用JMock模拟依赖关系,所以从某种意义上来说,我猜JMock框架正在为我们做断言……但是它确实是这样。我们有一个要测试的控制器:
Class Controller { private Validator validator; public void control(){ validator.validate; } public setValidator(Validator validator){ this.validator = validator; } }
现在,当我们测试Controller时,我们不想测试Validator,因为它具有自己的测试。因此,我们使用JMock进行了测试,以确保我们调用validate:
public void testControlShouldCallValidate(){ mockValidator.expects(once()).method("validate"); controller.control; }
就是这样,没有" assertion"可以看到,但是当我们调用控件且未调用" validate"方法时,JMock框架将引发异常(诸如" expected method not invoked"之类的东西)。
我们到处都有。因为我们基本上设置了断言,然后调用被测方法,所以这有些倒退。
我以前见过类似的东西,我认为这样做只是为了支持代码覆盖率数字。可能不是真正在测试代码行为。无论如何,我都同意在测试中将其(意图)记录在案以保持清晰。
这些被称为烟雾测试,并且很常见。它们是基本的健康检查。但是,它们不应该是我们仅有的唯一一种测试。我们仍然需要在另一项测试中进行某种形式的验证。
如果没有断言,那就不是测试。
别太懒惰了-可能需要一点时间来弄清楚如何在其中获得断言,但是值得知道它确实做了我们期望的工作。
有时,我会使用选择的单元测试框架(NUnit)来构建用作代码特定部分入口点的方法。这些方法对于分析代码子集的性能,内存消耗和资源消耗很有用。
这些方法绝对不是单元测试(即使已使用[Test]属性标记),并且在将其检入源代码管理时,始终将其标记为忽略并明确记录。
我有时还会将这些方法用作Visual Studio调试器的入口点。我使用Resharper直接进入测试,然后进入要调试的代码。这些方法或者没有达到源代码控制的目的,或者获得了自己的断言。
我的"真实"单元测试是在正常的TDD周期内构建的,它们始终声明某些东西,尽管有时并不总是直接声明某些东西,这些声明是模拟框架的一部分,有时我可以将相似的声明重构为一个方法。这些重构方法的名称始终以前缀" Assert"开头,以使其对我显而易见。
测试的名称应对此进行记录。
void TestLogDoesNotThrowException(void) { log("blah blah"); }
测试如何验证日志是否写成没有断言?