在我的工作中,我们正在编写由应用程序调用的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.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); Listtransactions = new List (); List balances = new List (); 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,这是域责任.因此问题是:应用程序层单元测试应该如何以及它应该测试什么?在某处,我读到单元测试应该在"流程控制的应用程序服务和业务规则的域模型"中进行测试.有人可以详细说明并举例说明应用层单元测试应该测试和看起来像什么?
应用服务的职责包括输入验证,安全性和事务控制.所以这就是你应该测试的!
以下是应用服务单元测试应提供和回答的一些示例问题:
我的应用程序服务...
当我传入垃圾时,表现正确(例如,返回预期的错误)?
只允许管理员访问它?
在成功案例中正确提交交易?
根据您实现这些方面的具体程度,测试它们可能有也可能没有意义.例如,安全性通常以声明式样式实现(例如,使用C#属性).在这种情况下,您可能会发现代码审查方法比使用单元测试检查每个应用程序服务的安全属性更合适.但是YMMV.
另外,确保您的单元测试是实际的单元测试,即存根或模拟所有内容(尤其是域对象).在您的测试中并不清楚这种情况(见下面的旁注).
对应用服务进行单元测试是一件好事.但是,在应用程序服务级别上,我发现集成测试从长远来看更有价值.因此,我通常会建议以下用于测试应用服务的组合策略:
为好的和坏的情况创建单元测试(如果你愿意,可以使用TDD样式).输入验证很重要,所以不要跳过坏的情况.
为好案例创建集成测试.
如果需要,创建其他集成测试.
您的单元测试包含一些代码味道.
例如,我总是直接在单元测试中实例化SUT(被测系统).就像那样,你完全知道它有哪些依赖关系,以及哪些是存根,模拟或真实的依赖.在你的测试中,这一点都不清楚.
此外,您似乎依赖于字段来收集测试输出(this._balances
例如).虽然如果测试类只包含一个测试,这通常不是问题,否则可能会有问题.通过依赖字段,您依赖于测试方法的"外部"状态.这可以使测试方法难以理解,因为您不能只通读测试方法,需要考虑整个类.这与过度使用设置和拆卸方法时出现的问题相同.