您是否应该在单元测试中使用Moq提供的"验证"和"VerifyAll"方法?

Ada*_*ene 7 tdd automated-tests unit-testing moq

似乎使用它们作为一种方法来确定被测方法是否正确执行会产生反作用,因为它会导致脆弱的测试.换句话说,你将测试与实现联系在一起.因此,如果您以后想要更改实现,那么您还必须更改测试.我问这个问题是因为我接受过训练,在每次单元测试中总是至少使用其中一种方法,我想我可能只是顿悟了这实际上是一种非常糟糕的做法.

k.m*_*k.m 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);
}
Run Code Online (Sandbox Code Playgroud)

你如何测试这种方法?它不会返回任何内容,因此您无法检查值.它不会改变系统的状态(比如,翻转一些标志),因此你也无法检查.SendReport被调用的唯一可见结果是报告是通过SendEmailToSubscribers调用发送的.这是SendReport方法的主要责任- 这是单元测试应该验证的.

当然,您的单元测试不应该也不会检查是否发送了一些电子邮件.你将验证模拟reportSender.这就是你使用Verify方法的地方.要检查是否实际发生了一些对某些模拟的调用.

最后一点,Roy Osherove在他的"单位测试艺术"(第2版)一书中将单元测试分为三类,具体取决于可以检查的内容:

  • 方法的返回值(简单,常见)
  • 系统状态的变化(简单,罕见)
  • 呼叫外部组件(复杂,罕见)

最后一类是Verify在它们上使用模拟和方法的地方.对于其他两个,存根就足够了(Setup方法).

当您的代码设计正确时,此类测试将(最后一类)在您的代码库中占少数,在5% - 10%的范围内(从Roy的书中获取的数字,符合我的观察).


1:不可观察,因为呼叫者无法轻易验证呼叫后到底发生了什么.


gui*_*e31 5

基于模拟的测试

关于单元测试中模拟的脆弱性以及它们是否是好事,存在很多争论.我个人认为这是在可维护性和稳健性之间必须做出的权衡.您将生产代码置于单元测试压力之下的越多,使用模拟单独测试它,实现的可能性就越少.因此,您可以强制您的生产代码具有稳健性和良好的设计.另一方面,它确实将自己绑定到特定实现并增加了维护负担,因为一旦实现细节发生更改,就必须更改更多测试.

VerifyAll()语法

这主要是一种品味问题,但我发现这VerifyAll()并非意图揭示,即当您阅读测试套件时,您只希望通过查看断言来了解规范,并且VerifyAll()根本没有任何意义.即使我编写基于模拟的测试,我也更喜欢使用特定断言失败消息的Arrange Act Assert方法.它比一个无所不能的VerifyAll()电话更清晰,更少"神奇" .

在每个测试方法中使用VerifyAll()

它最多是矫枉过正,最坏的情况会对您的测试套件造成损害.

  • 作为一般规则,单元测试应该只测试一件事.VerifyAll()除了正常的断言之外,系统地调用会带来混乱 - 如果测试失败,你无法确定出现了什么问题.

  • 就可读性而言,您只是在每个测试中添加噪声.通过阅读测试方法来追溯到VerifyAll()真正意义上的东西是非常困难的.

  • 您通常希望选择在您的实施中使用模拟应用设计压力的位置,而不是盲目地在任何地方应用它,因为它有维护价格.

因此,如果你真的必须使用VerifyAll(),最好为IMO编写单独的测试.