简体   繁体   English

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

[英]How does application layer unit tests look like in DDD?

In my job we are writing web services, which are called by an app. 在我的工作中,我们正在编写由应用程序调用的Web服务。 We are working in agile mind set using domain driven design. 我们正在使用域驱动设计以敏捷的思维方式进行工作。 As in DDD we have domain and application layer. 与DDD中一样,我们具有域和应用程序层。 However, we encountered problem in writing unit tests for those layers, because it seems that we are testing domain logic twice: in domain unit tests and again in application unit tests: 但是,在为这些层编写单元测试时,我们遇到了问题,因为似乎我们在域单元测试和应用程序单元测试中两次测试了域逻辑:

Application unit test 应用单元测试

    [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));
    }

Domain unit test 域单元测试

    [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);
    }

As you can see these both tests checks whether user balance is 0, which is domain responsibility. 如您所见,这两个测试都检查用户余额是否为0,这是域责任。 Thus the question is: how application layer unit tests should look like and what it should test? 因此,问题是:应用程序层单元测试应该看起来如何,应该测试什么? Somewhere I read that unit tests should test in "application services for flow control and domain models for business rules". 我读过某个地方,单元测试应该在“用于流控制的应用程序服务和用于业务规则的域模型”中进行测试。 Could someone elaborate more and give some examples what application layer unit tests should test and look like? 有人可以详细说明并举例说明应测试什么样的应用程序层单元测试吗?

App Service Unit Tests 应用服务单元测试

The responsibilities of app services include input validation, security and transaction control. 应用程序服务的职责包括输入验证,安全性和事务控制。 So this is what you should test! 所以这是您应该测试的!

Here are some example questions that app service unit tests should provide and answer for: 以下是应用服务单元测试应提供的一些示例问题,并回答:

Does my app service... 我的应用程式服务...

  • behave correctly (eg return the expected error) when I pass in garbage? 传递垃圾时行为正确(例如返回预期的错误)?
  • allow only admins to access it? 只允许管理员访问它?
  • correctly commit the transaction in the success case? 在成功案例中正确提交交易?

Depending on how exactly you implement these aspects it may or may not make sense to test them. 根据您实现这些方面的精确程度,测试它们可能有意义也可能没有意义。 Security, for example, is often implemented in a declarative style (eg with C# attributes). 例如,安全性通常以声明性的方式实现(例如,具有C#属性)。 In that case you may find a code review approach more appropriate than checking the security attributes of each and every app service with a unit test. 在这种情况下,您可能会发现一种代码审查方法比通过单元测试检查每个应用程序服务的安全属性更合适。 But YMMV. 但是YMMV。

Also, make sure your unit tests are actual unit tests, ie stub or mock everything (especially domain objects). 另外,请确保您的单元测试是实际的单元测试,即对所有内容(尤其是域对象)进行存根或模拟。 It's not clear in your test that this is the case (see side note below). 在测试中尚不清楚是这种情况(请参见下面的旁注)。

Testing Strategies for App Services in General 一般应用服务的测试策略

Having unit tests for app services is a good thing. 对应用程序服务进行单元测试是一件好事。 However, on the app service level, I find integration tests to be more valuable in the long run. 但是,在应用程序服务级别上,从长远来看,我发现集成测试更有价值。 So I typically suggest the following combined strategy for testing app services: 因此,我通常建议以下组合策略来测试应用程序服务:

  1. Create unit tests for the good and bad cases (TDD style if you want to). 为好的情况和坏的情况创建单元测试(如果需要,可以使用TDD样式)。 Input validation is important, so don't skip the bad cases. 输入验证很重要,因此请不要跳过不良情况。
  2. Create an integration test for the good case. 为好的情况创建一个集成测试。
  3. Create additional integration tests if required. 如果需要,请创建其他集成测试。

Side Note 边注

Your unit tests contain a few code smells. 您的单元测试包含一些代码气味。

For example, I always instantiate the SUT (system under test) directly in unit tests. 例如,我总是在单元测试中直接实例化SUT(被测系统)。 Like that, you exactly know what dependencies it has, and which of them are stubbed, mocked, or the real one is used. 这样,您便确切知道它具有哪些依赖项,并且对其中的哪些项进行了存根,模拟或使用了真正的依赖项。 In your tests, this is not at all clear. 在您的测试中,这一点还不清楚。

Also, you seem to depend on fields for collecting the test output ( this._balances for example). 另外,您似乎依赖于字段来收集测试输出(例如this._balances )。 While this is usually not a problem if the test class contains only a single test, it can be problematic otherwise. 如果测试类仅包含一个测试,通常这不是问题,但否则可能会出现问题。 By depending on fields, you depend on state that is "external" to the test method. 通过依赖字段,您可以依赖测试方法“外部”的状态。 This can make the test method difficult to understand, because you can't just read through the test method, you need to consider the whole class. 这可能会使测试方法难以理解,因为您不能只是通读测试方法,而是需要考虑整个类。 This is the same problem that occurs when over-using setup and tear-down methods. 这是过度使用设置和拆卸方法时发生的相同问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM