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

时间:2017-06-09 04:39:09

标签: c# unit-testing

我试图为某些课程编写测试。我想测试的类使用不同的存储库来从数据库中获取和保存东西。

简化示例

library(plyr);library(dplyr)
library(googleVis)

df <- data.frame(Language = structure(c(rep("English",7), rep("German",5), rep("French", 10)), class = "character"),
                 Students = c(LETTERS[1:7], LETTERS[1:5], LETTERS[1:10]), 
                 Start = structure(c(16713,16713,16713,16744,16713,16714,16754,16729,16729,16729,16750,16769,
                                     16724,16724,16745,16724,16759,16766,16723,16722,16736,16796), class = "Date"), 
                 End = structure(c(16762,16720,16762,16755,16720,16764,16762,16765,16765,16749,16761,16770,16758,
                                   16744,16758,16764,16765,16766,16726,16723,16758,16806), class = "Date"))

ddply(df, .(Language), summarise,
      FirstDay = min(Start),
      LastDay = max(End), 
      Duration = LastDay - FirstDay)

plot(gvisTimeline(data=df, rowlabel = "Class", start = "Start", end = "End", options=list(width=600, height=1000) ))

我想要做的是确保保存到数据库中的内容是正确的。

所以我有这样的单元测试:

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);      
  }
}

我认为这很好,但后来我正在阅读更多内容并且我读到你不应该断言,我认为我在断言中做了什么?我从存根中获取信息,然后在结果上使用Assert?

所以我不确定这是否是正确的测试方法。如果没有,为什么以及正确的方法是什么?

3 个答案:

答案 0 :(得分:1)

  

不建议您测试保存的内容   数据库是正确的。它更多的是集成测试而不是单元   测试,它超出了你的范围。

可能有很多原因导致数据无法在数据库中正确保存,但是您的类的行为应该是正常的,例如网络问题,但是失败的UT将不正确。

相反,您应该测试您的类,以了解数据库插入失败时类的行为方式,或者是否发生某些异常。然后使用您的模拟框架来模拟具有这些期望的存储库,并相应地在您的类的测试中断言。

一些好的读数herehere

答案 1 :(得分:1)

您不应该测试正在实施单元测试的类中使用的另一个模块/类的逻辑。

您应该只测试/验证是否正在调用对该模块的调用,正如您的业务逻辑所说的那样。您应该只关注该类的业务逻辑,仅用于编写单元测试用例。

在您的情况下,IGroupRepository是另一个模块/类,而不是您正在编写单元测试的模块/类。相反,如果您想验证数据是否正在保存,那么您应该将其包含在您为IGroupRepository实现的另一个测试用例中。

但是,不建议编写用于保存数据的单元测试,并将其视为集成测试的一部分。

答案 2 :(得分:1)

是的,你是对的,测试的编写方式有问题。实际上,您没有测试MyClass。你正在测试那个 你使用的模拟框架是有效的。作为证明,请注释使用MyClass的两行并再次运行测试。它仍然会通过:

[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);
}

通常帮助我编写单元测试,首先问自己我正在测试的行为。在这种特殊情况下,我相信你想在调用MyClass.Execute时进行测试, 您希望在更改此人姓名的情况下调用IGroupRepository.Save

在我熟悉的(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";
    });

你使用的模拟框架中应该有类似的东西。