构建测试代码

时间:2015-07-02 14:11:48

标签: c# unit-testing xunit nsubstitute

目前,我们正在尝试对我们的服务实施一些单元测试。在以下服务中,创建订单并对订单的创建进行审核注册。在编写这两个测试时(因为我们认为测试应该分开进行以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>();
    }
}

有没有办法为我们的单元测试获得更好的代码库?

1 个答案:

答案 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)不喜欢共享每个测试设置。真的是找到适合你和你的团队的方法......