Unit testing 您是否应该使用;核实;及;“验证全部”;Moq在单元测试中提供的方法?

Unit testing 您是否应该使用;核实;及;“验证全部”;Moq在单元测试中提供的方法?,unit-testing,tdd,automated-tests,moq,Unit Testing,Tdd,Automated Tests,Moq,似乎使用它们作为一种方法来确定被测试的方法是否正确执行会适得其反,因为它会导致脆性测试。换句话说,您将测试与实现绑定在一起。因此,如果以后要更改实现,还必须更改测试。我问这个问题是因为我被训练在每个单元测试中至少使用其中一种方法,我想我可能刚刚意识到这实际上是一种非常糟糕的做法 首选方法是使用AAA。但是对于具有void返回类型的外部依赖项(比如void WriteData(数据数据)),Verify可能很有用(或者设置,然后VerifyAll)。首先,重要的是要了解Verify-族方法的存在是

似乎使用它们作为一种方法来确定被测试的方法是否正确执行会适得其反,因为它会导致脆性测试。换句话说,您将测试与实现绑定在一起。因此,如果以后要更改实现,还必须更改测试。我问这个问题是因为我被训练在每个单元测试中至少使用其中一种方法,我想我可能刚刚意识到这实际上是一种非常糟糕的做法

首选方法是使用AAA。但是对于具有void返回类型的外部依赖项(比如void WriteData(数据数据)),Verify可能很有用(或者设置,然后VerifyAll)。

首先,重要的是要了解
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
的唯一可见结果是,报告是通过
sendmailtosubscribers
调用发送的。这是
SendReport
方法的主要职责——这也是单元测试应该验证的

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

最后,Roy Osherove在他的书中将单元测试分为三类,这取决于可以检查的内容:

  • 方法的返回值(简单、通用)
  • 系统状态变化(简单、罕见)
  • 调用外部组件(复杂、罕见)
最后一类是使用mock和
Verify
方法的地方。对于其他两个,存根就足够了(
Setup
methods)

当您的代码设计正确时,这样的测试(最后一类)将在您的代码库中占少数,大约在5%-10%的范围内(根据我的观察,这个数字取自Roy的书)



1:无法观察,因为调用方无法轻松验证调用后到底发生了什么。

基于模拟的测试

关于单元测试中模拟的脆弱性以及它们是否是一件好事,有很多争论。我个人认为这是在可维护性和健壮性之间必须进行的权衡。您对生产代码进行的测试越多,并且与mock隔离测试,通过测试的实现就越少。因此,您可以强制生产代码具有健壮性和良好的设计。另一方面,它确实将您与特定的实现联系在一起,并增加了维护负担,因为一旦实现细节发生变化,就必须更改更多的测试

VerifyAll()语法

这主要是一个品味的问题,但我发现
VerifyAll()
并没有揭示意图,也就是说,当你阅读一个测试套件时,你会期望通过查看断言就对规范有一个很好的了解,而
VerifyAll()
没有任何意义。即使在编写基于模拟的测试时,我也更喜欢使用带有特定断言失败消息的方法。它比catch-all
VerifyAll()
call更清晰、更不“神奇”

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

这充其量是过度的,最坏的情况是会对您的测试套件造成损害

  • 一般来说,单元测试应该只测试一件事情。除了正常的断言之外,系统地调用
    VerifyAll()
    会带来混乱——如果测试失败,您无法确定到底出了什么问题

  • 就可读性而言,您只是在给每个测试添加噪音。仅仅通过阅读一个测试方法来追溯那
    VerifyAll()
    的真正含义是非常困难的

  • 您通常希望选择在何处使用mock对您的实现施加设计压力,而不是盲目地在任何地方应用它,因为它需要维护成本


所以,如果你真的必须使用<代码> ViFielAlar()/Cube >,最好为IO ./P>编写单独的测试,我也认为它们是不好的练习。它们是无用的,正如你所说:甚至适得其反。绝对没有什么好处,但人们这样做是出于自动化。这与单元测试的想法背道而驰,所以除非其他人能提供一个很好的理由:不要麻烦他们。