结构化测试代码

本文关键字:代码 测试 结构化 | 更新日期: 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团队(不喜欢共享的每个测试设置。实际上,这是关于找到适合你和你的团队的方法。。。