如果 ID 是随机生成的,如何测试聚合?
How Can I Test Aggregate if ID is randomly generated?
这可能更像是一个设计问题,但我有一个通过命令生成的聚合成员,需要能够测试事件是否已生成,因为命令是 运行。
但是,我没有看到任何明显的方法可以在 TestFixture 框架中对事件的一个字段进行 anyString
匹配。
是不是"bad practice"创建时在聚合中生成ID?是否应该在聚合之外生成 ID?
@AggregateMember(eventForwardingMode = ForwardMatchingInstances.class)
private List<TimeCardEntry> timeCardEntries = new ArrayList<>();
data class ClockInCommand(@TargetAggregateIdentifier val employeeName: String)
@CommandHandler
public TimeCard(ClockInCommand cmd) {
apply(new ClockInEvent(cmd.getEmployeeName(),
GenericEventMessage.clock.instant(),
UUID.randomUUID().toString()));
@EventSourcingHandler
public void on(ClockInEvent event) {
this.employeeName = event.getEmployeeName();
timeCardEntries.add(new TimeCardEntry(event.getTimeCardEntryId(), event.getTime()));
}
@Data
public class TimeCardEntry {
@EntityId
private final String timeCardEntryId;
private final Instant clockInTime;
private Instant clockOutTime;
@EventSourcingHandler
public void on(ClockOutEvent event) {
this.clockOutTime = event.getTime();
}
private boolean isClockedIn() {
return clockOutTime != null;
}
}
@ParameterizedTest
@MethodSource(value = "randomEmployeeName")
void testClockInCommand(String employeeName) {
testFixture.givenNoPriorActivity()
.when(new ClockInCommand(employeeName))
.expectEvents(new ClockInEvent(employeeName, testFixture.currentTime(), "Any-String-Works"));
}
Is it "bad practice" to generate IDs in the aggregate when created? Should IDs be generated outside of the aggregate?
随机数很像时钟 - 它们是共享可变状态的一种形式。换句话说,它们是 imperative shell 的关注点,而不是功能核心的关注点。
对于您的域模型,这通常意味着随机性作为参数传入,而不是由聚合本身产生。这可能意味着将 ID 生成器传递给域模型,或者甚至在应用程序中生成 id 并传递生成的标识符 as a value.
因此,在我们的单元测试中,我们将目标应用程序提供的随机生成器替换为测试提供的 "random" 生成器——因为测试控制生成器,所使用的标识符变得确定,并且因此您可以更轻松地解决它。
如果您不满意将随机生成器作为域模型 api 的一部分,另一种选择是将其公开为测试界面的一部分。
// We don't necessarily worry about testing this version, it is "too simple to break"
void doSomethingCool(...) {
doSomethingCool(ID.new, ...);
}
// Unit tests measure this function instead, which is easier to test and has
// all of the complicated logic
void doSomethingCool(ID id, ...) {
// ...
}
这可能更像是一个设计问题,但我有一个通过命令生成的聚合成员,需要能够测试事件是否已生成,因为命令是 运行。
但是,我没有看到任何明显的方法可以在 TestFixture 框架中对事件的一个字段进行 anyString
匹配。
是不是"bad practice"创建时在聚合中生成ID?是否应该在聚合之外生成 ID?
@AggregateMember(eventForwardingMode = ForwardMatchingInstances.class)
private List<TimeCardEntry> timeCardEntries = new ArrayList<>();
data class ClockInCommand(@TargetAggregateIdentifier val employeeName: String)
@CommandHandler
public TimeCard(ClockInCommand cmd) {
apply(new ClockInEvent(cmd.getEmployeeName(),
GenericEventMessage.clock.instant(),
UUID.randomUUID().toString()));
@EventSourcingHandler
public void on(ClockInEvent event) {
this.employeeName = event.getEmployeeName();
timeCardEntries.add(new TimeCardEntry(event.getTimeCardEntryId(), event.getTime()));
}
@Data
public class TimeCardEntry {
@EntityId
private final String timeCardEntryId;
private final Instant clockInTime;
private Instant clockOutTime;
@EventSourcingHandler
public void on(ClockOutEvent event) {
this.clockOutTime = event.getTime();
}
private boolean isClockedIn() {
return clockOutTime != null;
}
}
@ParameterizedTest
@MethodSource(value = "randomEmployeeName")
void testClockInCommand(String employeeName) {
testFixture.givenNoPriorActivity()
.when(new ClockInCommand(employeeName))
.expectEvents(new ClockInEvent(employeeName, testFixture.currentTime(), "Any-String-Works"));
}
Is it "bad practice" to generate IDs in the aggregate when created? Should IDs be generated outside of the aggregate?
随机数很像时钟 - 它们是共享可变状态的一种形式。换句话说,它们是 imperative shell 的关注点,而不是功能核心的关注点。
对于您的域模型,这通常意味着随机性作为参数传入,而不是由聚合本身产生。这可能意味着将 ID 生成器传递给域模型,或者甚至在应用程序中生成 id 并传递生成的标识符 as a value.
因此,在我们的单元测试中,我们将目标应用程序提供的随机生成器替换为测试提供的 "random" 生成器——因为测试控制生成器,所使用的标识符变得确定,并且因此您可以更轻松地解决它。
如果您不满意将随机生成器作为域模型 api 的一部分,另一种选择是将其公开为测试界面的一部分。
// We don't necessarily worry about testing this version, it is "too simple to break"
void doSomethingCool(...) {
doSomethingCool(ID.new, ...);
}
// Unit tests measure this function instead, which is easier to test and has
// all of the complicated logic
void doSomethingCool(ID id, ...) {
// ...
}