Unit testing 什么是严格的和非严格的模拟?

Unit testing 什么是严格的和非严格的模拟?,unit-testing,mocking,moq,Unit Testing,Mocking,Moq,我已经开始使用moq进行模拟。有人能给我解释一下严格和非严格模拟的概念吗?如何在moq中使用它们 编辑: 在哪种情况下,我们使用哪种类型的mock?我不确定moq的具体情况,但以下是Rhino中严格的mock是如何工作的。我声明我希望在我的对象foo上调用foo.Bar: foo.Expect(f => f.Bar()).Returns(5); 如果调用代码 foo.Bar(); 那么我很好,因为我的期望完全满足了 但是,如果调用代码为: foo.Quux(12); foo.Bar()

我已经开始使用moq进行模拟。有人能给我解释一下严格和非严格模拟的概念吗?如何在moq中使用它们

编辑:
在哪种情况下,我们使用哪种类型的mock?

我不确定moq的具体情况,但以下是Rhino中严格的mock是如何工作的。我声明我希望在我的对象
foo
上调用
foo.Bar

foo.Expect(f => f.Bar()).Returns(5);
如果调用代码

foo.Bar();
那么我很好,因为我的期望完全满足了

但是,如果调用代码为:

foo.Quux(12);
foo.Bar();
然后我的期望失败了,因为我没有明确地期望调用
foo.qux

总而言之,如果与预期不同,严格的模拟将立即失败。另一方面,非严格的mock(或存根)很乐意“忽略”对
foo.qux
的调用,它应该为
foo.qux
的返回类型
T
返回
default(T)


Rhino的创建者(并且更喜欢存根),因为您通常不希望在收到上述意外调用时测试失败。当您必须修复几十个依赖于原始行为的测试时,重构代码会变得更加困难。

是否遇到过给定的/when/Then

  • 给定上下文
  • 当我表演一些活动时
  • 然后就应该有结果了
这种模式出现在BDD的场景中,也与单元测试相关

如果您正在设置上下文,您将使用上下文提供的信息。例如,如果您正在按Id查找某个内容,这就是上下文。如果它不存在,测试将不会运行。在本例中,您希望使用NiceMock、Stub或其他-Moq的默认运行方式

如果要验证结果,可以使用Moq的verify。在本例中,您希望记录相关的交互。幸运的是,这也是Moq的默认运行方式。如果考试中发生了你不感兴趣的事情,它不会抱怨

StrictMock适用于您不希望发生意外交互的情况。这就是旧式模拟框架的运行方式。如果你正在做BDD风格的例子,你可能不会想要这个。如果你把你感兴趣的行为的各个方面分开,它会使测试变得有点脆弱,更难阅读。你必须为环境和结果设定期望,为所有将要发生的结果设定期望,不管它们是否有趣

例如,如果您正在测试一个控制器并模拟验证程序和存储库,并且您想验证您是否保存了对象,那么使用严格的模拟,您还必须首先验证您是否验证了对象。我更喜欢在单独的例子中看到行为的这两个方面,因为这使我更容易理解控制器的价值和行为

在过去的四年中,我没有发现一个需要使用严格模拟的例子——要么是我想要验证的结果(即使我验证了调用的次数),要么是我可以判断我是否正确响应所提供的信息的上下文。所以在回答你的问题时:

  • 非严格模拟:通常
  • 严格模拟:最好不要
注意:我对BDD有强烈的偏见,所以核心TDD可能不同意我的观点,而且这对他们的工作方式是正确的。

这里有一个好的建议。
我通常会吃这样的东西

public class TestThis {

    private final Collaborator1 collaborator1;
    private final Collaborator2 collaborator2;
    private final Collaborator2 collaborator3;

    TestThis(Collaborator1 collaborator1, Collaborator2 collaborator2, Collaborator3 collaborator3) {
        this.collaborator1 = collaborator1;
        this.collaborator2 = collaborator2;
        this.collaborator3 = collaborator3;
    }

    public Login login(String username) {
        User user = collaborator1.getUser(username);
        collaborator2.notify(user);
        return collaborator3.login(user);
    }

}

…我对3个合作者使用严格的模拟来测试登录名(用户名)。我不认为永远不应该使用严格的mock。

我有一个简单的约定:

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

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

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

    数据访问接口如下所示:

    public Student GetStudentById(int id);
    public IList<Student> GetStudents(int ageFilter, int classId);
    
    public Student GetStudentById(int-id);
    公共IList 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.
    
    }
    
    公共学生查找学生(int id)
    {
    //学生依赖性注入
    return StudentDAL.GetStudentById(id);
    //使用strict mock来测试这一点
    }
    公共IList GetStudentsForClass(StudentListRequest StudentListRequest)
    {
    //学生依赖性注入
    //年龄筛选器从请求派生,然后传递到底层
    int ageFilter=DateTime.Now.Year-studentListRequest.DateOfBirthFilter.Year;
    return StudentDAL.GetStudents(ageFilter,studentListRequest.ClassId)
    //使用松散模拟并使用MOQ验证api,以确保正确传递老化过滤器。
    }
    
    您在哪里指定它是严格的模拟?@Sandbox:您可以在中指定严格或非严格。默认行为(当不指定
    MockBehavior
    时)似乎不严格(他们称之为“松散”)。我认为最好在答案中包含Mark的注释;关于如何创建严格与非严格的解释,以及提到别名“loose”。我想知道严格与非严格是否与“严格单元测试”和“loose(blackbox)单元测试”之间的区别相同在“如何避免方法更改功能,然后由于所有单元测试仍然通过而没有任何测试验证此新功能的问题?”中介绍了@DanielLorenz如果要更改功能,请先编写一个失败的测试;这是TDD周期的核心部分,无论您使用的是哪种模拟。如果你指的是m