目前,我们正在尝试对我们的服务实施一些单元测试。在以下服务中,创建订单并对订单的创建进行审核注册。在编写这两个测试时(因为我们认为测试应该分开进行以1个职责进行测试)这是我开始的地方:
public class TestPacklineOrderManagementService
{
[Fact]
public void CreateNewProductWhenNoPacklineOrderIsAvailable()
{
IPackLineOrderRepository packLineOrderRepository = Substitute.For<IPackLineOrderRepository>();
packLineOrderRepository.GetActive(Arg.Any<PackLine>()).Returns(x => null);
var rawProductRepository = Substitute.For<IRawProductRepository>();
rawProductRepository.Get(1).Returns(new RawProduct {Id = 1});
var packlineRepository = Substitute.For<IPackLineRepository>();
packlineRepository.Get(1).Returns(new PackLine {Id = 1});
var auditRegistrationService = Substitute.For<IAuditRegistrationService>();
var packlineOrderManagementService = new PacklineOrderManagementService(packLineOrderRepository, rawProductRepository, packlineRepository, auditRegistrationService);
packlineOrderManagementService.SetProduct(1,1);
packLineOrderRepository.Received()
.Insert(Arg.Is<PackLineOrder>(x => x.PackLine.Id == 1 && x.Product.Id == 1));
}
[Fact]
public void AuditCreateNewProductWhenNoPacklineOrderIsAvailable()
{
IPackLineOrderRepository packLineOrderRepository = Substitute.For<IPackLineOrderRepository>();
packLineOrderRepository.GetActive(Arg.Any<PackLine>()).Returns(x=>null);
var rawProductRepository = Substitute.For<IRawProductRepository>();
rawProductRepository.Get(1).Returns(new RawProduct { Id = 1 });
var packlineRepository = Substitute.For<IPackLineRepository>();
packlineRepository.Get(1).Returns(new PackLine { Id = 1 });
var auditRegistrationService = Substitute.For<IAuditRegistrationService>();
var packlineOrderManagementService = new PacklineOrderManagementService(packLineOrderRepository, rawProductRepository, packlineRepository, auditRegistrationService);
packlineOrderManagementService.SetProduct(1, 1);
auditRegistrationService.Received()
.Audit(Arg.Is<PackLineOrderAudit>(item => item.Action == PackLineOrderAction.CreatePacklineOrder));
}
}
你可以看到很多重复的代码。为了防止这种情况,我尝试重构这个,结果如下:
public class TestPacklineOrderManagementService2
{
[Fact]
public void CreateNewProductWhenNoPacklineOrderIsAvailable()
{
IPackLineOrderRepository packLineOrderRepository;
IAuditRegistrationService auditRegistrationService;
var packlineOrderManagementService = BuilderForCreateNewProductWhenNoPacklineOrderIsAvailable(out packLineOrderRepository, out auditRegistrationService);
packlineOrderManagementService.SetProduct(1,1);
packLineOrderRepository.Received().Insert(Arg.Any<PackLineOrder>());
}
[Fact]
public void AuditCreateNewProductWhenNoPacklineOrderIsAvailable()
{
IPackLineOrderRepository packLineOrderRepository;
IAuditRegistrationService auditRegistrationService;
var packlineOrderManagementService = BuilderForCreateNewProductWhenNoPacklineOrderIsAvailable(out packLineOrderRepository, out auditRegistrationService);
packlineOrderManagementService.SetProduct(1, 1);
auditRegistrationService.Received()
.Audit(Arg.Is<PackLineOrderAudit>(item => item.Action == PackLineOrderAction.CreatePacklineOrder));
}
private PacklineOrderManagementService BuilderForCreateNewProductWhenNoPacklineOrderIsAvailable(out IPackLineOrderRepository packLineOrderRepository,
out IAuditRegistrationService auditRegistrationService)
{
packLineOrderRepository = CreatePackLineOrderRepository(x => null);
auditRegistrationService = CreateAuditRegistrationService();
var rawProductRepository = CreateRawProductRepository(x => new RawProduct { Id = 1 });
var packlineRepository = CreatePacklineRepository(x => new PackLine { Id = 1 });
var packlineOrderManagementService = new PacklineOrderManagementService(packLineOrderRepository,
rawProductRepository, packlineRepository, auditRegistrationService);
return packlineOrderManagementService;
}
private IPackLineOrderRepository CreatePackLineOrderRepository(Func<CallInfo, PackLineOrder> getActiveResult)
{
IPackLineOrderRepository packLineOrderRepository = Substitute.For<IPackLineOrderRepository>();
packLineOrderRepository.GetActive(Arg.Any<PackLine>()).Returns(getActiveResult);
return packLineOrderRepository;
}
private IRawProductRepository CreateRawProductRepository(Func<CallInfo, RawProduct> getResult)
{
IRawProductRepository rawProductRepository = Substitute.For<IRawProductRepository>();
rawProductRepository.Get(1).Returns(getResult);
return rawProductRepository;
}
private IPackLineRepository CreatePacklineRepository(Func<CallInfo, PackLine> getResult)
{
IPackLineRepository packLineRepository = Substitute.For<IPackLineRepository>();
packLineRepository.Get(1).Returns(getResult);
return packLineRepository;
}
private IAuditRegistrationService CreateAuditRegistrationService()
{
return Substitute.For<IAuditRegistrationService>();
}
}
有没有办法为我们的单元测试获得更好的代码库?
答案 0 :(得分:1)
更好是非常主观的,它在很大程度上取决于你如何定义它。有些人可能认为你的第一个例子更好,因为你的测试中所有的设置代码都在一起。我确实根据你上面的代码得到了一些反馈......
当您编写测试时,不要对被测系统(SUT)的两个参数使用相同的值,除非它们真的相同,它会隐藏转置错误。所以,在你的测试中,你正在建立一个这样的替代品:
rawProductRepository.Get(1).Returns(new RawProduct {Id = 1});
然后打电话给你的SUT:
packlineOrderManagementService.SetProduct(1,1);
SUT调用中的1是否与存储库设置有关?根本不清楚哪一个是......
这有点主观,但如果您的测试设置完全相同,您是否真的需要使用不同的断言复制测试?如果Audit
没有等,{em>
如果您确实有一组具有类似设置的测试,那么您可以将公共位推送到类构造函数中。您还可以使用嵌套类来组织测试,如下所示:
Insert
这种方法使您的测试更简洁,更容易理解,如果它们只包含测试特定信息,但它更好吗?这是非常主观的,有些人(including the xunit team)不喜欢共享每个测试设置。真的是找到适合你和你的团队的方法......