起订量,严格与宽松的使用

Posted

技术标签:

【中文标题】起订量,严格与宽松的使用【英文标题】:Moq, strict vs loose usage 【发布时间】:2011-06-27 03:34:36 【问题描述】:

在过去,我只使用 Rhino Mocks,具有典型的严格模拟。我现在正在与 Moq 合作开展一个项目,我想知道如何正确使用。

假设我有一个带有方法 Bar 的对象 Foo,它在对象 Buzz 上调用 Bizz 方法。

在我的测试中,我想验证是否调用了 Bizz,因此我觉得有两种可能的选择:

严格的模拟

var mockBuzz= new Mock<IBuzz>(MockBehavior.Strict);
mockBuzz.Setup(x => x.Bizz()); //test will fail if Bizz method not called
foo.Buzz = mockBuzz
foo.Bar();
mockBuzz.VerifyAll();

使用松散的模拟

var mockBuzz= new Mock<IBuzz>();    
foo.Buzz = mockBuzz
foo.Bar();
mockBuzz.Verify(x => x.Bizz()) //test will fail if Bizz method not called

是否有标准或正常的方式来做这件事?

【问题讨论】:

你错过了一个 mockBuzz.VerifyAll();在第一个代码sn-p的末尾?就目前而言,即使没有调用 Bizz,测试也会通过,但如果调用任何其他方法,测试就会失败。 【参考方案1】:

当我第一次开始在单元测试中使用模拟时,我曾经使用严格的模拟。这并没有持续很长时间。我停止这样做的真正原因有两个:

    测试变得脆弱 - 使用严格的模拟,您断言不止一件事,即调用设置方法,并且不调用其他方法。当您重构代码时,测试通常会失败,即使您尝试测试的内容仍然正确。 测试更难阅读 - 您需要为模拟中调用的每个方法设置一个设置,即使它与您要测试的内容并不真正相关。当有人阅读此测试时,他们很难分辨出什么对测试很重要,什么只是实施的副作用。

由于这些,我强烈建议在您的单元测试中使用松散的模拟。

【讨论】:

-1 这正是严格模拟要好得多的原因,因为它明确描述了依赖项会发生什么。如果同一个依赖对象被许多其他意外调用触发,那么验证一个特定调用发生是没有意义的。如果测试变得过于复杂,这不是严格模拟的错,而是代码或测试架构必须重新设计的好兆头。松散的模拟对于特定情况来说是一个很好的工具,但是这个答案所暗示的一般性陈述会适得其反并且违背了测试驱动的开发原则。 @TiborTakács Uhm 从我读到他的经验来看,根据我的经验,在 TDD 方面使用 Srict 实际上会适得其反。老实说,我不明白你是如何从中得到“这就是为什么严格的模拟要好得多”...... ***.com/questions/23646384/when-to-use-strict-mocks【参考方案2】:

就我个人而言,对于 mocking 和 Moq 不熟悉,我觉得从 Strict 模式开始有助于更好地了解内部情况和正在发生的事情。 “松散”有时会隐藏细节并通过最小起订量初学者可能看不到的测试。一旦你降低了你的模拟技能 - Loose 可能会更有效率 - 比如在这种情况下,使用“Setup”保存一行并只使用“Verify”。

【讨论】:

【参考方案3】:

我有一个简单的约定:

    当被测系统 (SUT) 将调用委托给底层模拟层时,使用严格模拟,而没有真正修改或将任何业务逻辑应用于传递给自身的参数。

    当 SUT 将业务逻辑应用于传递给自身的参数并将一些派生/修改的值传递给模拟层时,请使用松散模拟。

例如: 假设我们有数据库提供者 StudentDAL,它有两种方法:

数据访问接口如下所示:

public Student GetStudentById(int id);
public IList<Student> GetStudents(int ageFilter, int classId);

使用此 DAL 的实现如下所示:

public Student FindStudent(int id)

   //StudentDAL dependency injected
   return StudentDAL.GetStudentById(id);
   //Use strict mock to test this

public IList<Student> GetStudentsForClass(StudentListRequest studentListRequest)

  //StudentDAL dependency injected
  //age filter is derived from the request and then passed on to the underlying layer
  int ageFilter = DateTime.Now.Year - studentListRequest.DateOfBirthFilter.Year;
  return StudentDAL.GetStudents(ageFilter , studentListRequest.ClassId)
  //Use loose mock and use verify api of MOQ to make sure that the age filter is correctly passed on.


【讨论】:

【参考方案4】:

我有 C++/非 .NET 开发背景,最近我更喜欢 .NET,所以当我第一次使用 Moq 时,我有一定的期望。我试图了解 WTF 正在进行我的测试,以及为什么我正在测试的代码抛出一个随机异常,而不是 Mock 库告诉我代码试图调用哪个函数。所以我发现我需要打开严格的行为,这很令人困惑——然后我遇到了这个我看到还没有勾选答案的问题。

松散模式,以及它是默认的事实太疯狂了。一个 Mock 库做一些完全不可预测的事情,而你没有明确列出它应该做的事情,到底有什么意义?

我完全不同意其他答案中列出的支持松散模式的观点。没有充分的理由使用它,我永远也不想使用它。在编写单元测试时,我想确定发生了什么——如果我知道一个函数需要返回一个空值,我会让它返回它。我希望我的测试是脆弱的(以重要的方式),以便我可以修复它们并将设置行添加到测试代码套件中,这些设置行是向我准确描述我的软件将做什么的明确信息。

问题是 - 有没有标准和正常的方式来做到这一点?

是的 - 从一般编程的角度来看,即其他语言和 .NET 世界之外,您应该始终使用 Strict。天知道为什么它不是 Moq 中的默认设置。

【讨论】:

我也一直在试图弄清楚为什么人们最近也不喜欢 Strict。我得出的结论是,想要使用 Loose 的人有很多公共方法调用同一个类中的其他公共方法。发生这种情况时,即使您只想专注于一种特定的方法,最终的类重构也会破坏您的所有测试。话虽如此,对我来说,松散模式 = 懒惰,因为你怎么能相信这些测试正在做你打算做的事情?无论如何,如果模拟默认值在未来发生变化并且它们都破坏了怎么办。严格来说,这是不可能发生的。 实际上,这甚至不是真的。严格成为问题的唯一方法是您的代码不遵循 SOLID。否则,如果更改如此剧烈,您希望测试中断的任何更改。 我同意,Strict 应该是默认设置。为什么人们不喜欢严格。因为它是反“easy peezey”。为什么有人会打开一些东西(松散),这使得发现真正的问题变得更加困难?因为easyPeezey人不关心质量。还有一种承包商的心态,他们喜欢为这些愚蠢的事情提供更多的计费时间。 #hitANerve 虽然这个讨论很老了,但我觉得我需要添加一个愚蠢的例子来帮助其他到达这里的人。想象一下 OrderProcessor 类依赖于类 Calculator。它有方法 getCost(itemCost, quantity)。单元测试不应该关心 getCost 是否调用 Calculator.add(itemCost) 数量次或调用 Calculator.add(Calculator.multiply(itemCost, quantity))。这就是为什么松散是默认的并且应该是首选的原因。有时 Strict 是有意义的,但主要是不幸的时候,你有非纯函数会产生副作用。

以上是关于起订量,严格与宽松的使用的主要内容,如果未能解决你的问题,请参考以下文章

起订量 IServiceProvider / IServiceScope

起订量中 VerifyAll() 的用途是啥?

使用最小起订量测试 Polly 重试策略

带参数的起订量 ReturnsAsync()

无法为 MediatR 设置起订量回调

如何最小起订量实体框架 SqlQuery 调用