应用层单元测试在DDD中的外观如何?

时间:2015-12-28 08:59:49

标签: c# unit-testing tdd domain-driven-design application-layer

在我的工作中,我们正在编写由应用程序调用的Web服务。我们正在使用域驱动设计进行敏捷思维设置。与在DDD中一样,我们有域和应用层。但是,我们在为这些层编写单元测试时遇到了问题,因为我们似乎正在测试域逻辑两次:在域单元测试中,再次在应用程序单元测试中:

申请单元测试

    [TestMethod]
    public void UserApplicationService_SignOut_ForExistingUserWithBalance_ShouldClearBalanceAndSignOut()
    {
        //Arrange
        long merchantId = 1;
        long userId = 1;

        var transactionId = "001";
        var id = "122";            
        var user = Help.SetId<User>(User.Register(id, new DateTime(2015, 01, 01, 00, 00, 01)), userId);

        _usersDb.Add(user);
        var userBonusBalanceRepository = _testContext.MoqUnitOfWork.MockUnitOfWork.Object.GetUserBonusAccountRepository();

        UserBonusAccount uba = userBonusBalanceRepository.GetForUser(user);
        uba.PayTo(
            new Domain.Core.Money { TotalAmount = 10, BonusAmount = 0 },
            new Domain.Core.Outlet
            {
                BonusPercentage = 50,
                IsLoyalty = true,
                Id = id,
                OutletId = "111"
            },
            transactionId,
            DateTime.Now);
        userBonusBalanceRepository.Update(uba);          

        //Act
        _testContext.UserApplicationService.SignOut(id);

        //Assert
        var firstOrDefault = this._balances.FirstOrDefault(x => x.UserId == user.Id && x.MerchantId == merchantId);
        Assert.IsTrue(firstOrDefault != null && firstOrDefault.Balance == 0);
        Assert.IsNotNull(this._transactions.Where(x => x.Id == transactionId && x.Type == BonusTransactionType.EraseFunds));
    }

域单元测试

    [TestMethod]
    public void UserBonusAccount_ClearBalances_shouldClearBalancesForAllMerchants()
    {
        long userId = 1;
        long firstMerchantId = 1;
        long secondMerchantId = 2;
        User user = User.Register("111", new DateTime(2015, 01, 01, 00, 00, 01));
        Shared.Help.SetId(user, userId);
        List<BonusTransaction> transactions = new List<BonusTransaction>();
        List<BonusBalance> balances = new List<BonusBalance>();

        var userBonusAccount = UserBonusAccount.Load(transactions.AsQueryable(), balances.AsQueryable(), user);

        userBonusAccount.PayTo(new Money {TotalAmount = 100, BonusAmount = 0},
            new Outlet
            {
                BonusPercentage = 10,
                IsLoyalty = true,
                MerchantId = firstMerchantId,
                OutletId = "4512345678"
            }, "001", DateTime.Now);

        userBonusAccount.PayTo(new Money {TotalAmount = 200, BonusAmount = 0},
            new Outlet
            {
                BonusPercentage = 10,
                IsLoyalty = true,
                MerchantId = secondMerchantId,
                OutletId = "4512345679"
            }, "002", DateTime.Now);

        userBonusAccount.ClearBalances();

        Assert.IsTrue(userBonusAccount.GetBalanceAt(firstMerchantId) == 0);
        Assert.IsTrue(userBonusAccount.GetBalanceAt(secondMerchantId) == 0);
    }

正如您所看到的,这两个测试都会检查用户余额是否为0,这是域责任。因此问题是:应用程序层单元测试应该如何以及它应该测试什么?在某处,我读到单元测试应该在“流程控制的应用程序服务和业务规则的域模型”中进行测试。有人可以详细说明并给出一些应用层单元测试应该测试和看起来像的例子吗?

1 个答案:

答案 0 :(得分:6)

应用服务单元测试

应用服务的职责包括输入验证,安全性和事务控制。所以这就是你应该测试的东西!

以下是应用服务单元测试应提供并回答的一些示例问题:

我的应用服务......

    当我传入垃圾时,
  • 表现正确(例如,返回预期的错误)?
  • 只允许管理员访问它?
  • 在成功案例中正确提交交易吗?

根据您实际执行这些方面的准确程度,测试它们可能有也可能没有意义。例如,安全性通常以声明式样式实现(例如,使用C#属性)。在这种情况下,您可能会发现代码审查方法比使用单元测试检查每个应用程序服务的安全属性更合适。但是YMMV。

另外,确保您的单元测试是实际的单元测试,即存根或模拟所有内容(尤其是域对象)。在您的测试中并不清楚是这种情况(见下面的旁注)。

一般的应用服务测试策略

对应用服务进行单元测试是一件好事。但是,在应用程序服务级别上,我发现集成测试从长远来看更有价值。因此,我通常会建议以下用于测试应用服务的组合策略:

  1. 为好的和坏的情况创建单元测试(如果您愿意,可以使用TDD样式)。输入验证非常重要,因此请不要跳过不良案例。
  2. 为好案例创建集成测试。
  3. 根据需要创建其他集成测试。
  4. 旁注

    您的单元测试包含一些代码味道。

    例如,我总是在单元测试中直接实例化SUT(被测系统)。就像那样,你完全知道它有哪些依赖关系,以及哪些是存根,模拟或真实存在。在你的测试中,这一点都不清楚。

    此外,您似乎依赖于收集测试输出的字段(例如this._balances)。虽然如果测试类只包含一个测试,这通常不是问题,否则可能会有问题。依靠字段,你依赖于&#34;外部&#34;对测试方法。这可能使测试方法难以理解,因为您无法通读测试方法,您需要考虑整个类。这与过度使用设置和拆卸方法时出现的问题相同。