TDD不只是写测试:我是如何用‘测试先行’思维设计出一个更灵活的支付领域模型的
2026/5/8 6:16:40 网站建设 项目流程

TDD不只是写测试:我是如何用‘测试先行’思维设计出一个更灵活的支付领域模型的

当大多数开发者谈论测试驱动开发(TDD)时,第一反应往往是"先写测试能减少bug"。但在我参与设计一个支持信用卡、电子钱包和优惠券组合支付的金融系统时,TDD给我的最大惊喜是:它成了最严格的设计评审工具。每次在测试用例中写下assertPaymentSplitCorrectly()这样的断言时,都像在回答"这个对象到底该对谁负责"的灵魂拷问。

1. 从失败的测试用例开始:拆解支付场景

在传统开发模式中,我们可能会直接创建一个PaymentService类,然后在里面堆砌各种processCreditCard()applyCoupon()方法。但TDD要求我们换一种思考方式——先描述"正确的结果应该长什么样"。

1.1 第一个红测试:混合支付的金额分摊

@Test void should_split_amount_when_combine_payment_methods() { Payment payment = new Payment(Money.of(100)); payment.apply(new Coupon("FESTIVAL", Money.of(20))); payment.select(new CreditCard("4111111111111111")); PaymentResult result = payment.confirm(); assertEquals(Money.of(80), result.getPaidByCard()); assertEquals(Money.of(20), result.getDiscountByCoupon()); }

这个初始测试暴露了三个关键设计问题:

  1. 值对象缺失:金额计算需要Money类型而非原始BigDecimal
  2. 职责模糊:优惠券抵扣应该由Coupon还是Payment处理?
  3. 结果反馈:支付结果需要结构化返回而非简单返回布尔值

1.2 测试驱动的领域概念澄清

通过不断让测试失败-通过-重构的循环,我们逐渐厘清了核心领域对象:

对象类型职责边界TDD催生的设计决策
Payment支付主聚合根维护支付状态,协调子对象交互
Money值对象封装货币运算和四舍五入规则
Coupon领域实体自行验证有效期和计算抵扣金额
PaymentRule领域服务处理跨境支付等复杂业务规则

2. 红-绿-重构循环中的模型演进

2.1 第二周期:支付方式的选择策略

当测试用例扩展到支持电子钱包时,我们发现初始设计存在严重缺陷:

// 反例:支付方式处理硬编码在聚合根中 public class Payment { public void select(CreditCard card) { /*...*/ } public void select(EWallet wallet) { /*...*/ } // 每新增方式都要修改 }

通过以下重构步骤实现开闭原则:

  1. 引入PaymentMethod接口
  2. 定义PaymentStrategy值对象
  3. 将支付方式决策移出聚合根
// 重构后的选择逻辑 payment.select(PaymentStrategy.of( new CreditCard("4111..."), new Coupon("SUMMER20") ));

2.2 测试保护下的激进重构

当需要支持"部分金额用A方式支付,剩余用B方式"的复杂场景时,我们在测试覆盖率保护下进行了两次关键重构:

  1. 拆分支付阶段

    graph TD A[初始化支付] --> B[应用优惠] B --> C[选择支付策略] C --> D[执行金额分配]
  2. 引入规则引擎

    public interface PaymentRule { boolean canApply(PaymentContext context); PaymentResult execute(PaymentContext context); } // 测试用例验证规则优先级 @Test void should_apply_high_priority_rule_first() { PaymentRule rule1 = new CrossBorderRule(); PaymentRule rule2 = new BlackFridayRule(); PaymentProcessor processor = new PaymentProcessor(List.of(rule1, rule2)); PaymentResult result = processor.process(payment); // 断言规则执行顺序 }

3. DDD模式在测试驱动下的自然浮现

3.1 测试用例催生的限界上下文

当测试覆盖率达到一定阶段后,我们注意到支付核心逻辑与风控逻辑的测试经常同时失败。这提示我们需要明确限界上下文:

// 支付上下文 @Test void should_decline_when_risk_score_exceeds_threshold() { Payment payment = createValidPayment(); when(riskService.evaluate(any())).thenReturn(RiskLevel.HIGH); assertThrows(RiskRejectedException.class, () -> payment.confirm()); } // 风控上下文 @Test void should_calculate_risk_based_on_payment_attributes() { RiskAssessment assessment = riskAssessor.assess( paymentContext.getAmount(), paymentContext.getUserProfile() ); assertTrue(assessment.getScore() > 0); }

3.2 测试数据构建的工厂模式演进

随着测试复杂度提升,我们经历了三种测试数据构造方式:

  1. 原始构造器(初期):

    Coupon coupon = new Coupon("TEST", Money.of(10), LocalDate.now().plusDays(1));
  2. 测试建造者(中期):

    Coupon coupon = CouponBuilder.new() .withCode("SPRING20") .withDiscount(Money.of(20)) .validForDays(30) .build();
  3. 领域语意化工厂(后期):

    Coupon coupon = Coupons.percentageOff(20) .expireIn(30, DAYS) .generate();

4. 组合支付的领域模型最终形态

经过上百次红绿重构循环后,最终的领域模型呈现出清晰的职责分层:

4.1 核心聚合关系

public class Payment { private PaymentId id; private Money amount; private List<PaymentLine> lines; private PaymentStatus status; public void apply(Coupon coupon) { this.lines.add(coupon.createDeductionLine()); } public PaymentResult confirm() { validate(); return PaymentSplitter.split(this); } }

4.2 支付金额分摊算法

public class PaymentSplitter { public static PaymentResult split(Payment payment) { return payment.getLines().stream() .collect(Collectors.groupingBy( PaymentLine::getType, Collectors.reducing(Money.ZERO, PaymentLine::getAmount, Money::add) )); } }

4.3 异常处理设计

测试驱动的异常处理策略:

@Test void should_throw_when_apply_expired_coupon() { Coupon coupon = Coupons.fixedAmount(10) .expiredSince(1, DAYS) .generate(); Payment payment = new Payment(Money.of(100)); assertThrows(CouponExpiredException.class, () -> payment.apply(coupon)); }

在项目上线后的三个月里,这个支付核心领域模型支撑了7种新支付方式的快速接入。最让我意外的是,当初那些为设计而写的测试用例,在团队新人熟悉系统时成了最好的领域字典——每个测试用例都像是一个具体业务场景的规范说明。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询