结构化测试代码
本文关键字:代码 测试 结构化 | 更新日期: 2023-09-27 18:00:57
目前我们正在尝试对我们的服务进行一些单元测试。在下面的服务中,将创建一个订单,并对订单的创建进行审计注册。在编写这两个测试时(因为我们认为测试应该分开,以获得一个责任的测试(,这是我开始的地方:
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>();
}
}
有什么方法可以为我们的单元测试获得更好的代码库吗?
Better是非常主观的,这在很大程度上取决于你如何定义它。有些人可能会认为你的第一个例子更好,因为所有的设置代码都在测试中。不过,我确实有一些基于你上面代码的反馈。。。
在编写测试时,不要对被测系统(SUT(的两个参数使用相同的值,除非它们真的相同,否则会隐藏换位错误。所以,在你的测试中,你要设置一个这样的替代品:
rawProductRepository.Get(1).Returns(new RawProduct {Id = 1});
然后拨打SUT:
packlineOrderManagementService.SetProduct(1,1);
SUT调用中的1是否与存储库设置有关?根本不清楚哪个1是哪个。。。
这有点主观,但如果你的测试设置完全相同,你真的需要用不同的断言来复制测试吗?如果Insert
没有等等,那么Audit
的发生真的有意义吗?
如果您确实有具有类似设置的测试组,那么您可以将公共位推送到类构造函数中。你也可以使用嵌套类来组织测试,比如
public class TestPacklineOrderManagementService
{
public class TestSetProduct {
IPackLineOrderRepository _packLineOrderRepository;
IRawProductRepository _rawProductRepository;
// etc
public TestSetProduct() {
_packLineOrderRepository = Substitute.For<IPackLineOrderRepository>();
_packLineOrderRepository.GetActive(Arg.Any<PackLine>()).Returns(x => null);
_rawProductRepository = Substitute.For<IRawProductRepository>();
// etc
}
[Fact]
public void CreateNewProductWhenNoPacklineOrderIsAvailable()
{
// Any test specific setup...
_packlineOrderManagementService.SetProduct(1,1);
_packLineOrderRepository.Received()
.Insert(Arg.Is<PackLineOrder>(x => x.PackLine.Id == 1
&& x.Product.Id == 1));
}
[Fact]
public void AuditCreateNewProductWhenNoPacklineOrderIsAvailable()
{
_packlineOrderManagementService.SetProduct(1, 1);
_auditRegistrationService.Received()
.Audit(Arg.Is<PackLineOrderAudit>(item =>
item.Action == PackLineOrderAction.CreatePacklineOrder));
}
}
public class TestSomeOtherScenario {
// tests...
}
}
如果测试只包含特定于测试的信息,这种方法会使测试更简洁、更容易遵循,但它更好吗?这是非常主观的,有些人(包括xunit团队(不喜欢共享的每个测试设置。实际上,这是关于找到适合你和你的团队的方法。。。