DDD测试策略
DDD测试策略
领域层单元测试
领域层测试聚焦于业务规则的正确性,不依赖外部基础设施。
// 聚合根测试
@ExtendWith(MockitoExtension.class)
class OrderTest {
@Test
void shouldCreateOrderSuccessfully() {
// Given
Long customerId = 1L;
List<OrderItem> items = List.of(
OrderItem.create("P001", 2, Money.of(99.99)),
OrderItem.create("P002", 1, Money.of(199.99))
);
// When
Order order = Order.create(customerId, items);
// Then
assertThat(order.getStatus()).isEqualTo(OrderStatus.CREATED);
assertThat(order.getItems()).hasSize(2);
assertThat(order.calculateTotal()).isEqualTo(Money.of(399.97));
}
@Test
void shouldRejectEmptyOrder() {
// Given
List<OrderItem> emptyItems = Collections.emptyList();
// When & Then
assertThatThrownBy(() -> Order.create(1L, emptyItems))
.isInstanceOf(EmptyOrderException.class)
.hasMessage("订单不能为空");
}
@Test
void shouldTransitionToPaidStatus() {
// Given
Order order = Order.create(1L, List.of(
OrderItem.create("P001", 1, Money.of(100))
));
// When
order.pay(Money.of(100));
// Then
assertThat(order.getStatus()).isEqualTo(OrderStatus.PAID);
assertThat(order.getDomainEvents()).hasSize(2);
assertThat(order.getDomainEvents().get(1))
.isInstanceOf(OrderPaidEvent.class);
}
@Test
void shouldRejectPaymentForShippedOrder() {
// Given
Order order = Order.create(1L, List.of(
OrderItem.create("P001", 1, Money.of(100))
));
order.pay(Money.of(100));
order.ship();
// When & Then
assertThatThrownBy(() -> order.pay(Money.of(100)))
.isInstanceOf(InvalidOrderStateException.class);
}
}
// 值对象测试
class MoneyTest {
@Test
void shouldAddMoneyWithSameCurrency() {
Money result = Money.of(100).add(Money.of(50));
assertThat(result).isEqualTo(Money.of(150));
}
@Test
void shouldRejectAdditionWithDifferentCurrency() {
assertThatThrownBy(() ->
Money.of(100, Currency.USD).add(Money.of(50, Currency.EUR)))
.isInstanceOf(CurrencyMismatchException.class);
}
}
应用层集成测试
应用层测试验证服务编排和事务管理。
@SpringBootTest
@Transactional
class OrderApplicationServiceTest {
@Autowired
private OrderApplicationService orderService;
@MockBean
private EventPublisher eventPublisher;
@Test
void shouldCreateOrderAndPublishEvent() {
// Given
CreateOrderCommand command = CreateOrderCommand.builder()
.customerId(1L)
.items(List.of(new OrderItemRequest("P001", 2)))
.build();
// When
OrderId orderId = orderService.createOrder(command);
// Then
Order order = orderRepository.findById(orderId.getValue()).orElseThrow();
assertThat(order.getStatus()).isEqualTo(OrderStatus.CREATED);
verify(eventPublisher).publish(argThat(event ->
event instanceof OrderCreatedEvent &&
((OrderCreatedEvent) event).getOrderId().equals(orderId)
));
}
@Test
void shouldReserveInventoryWhenOrderCreated() {
// Given
CreateOrderCommand command = CreateOrderCommand.builder()
.customerId(1L)
.items(List.of(new OrderItemRequest("P001", 2)))
.build();
// When
OrderId orderId = orderService.createOrder(command);
// Then
verify(inventoryClient).reserve(orderId.getValue(),
argThat(items -> items.size() == 1));
}
}
契约测试
契约测试验证服务间接口的兼容性。
// 消费者端契约测试
@ExtendWith(RestAssuredRestExtension.class)
class OrderServiceContractTest {
@TestTemplate
@ContextConfiguration(locations = "classpath:/consumer-context.xml")
void consumeOrderCreatedEvent(RestDocumentationContextProvider provider) {
RestAssured.given()
.spec(RestAssuredRestDocumentation.documentationSpecification(provider))
.when()
.body(new ClassPathResource("order-created-event.json"))
.post("/events")
.then()
.statusCode(200);
}
}
// 提供者端契约测试
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class OrderProviderContractTest {
@LocalServerPort
private int port;
@Test
void verifyOrderCreatedEventSchema() {
// 启动 Pact 验证服务器
PactVerificationServer server = new PactVerificationServer(port);
// 验证事件符合契约
server.verify(new ClassPathResource("pact/order-events.json"));
}
}
测试金字塔
/\
/ \ E2E 测试(少量)
/ \ 端到端业务流程
/──────\
/ \ 集成测试(适量)
/ \ 服务间交互、数据库操作
/────────────\
/ \ 单元测试(大量)
/ \ 领域逻辑、值对象
──────────────────
DDD 测试的核心原则是领域层测试为主,上层测试验证集成和流程。