一、前言

实际编码已经写了 2 篇了,在这过程中非常感谢有听到观点不同的声音,借着这个契机,今天这篇就把大家提出的建议一个个的过一遍,重新整理,重新出发,为了让接下去的 DDD 之路走的更好。

二、单元测试

蟋蟀兄在我的第三篇文章下面指出:

5 停下脚步,重新出发 - 图1

这点其实是我偷懒了,单元测试其实不单单在 DDD 中是一个很重要的一环,在我们崇尚敏捷,快速迭代的大背景下,有良好的单元测试模块可以保证快速迭代下的项目质量。有甚至可以使用测试先行的 TDD 模式。

单元测试的好处我就不多说了,那么现在开始在项目中增加单元测试。

单元测试有多种命名方式,我个人的方式是给每一个对象单独建立一个测试类,然后里面每个单元测试方法的命名规则为” 方法名条件预期的结果” 这样子。

那么根据我们之前的 Cart 和 CartItem 的建模,编写的单元测试如下:

  1. [TestClass]
  2. public class CartTest
  3. {
  4. [TestMethod]
  5. [ExpectedException(typeof(ArgumentException))]
  6. public void Constructor_CartIdDefault_ThrowArgumentException()
  7. {
  8. var cart = new Cart(default(Guid), Guid.NewGuid(), DateTime.Now);
  9. Assert.AreNotEqual(null, cart);
  10. }
  11. [TestMethod]
  12. [ExpectedException(typeof(ArgumentException))]
  13. public void Constructor_UserIdDefault_ThrowArgumentException()
  14. {
  15. var cart = new Cart(Guid.NewGuid(), default(Guid), DateTime.Now);
  16. Assert.AreNotEqual(null, cart);
  17. }
  18. [TestMethod]
  19. [ExpectedException(typeof(ArgumentException))]
  20. public void Constructor_LastChangeTimeDefault_ThrowArgumentException()
  21. {
  22. var cart = new Cart(Guid.NewGuid(), Guid.NewGuid(), default(DateTime));
  23. Assert.AreNotEqual(null, cart);
  24. }
  25. [TestMethod]
  26. public void AddCartItem_NotExisted_TotalItemCountIsIncreased()
  27. {
  28. var cart = new Cart(Guid.NewGuid(), Guid.NewGuid(), DateTime.Now);
  29. cart.AddCartItem(new CartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100));
  30. Assert.AreEqual(1, cart.TotalItemCount());
  31. cart.AddCartItem(new CartItem(new Guid("22222222-2222-2222-2222-222222222222"), 1, 100));
  32. Assert.AreEqual(2, cart.TotalItemCount());
  33. }
  34. [TestMethod]
  35. public void AddCartItem_Existed_TotalItemCountIsNotIncreasedTotalItemNumIsIncreased()
  36. {
  37. var cart = new Cart(Guid.NewGuid(), Guid.NewGuid(), DateTime.Now);
  38. cart.AddCartItem(new CartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100));
  39. Assert.AreEqual(1, cart.TotalItemCount());
  40. Assert.AreEqual(1, cart.TotalItemNum());
  41. cart.AddCartItem(new CartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100));
  42. Assert.AreEqual(1, cart.TotalItemCount());
  43. Assert.AreEqual(2, cart.TotalItemNum());
  44. }
  45. }
  46. [TestClass]
  47. public class CartItemTest
  48. {
  49. [TestMethod]
  50. [ExpectedException(typeof(ArgumentException))]
  51. public void ModifyQuantity_LessZero_ThrowArgumentException()
  52. {
  53. var cartItem = new CartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100);
  54. cartItem.ModifyQuantity(-1);
  55. }
  56. [TestMethod]
  57. [ExpectedException(typeof(ArgumentException))]
  58. public void ModifyQuantity_EqualsZero_ThrowArgumentException()
  59. {
  60. var cartItem = new CartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100);
  61. cartItem.ModifyQuantity(0);
  62. }
  63. [TestMethod]
  64. public void ModifyQuantity_MoreZero_Success()
  65. {
  66. var cartItem = new CartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100);
  67. cartItem.ModifyQuantity(10);
  68. Assert.AreEqual(10, cartItem.Quantity);
  69. }
  70. [TestMethod]
  71. [ExpectedException(typeof(ArgumentException))]
  72. public void ModifyPrice_LessZero_ThrowArgumentException()
  73. {
  74. var cartItem = new CartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100);
  75. cartItem.ModifyPrice(-1);
  76. }
  77. [TestMethod]
  78. public void ModifyQuantity_EqualsZero_Success()
  79. {
  80. var cartItem = new CartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100);
  81. cartItem.ModifyQuantity(0);
  82. Assert.AreEqual(0, cartItem.Price);
  83. }
  84. [TestMethod]
  85. public void ModifyQuantity_MoreZero_Success()
  86. {
  87. var cartItem = new CartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100);
  88. cartItem.ModifyQuantity(10);
  89. Assert.AreEqual(10, cartItem.Price);
  90. }
  91. }

三、纠正错误,重新出发

在写 CartItemTest 的时候发现了一个问题。领域对象的设计中有一个要点,就是实体必须需要通过其所属的聚合根才能访问,这样才能体现出聚合的的整体性,并且减少外界对聚合内部过多的了解。

而目前对于 CartItem 的运用却有些背道而驰的意思,由外部对象进行实例化,必然增加了外部调用方对整个购物项构造过程的了解。有一位园友tubo有提到这点。

5 停下脚步,重新出发 - 图2

我思考了下,觉得这位园友的建议是对的。他建议的改法恰恰能够满足这个要求,隐藏了构造 CartItem 实体的细节。

好了那先把 CartItem 的构造函数访问类型设置为 internal 吧,这样也只能在 CartItem 所在的 Domain 项目中进行实例化了,然后再修改 Cart.AddCartItem 方法的参数。变为如下:

  1. public void AddCartItem(Guid productId, int quantity, decimal price)
  2. {
  3. var cartItem = new CartItem(productId, quantity, price);
  4. var existedCartItem = this._cartItems.FirstOrDefault(ent => ent.ProductId == cartItem.ProductId);
  5. if (existedCartItem == null)
  6. {
  7. this._cartItems.Add(cartItem);
  8. }
  9. else
  10. {
  11. existedCartItem.ModifyPrice(cartItem.Price);
  12. existedCartItem.ModifyQuantity(existedCartItem.Quantity + cartItem.Quantity);
  13. }
  14. }

单元测试也做出相应的更改:

  1. [TestClass]
  2. public class CartTest
  3. {
  4. [TestMethod]
  5. [ExpectedException(typeof(ArgumentException))]
  6. public void Constructor_CartIdDefault_ThrowArgumentException()
  7. {
  8. var cart = new Cart(default(Guid), Guid.NewGuid(), DateTime.Now);
  9. Assert.AreNotEqual(null, cart);
  10. }
  11. [TestMethod]
  12. [ExpectedException(typeof(ArgumentException))]
  13. public void Constructor_UserIdDefault_ThrowArgumentException()
  14. {
  15. var cart = new Cart(Guid.NewGuid(), default(Guid), DateTime.Now);
  16. Assert.AreNotEqual(null, cart);
  17. }
  18. [TestMethod]
  19. [ExpectedException(typeof(ArgumentException))]
  20. public void Constructor_LastChangeTimeDefault_ThrowArgumentException()
  21. {
  22. var cart = new Cart(Guid.NewGuid(), Guid.NewGuid(), default(DateTime));
  23. Assert.AreNotEqual(null, cart);
  24. }
  25. [TestMethod]
  26. public void AddCartItem_NotExisted_TotalItemCountIsIncreased()
  27. {
  28. var cart = new Cart(Guid.NewGuid(), Guid.NewGuid(), DateTime.Now);
  29. cart.AddCartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100);
  30. Assert.AreEqual(1, cart.TotalItemCount());
  31. cart.AddCartItem(new Guid("22222222-2222-2222-2222-222222222222"), 1, 100);
  32. Assert.AreEqual(2, cart.TotalItemCount());
  33. }
  34. [TestMethod]
  35. public void AddCartItem_Existed_TotalItemCountIsNotIncreasedTotalItemNumIsIncreased()
  36. {
  37. var cart = new Cart(Guid.NewGuid(), Guid.NewGuid(), DateTime.Now);
  38. cart.AddCartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100);
  39. Assert.AreEqual(1, cart.TotalItemCount());
  40. Assert.AreEqual(1, cart.TotalItemNum());
  41. cart.AddCartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100);
  42. Assert.AreEqual(1, cart.TotalItemCount());
  43. Assert.AreEqual(2, cart.TotalItemNum());
  44. }
  45. }
  46. [TestClass]
  47. public class CartItemTest
  48. {
  49. [TestMethod]
  50. [ExpectedException(typeof(ArgumentException))]
  51. public void ModifyQuantity_LessZero_ThrowArgumentException()
  52. {
  53. var cart = new Cart(Guid.NewGuid(), Guid.NewGuid(), DateTime.Now);
  54. cart.AddCartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100);
  55. var cartItem = cart.GetCartItem(new Guid("11111111-1111-1111-1111-111111111111"));
  56. Assert.AreNotEqual(null, cartItem);
  57. Assert.AreEqual(1, cartItem.Quantity);
  58. cartItem.ModifyQuantity(-1);
  59. }
  60. [TestMethod]
  61. [ExpectedException(typeof(ArgumentException))]
  62. public void ModifyQuantity_EqualsZero_ThrowArgumentException()
  63. {
  64. var cart = new Cart(Guid.NewGuid(), Guid.NewGuid(), DateTime.Now);
  65. cart.AddCartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100);
  66. var cartItem = cart.GetCartItem(new Guid("11111111-1111-1111-1111-111111111111"));
  67. Assert.AreNotEqual(null, cartItem);
  68. Assert.AreEqual(1, cartItem.Quantity);
  69. cartItem.ModifyQuantity(0);
  70. }
  71. [TestMethod]
  72. public void ModifyQuantity_MoreZero_Success()
  73. {
  74. var cart = new Cart(Guid.NewGuid(), Guid.NewGuid(), DateTime.Now);
  75. cart.AddCartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100);
  76. var cartItem = cart.GetCartItem(new Guid("11111111-1111-1111-1111-111111111111"));
  77. Assert.AreNotEqual(null, cartItem);
  78. Assert.AreEqual(1, cartItem.Quantity);
  79. cartItem.ModifyQuantity(10);
  80. Assert.AreEqual(10, cartItem.Quantity);
  81. }
  82. [TestMethod]
  83. [ExpectedException(typeof(ArgumentException))]
  84. public void ModifyPrice_LessZero_ThrowArgumentException()
  85. {
  86. var cart = new Cart(Guid.NewGuid(), Guid.NewGuid(), DateTime.Now);
  87. cart.AddCartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100);
  88. var cartItem = cart.GetCartItem(new Guid("11111111-1111-1111-1111-111111111111"));
  89. Assert.AreNotEqual(null, cartItem);
  90. Assert.AreEqual(100, cartItem.Price);
  91. cartItem.ModifyPrice(-1);
  92. }
  93. [TestMethod]
  94. public void ModifyPrice_EqualsZero_Success()
  95. {
  96. var cart = new Cart(Guid.NewGuid(), Guid.NewGuid(), DateTime.Now);
  97. cart.AddCartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100);
  98. var cartItem = cart.GetCartItem(new Guid("11111111-1111-1111-1111-111111111111"));
  99. Assert.AreNotEqual(null, cartItem);
  100. Assert.AreEqual(100, cartItem.Price);
  101. cartItem.ModifyPrice(0);
  102. Assert.AreEqual(0, cartItem.Price);
  103. }
  104. [TestMethod]
  105. public void ModifyPrice_MoreZero_Success()
  106. {
  107. var cart = new Cart(Guid.NewGuid(), Guid.NewGuid(), DateTime.Now);
  108. cart.AddCartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100);
  109. var cartItem = cart.GetCartItem(new Guid("11111111-1111-1111-1111-111111111111"));
  110. Assert.AreNotEqual(null, cartItem);
  111. Assert.AreEqual(100, cartItem.Price);
  112. cartItem.ModifyPrice(10);
  113. Assert.AreEqual(10, cartItem.Price);
  114. }
  115. }

这样一来,被玻璃鱼儿netfocus2 位园友所指出的奇怪的 “UserBuyProductDomainService” 也自然消失了。应用层代码变成:

  1. public Result Buy(Guid userId, Guid productId, int quantity)
  2. {
  3. var product = DomainRegistry.ProductService().GetProduct(productId);
  4. if (product == null)
  5. {
  6. return Result.Fail("对不起,未能获取产品信息请重试~");
  7. }
  8. var cart = _getUserCartDomainService.GetUserCart(userId);
  9. cart.AddCartItem(productId, quantity, product.SalePrice);
  10. DomainRegistry.CartRepository().Save(cart);
  11. return Result.Success();
  12. }

四、结语

DDD 的道路是坎坷的,我希望通过在园子里发布的文章能够结交到志同道合的 DDD 之友,欢迎大家不吝啬自己的见解,多多留言,也让想学习或者正在学习 DDD 的园友少走一些弯路。

本文的源码地址:https://github.com/ZacharyFan/DDDDemo/tree/Demo5


原创文章,转载请注明本文链接: https://zacharyfan.com/archives/141.html

关于作者:张帆(Zachary,个人微信号:Zachary-ZF)。坚持用心打磨每一篇高质量原创。欢迎扫描二维码~

5 停下脚步,重新出发 - 图3

定期发表原创内容:架构设计丨分布式系统丨产品丨运营丨一些思考。

如果你是初级程序员,想提升但不知道如何下手。又或者做程序员多年,陷入了一些瓶颈想拓宽一下视野。欢迎关注我的公众号「跨界架构师」,回复「技术」,送你一份我长期收集和整理的思维导图。

如果你是运营,面对不断变化的市场束手无策。又或者想了解主流的运营策略,以丰富自己的 “仓库”。欢迎关注我的公众号「跨界架构师」,回复「运营」,送你一份我长期收集和整理的思维导图。
https://zacharyfan.com/archives/141.html