似乎使用它们作为一种方法来确定被测方法是否正确执行会产生反作用,因为它会导致脆弱的测试。换句话说,您将测试与实施相结合。因此,如果您以后想要更改实施,那么您还必须更改测试。我问这个问题是因为我接受过训练,在每次单元测试中总是至少使用其中一种方法,我想我可能只是顿悟了这实际上是一种非常糟糕的做法。
答案 0 :(得分:12)
首先,重要的是要了解Verify
- 家庭方法是有原因的 - 它们允许您测试不可观察的 1 行为你的系统。那是什么意思?考虑应用程序生成和发送报告的简单示例。您的最终组件很可能如下所示:
public void SendReport(DateTime reportDate, ReportType reportType)
{
var report = generator.GenerateReport(reportDate, reportType);
var reportAsPlainText = converter.ConvertReportToText(report);
reportSender.SendEmailToSubscribers(body: reportAsPlainText);
}
你如何测试这种方法?它不会返回任何内容,因此您无法检查值。它不会改变系统的状态(比如,翻转一些标志),因此你也无法检查。 SendReport
被调用的唯一可见结果是报告是通过SendEmailToSubscribers
调用发送的。这是SendReport
方法的主要责任 - 这是单元测试应该验证的。
当然,您的单元测试不应该也不会检查是否发送了某些电子邮件。您将验证reportSender
的模拟。这是您使用Verify
方法的地方。检查是否确实发生了对某些模拟的调用。
最后一点,Roy Osherove在他的书Art Of Unit Testing (2nd edition)中将单元测试分为三类,取决于可以检查的内容:
最后一类是您使用模拟和Verify
方法的地方。对于其他两个,存根就足够了(Setup
方法)。
当您的代码设计正确时,此类测试(最后一类)将在您的代码库中占少数,在5% - 10%的范围内(从Roy的书中获取的数字,符合我的观察)
1 :不可观察,因为调用者无法轻易验证调用后到底发生了什么。
答案 1 :(得分:5)
基于模拟的测试
关于单元测试中模拟的脆弱性以及它们是否是好事,存在很多争论。我个人认为这是在可维护性和稳健性之间必须做出的权衡。您将生产代码放在unit test pressure下越多,使用模拟单独测试它,实现的次数越少,测试就越少。因此,您可以强制您的生产代码具有稳健性和良好的设计。另一方面,它确实将自己绑定到特定的实现并增加了维护负担,因为一旦实现细节发生变化,就必须更改更多的测试。
VerifyAll()语法
这主要是品味问题,但我发现VerifyAll()
并非意图揭示,即当您阅读测试套件时,您只希望通过查看断言来了解规范,并且VerifyAll()
根本没有任何意义。 即使我编写基于模拟的测试,我也更喜欢Arrange Act Assert方法和特定的断言失败消息。它比一般的VerifyAll()
电话更清晰,更少“神奇”。
在每个测试方法中使用VerifyAll()
最好是矫枉过正,最坏的情况是会对你的测试套件造成损害。
作为一般规则,单元测试应该只测试一件事。除了正常的断言之外,系统地调用VerifyAll()
会带来混乱 - 如果测试失败,你无法确定出现了什么问题。
就可读性而言,您只是在每个测试中添加噪音。通过阅读测试方法来追溯到VerifyAll()
的真正含义是非常困难的。
您通常希望选择使用模拟对您的实施施加设计压力的位置,而不是盲目地在任何地方应用它,因为它有维护价格。
因此,如果您真的必须使用VerifyAll()
,最好为IMO编写单独的测试。
答案 2 :(得分:1)
首选方法是使用AAA。但对于具有void返回类型的外部依赖项(假设为void WriteData(数据数据)),Verify可能很有用(或Setup,然后是VerifyAll)。