简体   繁体   English

使用存根和模拟进行单元测试

[英]Unit testing with stubs and mocks

I'm trying to write tests for some classes. 我正在尝试为某些类编写测试。 The classes I want to test use different repositories to to get and save things from my database. 我想测试的类使用不同的存储库来从我的数据库中获取和保存东西。

A simplified example 一个简化的例子

public class MyClass
{
    private readonly IGroupRepository _groupRepo;

  public MyClass(IGroupRepository groupRepo){ 
    _groupRepo = groupRepo; 
  }

  public void Execute(PersonInfo personInfo, string id){
  var group = _groupRepo.GetById(id);

  var person = group.Persons.First(p=> p.Id == personInfo.Id);

  person.FirstName = personInfo.FirstName;
  person.LastName = personInfo.LastName;

  _groupRepo.Save(group);      
  }
}

What I want to do is make sure that what is saved into the db is correct. 我想要做的是确保保存到数据库中的内容是正确的。

So I have a unit test like this: 所以我有这样的单元测试:

[TestMethod]
public void TestMethod(){
    var groupId = "ABC";
    var personId = 1;

    ver personInfo = new PersonInfo()
    {
      Id = personId,
      FirstName = "Sam",
      LastName = "Smith"  
    }

    var groupStub = new Mock<IGroupRepository>;
    groupStub.Setup(x=> x.GetById(groupId)).Returns(new Group(){
            Id = groupId,
            Persons = List<Person>()
            {
                new Person()
                {
                    Id = personId,
                    FirstName = "George",
                    LastName = "Bolton",
                }
            }
        }
    });

    var myClass = new MyClass();
    myClass.Execute(personInfo, groupId);

    var group = groupStub.GetById(groupId);
    var person = group.Persons.First(p=> p.Id == personId);

    Assert.AreEqual(personInfo.FirstName, person.FirstName);
}

I thought this was fine, but then I was reading more up on it and I read that you are not supposed to assert on a sub, which I think I'm doing in my assert statement? 我认为这很好,但后来我正在阅读更多内容并且我读到你不应该断言sub,我认为我在断言中做了什么? I'm getting info from the stub, then using Assert on the result? 我从存根获取信息,然后在结果上使用Assert?

So I'm not sure if this is the correct way to test. 所以我不确定这是否是正确的测试方法。 If not, why and what is the correct way? 如果没有,为什么以及正确的方法是什么?

It is not recommended that you should be testing what is saved into the database is correct. 不建议您测试保存到数据库中的内容是否正确。 It is more of integration testing than unit testing, and it goes beyond the scope of you class. 它更多的是集成测试而不是单元测试,它超出了您的课程范围。

There could be many reasons for data not getting saved properly in db, but your class is behaving as it should be, for example network issue, but then failing your UT will not be correct. 可能有很多原因导致数据无法在数据库中正确保存,但是您的类的行为应该是应该的,例如网络问题,但是然后失败您的UT将是不正确的。

Instead you should test your class for scenarios where how your class behaves when database insertions fails, or if some exception occurs. 相反,您应该测试您的类,以了解数据库插入失败时类的行为方式,或者是否发生某些异常。 And then use your mocking framework to mock repository with those expectations and assert accordingly in tests of your class. 然后使用您的模拟框架来模拟具有这些期望的存储库,并在您的类的测试中相应地断言。

Some good readings here and here 这里这里有一些很好的阅读

You should not test the logic of another module/class that is being used in the class you are implementing Unit test. 您不应该测试正在实现单元测试的类中使用的另一个模块/类的逻辑。

You should only test/verify that the call to that module is being invoked or not as your business logic says. 您应该只测试/验证是否正在调用对该模块的调用,就像业务逻辑所说的那样。 You should only concentrate on the Business logic of that class only for what you are writing unit testcases. 您应该只关注该类的业务逻辑,仅用于编写单元测试用例。

Here in your case IGroupRepository is another module/class and not the one for which you are writing unit tests. 在您的情况下,IGroupRepository是另一个模块/类,而不是您正在编写单元测试的模块/类。 Instead if you want to verify that data is being saved or not then you should include that in another test case which you are implementing for IGroupRepository. 相反,如果您要验证数据是否正在保存,那么您应该将其包含在您为IGroupRepository实现的另一个测试用例中。

However writing unit tests for data saving is not recommended and it is considered as part of integration test. 但是,不建议编写用于数据保存的单元测试,它被视为集成测试的一部分。

Yes, you are correct that there is something wrong with how the test is written. 是的,你是对的,测试的编写方式有问题。 In fact, you are not testing MyClass . 实际上,您没有测试MyClass You are instead testing that the mocking framework you use works. 您正在测试您使用的模拟框架是否有效。 As proof, comment the two lines that use MyClass and run the test again. 作为证明,请注释使用MyClass的两行并再次运行测试。 It will still pass: 它仍将通过:

[TestMethod]
public void TestMethod()
{
    var groupId = "ABC";
    var personId = 1;

    ver personInfo = new PersonInfo()
    {
      Id = personId,
      FirstName = "Sam",
      LastName = "Smith"  `
    }

    var groupStub = new Mock<IGroupRepository>;
    groupStub.Setup(x=> x.GetById(groupId)).Returns(new Group(){
            Id = groupId,
            Persons = List<Person>()
            {
                new Person()
                {
                    Id = personId,
                    FirstName = "George",
                    LastName = "Bolton",
                }
            }
        }
    });

    // var myClass = new MyClass();
    // myClass.Execute(personInfo, groupId);

    var group = groupStub.GetById(groupId);
    var person = group.Persons.First(p=> p.Id == personId);

    Assert.AreEqual(personInfo.FirstName, person.FirstName);
}

What generally helps me in writing unit tests, is asking myself first what behaviour I am really testing. 通常帮助我编写单元测试的是,首先问自己我真正测试的是什么行为。 In this particular case, I believe you want to test that when calling MyClass.Execute , you expect that IGroupRepository.Save is called with the person's name changed. 在这种特殊情况下,我相信你想测试一下,当调用MyClass.Execute ,你希望调用IGroupRepository.Save并更改人名。

Writing that assertion in the mocking framework I am familiar with (NSubstitute), looks like this: 在我熟悉的模拟框架(NSubstitute)中编写该断言,如下所示:

groupStub.Received().Save(
    Arg.Is<Group>(group =>
    {
        return groupId.Id == groupId && 
            group.Persons[0].Id == personId &&
            group.Persons[0].FirstName == "Sam" &&
            group.Persons[0].LastName == "Smith";
    });

There should be something similar in the mocking framework you use. 在您使用的模拟框架中应该有类似的东西。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM