给定以下简单服务类,在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());
}
答案 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(例如:发送电子邮件)。通常只要它产生正确的结果就足以不关心方法的工作原理。
使用黑盒测试,代码和测试更易于维护。对于白盒测试,在重构类时,一些内部结构的大多数更改通常必须遵循更改测试代码。在黑盒方法中,您可以更自由地重新排列所有内容,并且仍然可以确保界面的外部行为没有发生变化。