测试/模拟什么
本文关键字:什么 模拟 测试 | 更新日期: 2023-09-27 18:30:08
给定以下简单的服务类,在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());
}
这没关系。在这两种情况下(mock+行为验证与stub+断言),您可以实现完全相同的结果,并且需要关于类内部工作的完全相同级别的详细信息。在给定的场景中,坚持选择你认为更适合的那个。
你发布的单元测试就是行为验证的一个例子。您不断言任何值,而是检查是否调用了某个方法。当方法调用没有可见的结果(考虑日志记录)或没有返回任何值(显然)时,这一点尤其有用。当然,它也有缺点,尤其是当你对do返回值的方法进行验证时,不要检查它(就像你的情况一样,我们会处理它)。
存根和断言方法使用合作者来产生价值。它不检查是否调用了方法(至少不是直接调用,但这样的测试是在设置存根时执行的,并且该设置有效),而是依赖于存根值的正确流。
让我们举一个简单的例子。假设您测试类的一个方法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();
不是你点的披萨?请注意,所有对合作者的正确调用都发生在我们假设的情况下。
使用stub+assert的方法,除了没有验证您是否有存根之外,一切看起来都很相似。您使用早期合作者生成的值来拦截后期合作者(如果您不知何故弄错了面团或奶酪,烤箱将不会返回我们想要的披萨)。最后的值是您的方法返回的值,这就是我们所断言的:
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
返回正常面团),那么最终值将不同,测试将失败。
再一次,在我看来,在你的例子中,你使用哪种方法并不重要。在任何正常的环境中,这两种方法都将验证相同的东西,并且需要相同级别的实现知识。为了更加安全,您可能更喜欢使用stub+assert方法,以防有人给您放置1垃圾箱。但如果发生这种情况,单元测试就是最后的问题。
1请注意,这可能不是故意的(尤其是在考虑复杂方法时)。
是的,就是这样。
mockCategoryRepository.Setup(r => r.Query()).Returns(categories)
var actualCategories = new CategoryService(mockCategoryRepository, mock..).GetCategories();
CollectionAssert.AreEquivalent(categories, actualCategories.ToList());
Moq和NUnit的情况会类似。
您介绍的是一个白盒测试,这种方法在单元测试中也是可行的,但仅推荐用于简单的方法。
在Sruti给出的答案中,该服务是在黑盒意义上进行测试的。关于内部方法的知识仅用于准备测试,但您无法验证该方法是否被调用了一次、10次或根本没有被调用。就我个人而言,我验证方法调用只是为了验证某些必须存根的外部API是否正确使用(例如:发送电子邮件)。通常,只要能产生正确的结果,不关心一个方法是如何工作的就足够了。
使用黑盒测试,代码和测试更易于维护。对于白盒测试,在类的重构过程中,某些内部结构的大多数更改通常必须在更改测试代码之后进行。在黑盒方法中,您可以更自由地重新排列所有内容,并且仍然可以确保界面的外部行为没有改变。