繁体   English   中英

具有模拟存储库依赖关系的单元测试类

[英]Unit testing classes with mocked repository dependecies

我有几种方法的服务类。 服务对存储库有一些依赖性。 我正在使用Moq模拟存储库。 我对方法之一的正确单元测试有疑问。 我举一个例子:

public interface IRepository<T>
{
    IEnumerable<T> FindAll();
    IEnumerable<T> FindByQuery(Predicate<T> predicate);
    //many other methods for retreiving T's
}

public class MyService
{
    private readonly IRepository<Category> _repo;

    public MyService(IRepository<Category> repo)
    {
        _repo = repo;
    }

    public List<Category> FindActiveCategories()
    {
        return _repo.FindAll().Where(x => x.Active).ToList();
    }
}

现在,我编写了一个单元测试:

public FindActiveCategories_WhenCalled_ShouldReturnActiveCategories() {
   var moq = new Mock<IRepository<Category>>();
   var list = new List<Category>
   {
       new Category {Active = true},
       new Category {Active = false}
   };
   moq.Setup(x => x.FindAll()).Returns(list);
   var service = new MyService(moq.Object);
   var result = service.FindActiveCategories();

   Assert.IsTrue(result.All(x=>x.Active));
}

测试当然通过了。 但是,我意识到我使用FindAll检索了服务方法中的所有类别-这显然是要纠正的事情,因为我不想将数千个类别加载到内存中只是为了拉出其中的几个类别。 所以我将FindActiveCategories方法的实现更改为:

public List<Category> FindActiveCategories()
{
    return _repo.FindByQuery(x => x.Active).ToList();
}

这次我的测试失败了。 问题很明显-测试取决于实施细节。 我知道FindActiveCategories方法使用存储库的FindAll方法,因此我为此方法编写了一个设置。 更改实现后,我必须更改测试方法的实现-这似乎是一个问题。 当然,我可以设置所有的Find ...方法,但是存储库中有很多方法,并且可以选择其中的许多方法,这对我来说也不是正确的方法。 更不用说TDD了-如果我想先编写测试,我将不知道什么以及如何模拟存储库接口。 我的问题是:什么是处理这种依赖关系的正确方法,以便能够编写独立于实现的单元测试。

尽管有这个问题的标题,但这里的实际问题似乎是“如果我的代码实现发生更改,是否需要更改测试?”

答案是,如果实现完全包含在要测试的代码中,则为“否”。 例如,如果我有一个将两个值相乘的方法,并且以明显的方式实现了该方法,那么我可以编写测试以证明其有效。 现在,如果我更改实现以使用for循环进行计算并在循环内累加总计,则所有测试都应通过且未做任何更改。

问题在于我们编写的大多数代码都不是这样:它取决于其他事情。 如果被测代码依赖于其他事物,我们有两种选择:进行集成测试或模拟依赖关系。

  • 集成测试为被测类提供实际使用时将要使用的实际依赖关系。 这是最有价值的测试,因为它是完全现实的。 答案也将是“如果更改实现,则无需更改测试”。 但是,这通常是不切实际的,因为它成倍地增加了编写和维护测试的成本。
  • 模拟要求您指定依赖项的行为方式,因此根据定义,您的测试必须知道要使用的代码以及如何使用。 因此,如果您更改实现,则应该合理地期望必须更改测试,因此答案将是“是”。

但是,请考虑这一点。 与模拟无关的测试部分无需更改。 您没有使用Arrange Act Assert模式(这可以使测试更具可读性,尤其是在模拟时); 但是Act和Assert部分(这将是测试的最后两行)不需要更改。 也许这会让您相信您仍在进行TDD。

但不要因此而失眠:还有其他一些事情可以使您的注意力更多地受益。 例如,将方法的实现更改为...

public List<Category> FindActiveCategories()
{
    return new List<Category>();
}

...即使代码不会执行您想要的操作,测试也会通过(因为断言中的All对于空列表返回true)。

希望这是有用的。

以下将适用于上述更改。

[TestClass]
public class MyServiceShould {
    [TestMethod]
    public void FindActiveCategories_WhenCalled_ShouldReturnActiveCategories() {
        //Arrange
        var moq = new Mock<IRepository<Category>>();
        var list = new List<Category> {
            new Category {Active = true},
            new Category {Active = false}
        };
        moq
            .Setup(x => x.FindByQuery(It.IsAny<Predicate<Category>>()))
            .Returns((Predicate<Category> predicate) => list.Where(x => predicate(x)));
        var service = new MyService(moq.Object);

        //Act
        var result = service.FindActiveCategories();

        //Assert
        Assert.IsTrue(result.All(x => x.Active));
    }
}

该模拟采用传递的谓词并将其应用于假集合,以使测试能够完成。

参考Moq快速入门 ,以更好地了解如何使用模拟框架。

暂无
暂无

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

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