什么测试/模拟?

时间:2013-09-03 22:39:09

标签: c# unit-testing mocking moq xunit

给定以下简单服务类,在GetCategories()方法中,您应该测试调用categoryRepository.Query()方法的事实,还是应该设置一个保存类别列表的测试返回那些?

我想我所说的是嘲笑categoryRepository并验证一次覆盖此测试用例时调用了Query方法?

public class CategoryService : ValidatingServiceBase, ICategoryService
{
    private readonly IRepository<Category> categoryRepository;
    private readonly IRepository<SubCategory> subCategoryRepository;
    private readonly IValidationService validationService;

    public CategoryService(
        IRepository<Category> categoryRepository,
        IRepository<SubCategory> subCategoryRepository,
        IValidationService validationService)
        : base(validationService)
    {
        this.categoryRepository = categoryRepository;
        this.subCategoryRepository = subCategoryRepository;
        this.validationService = validationService;
    }

    public IEnumerable<Category> GetCategories()
    {
        return categoryRepository.Query().ToList();
    }
}

样本测试

[Fact]
public void GetCategories_Should_CallRepositoryQuery()
{
    var categoryRepo = new Mock<IRepository<Category>>();
    var service = new CategoryService(categoryRepo.Object, null, null);

    service.GetCategories();

    categoryRepo.Verify(x => x.Query(), Times.Once());
}

3 个答案:

答案 0 :(得分:4)

没关系。在这两种情况下(模拟+行为验证 vs 存根+断言),您实现了完全相同的结果,并且需要完全相同的内部细节级别上课的运作。坚持你认为在特定场景中更适合的那个。


您发布的单元测试是行为验证的一个示例。您不断言任何值,而是检查是否调用了某个方法。当方法调用没有可见结果(考虑记录)或不返回任何值(显然)时,这尤其有用。它当然有缺点,特别是当您对返回值的方法进行此类验证时,并且不检查它(就像您的情况一样 - 我们将会得到它)。

存根和断言方法使用协作者来生成价值。它不会检查方法是否被调用(至少不是直接调用,但是当您设置存根并且该设置有效时会执行此类测试),而是依赖于正确的存根值流。

让我们举一个简单的例子。假设您测试了类PizzaFactory.GetPizza的方法,如下所示:

public Pizza GetPizza()
{
    var dough = doughFactory.GetDough();
    var cheese = ingredientsFactory.GetCheese();
    var pizza = oven.Bake(dough, cheese);
    return pizza;
}

通过行为验证,您可以检查doughFactory.GetDough是否已被调用,然后是ingredientsFactory.GetCheese,最后是oven.Bake。如果确实做过这样的电话,你就会假设披萨已经创建了。您不会检查您的工厂是否返回披萨,但假设所有流程的步骤都已完成,则会发生这种情况。你已经可以看到我之前提到的那个缺点 - 我可以调用所有正确的方法但是返回别的东西,比如说:

var dough = doughFactory.GetDough();
var cheese = ingredientsFactory.GetCheese();
var pizza = oven.Bake(dough, cheese);
return garbageBin.FindPizza();

不是您订购的披萨?请注意,对协作者的所有正确调用都是按照我们的假设发生的。

使用存根+断言方法,它看起来都很相似,除了验证你有存根。您使用早期协作者生成的值来存储以后的协作者(如果您错误地面团奶酪,烤箱将不会返回我们想要的披萨)。最终值是您的方法返回的值,这就是我们断言的内容:

doughFactoryStub.Setup(df => dg.GetDough).Return("thick");
ingredientsFactoryStub.Setup(if => if.GetCheese()).Return("double");
var expectedPizza = new Pizza { Name = "Margherita" };
ovenStub.Setup(o => o.Bake("thick", "double")).Return(expectedPizza);

var actualPizza = pizzaFactory.GetPizza();

Assert.That(actualPizza, Is.EqualTo(expectedPizza));

如果过程的任何部分失败(比如doughFactory返回普通面团),那么最终值将会不同,测试将失败。

再一次,在我看来,在你的例子中,使用哪种方法无关紧要。在任何正常环境中,两种方法都将验证相同的内容,并且需要相同级别的实现知识。为了更加安全,您可能希望使用存根+断言方法,以防有人为您提供垃圾桶 1 < / SUP>。但如果发生这种情况,单元测试是你的最后一个问题。


1 但请注意,这可能不是故意的(特别是在考虑复杂方法时)。

答案 1 :(得分:1)

是的,那就是方式。

mockCategoryRepository.Setup(r => r.Query()).Returns(categories)
var actualCategories = new CategoryService(mockCategoryRepository, mock..).GetCategories();
CollectionAssert.AreEquivalent(categories, actualCategories.ToList());

它看起来与Moq和NUnit类似。

答案 2 :(得分:1)

您所呈现的是白盒测试 - 这种方法在单元测试中也是可行的,但建议仅用于简单方法。

在Sruti提供的答案中,服务以黑盒的方式进行测试。关于内部方法的知识仅用于准备测试,但是您不验证该方法是被调用一次,10次,还是根本没有被调用。就个人而言,我只验证方法调用,以验证是否正确使用了一些必须存根的外部API(例如:发送电子邮件)。通常只要它产生正确的结果就足以不关心方法的工作原理。

使用黑盒测试,代码和测试更易于维护。对于白盒测试,在重构类时,一些内部结构的大多数更改通常必须遵循更改测试代码。在黑盒方法中,您可以更自由地重新排列所有内容,并且仍然可以确保界面的外部行为没有发生变化。