Axon Framework官方文档(八)

8. Testing

CQRS最大的好处之一,特别是事件源的好处在于,它可以完全通过事件和命令来表达测试。无论是功能组件、事件和命令,都对领域专家或业务所有者有明确的含义。这不仅意味着用事件和命令表示的测试具有清晰的功能含义,也意味着它们几乎不依赖于任何实现选项。
本章所描述的特性要求通过配置maven依赖项来获得axon模块(使用<artifactId>axon-test</artifactId>和< scope>test</scope >)或从完整的包下载中获得。
本章描述的fixture与任何测试框架一起工作,例如JUnit和TestNG。

8.1 Command Component Testing(命令组件测试)

在任何CQRS基础架构中,命令处理组件通常是最复杂的。比其他组件更复杂,这也意味着该组件有额外的与测试相关的需求。
虽然更复杂,但命令处理组件的API相当简单。它有一个命令进来,然后发生了事件。在某些情况下,可能会有一个查询作为命令执行的一部分。除此之外,命令和事件是API的唯一部分。这意味着可以在事件和命令的基础上完整地定义测试场景。一般来说,形状是:
• given过去的某些事件,
• when 执行这个命令,
• expect 这些事件将被发布和/或存储
Axon框架提供了一个测试fixture,允许您精确地做到这一点。AggregateTestFixture允许您配置一个特定的基础结构,由必要的命令处理程序和存储库组成,并以given-when-then形式的事件和命令来表达你的场景。
下面的例子展示了given-when-then方式,用JUnit 4测试fixture的用法:
public class MyCommandComponentTest {
    private FixtureConfiguration fixture;

    @Before
    public void setUp() {
        fixture = new AggregateTestFixture(MyAggregate.class);
    }

    @Test
    public void testFirstFixture() {
        fixture.given(new MyEvent(1))
               .when(new TestCommand())
               .expectSuccessfulHandlerExecution()
               .expectEvents(new MyEvent(2));
        /*
这四行定义了实际的场景和它的预期结果。第一行定义发生在过去的事件。这些事件定义了测试中聚合的状态。 实际上,这些是事件存储在加载聚合时返回的事件。第二行定义了我们希望在系统上执行的命令。最后,我们还有两种定义预期行为的方法。在我们的样例程序中,使用推荐的void返回类型。最后一个方法定义了作为命令执行的结果,我们期望有一个事件产出。
        /*
    }
}
given-when-then测试fixture定义了三个阶段:配置、执行和验证。每个阶段由不同的接口表示:分别是,FixtureConfiguration, TestExecutor 和 ResultValidator。Fixture类上的静态newGivenWhenThenFixture()方法提供了对其中的第一个类的引用,从而可以提供验证器,等等。
Note:
为了更好地利用这些阶段之间的迁移,最好使用这些方法提供的流式接口,如上面的示例所示。

在配置阶段(即在提供第一个“given”之前),您提供执行测试所需的构建块。作为fixture的一部分提供事件总线、命令总线和事件存储的专用版本。有一些访问器方法来获得对它们的引用。任何命令处理程序不在聚合上直接注册,需要显式地使用registerAnnotatedCommandHandler方法配置。除了带注解的命令处理程序之外,您还可以配置各种各样的组件和设置,以定义如何设置测试的基础设施。
一旦配置了fixture,您就可以定义“jiven”事件了。测试fixture将把这些事件包装为DomainEventMessage。如果“given”事件实现了Message,则该消息的有效负载和元数据将包含在DomainEventMessage中,否则将使用给定的事件作为有效负载。DomainEventMessage的序列号是顺序的,从0开始。
或者,您也可以为“given”场提供命令。在这种情况下,当在测试中执行实际的命令时,这些命令生成的事件将被用于事件源。使用“givenCommands(…)“提供命令对象的。

执行阶段允许您提供命令处理组件执行的命令。被调用的处理程序(无论是在聚合的还是作为外部处理程序)的行为被监控,并与验证阶段注册的预计结果相比较。
Note:
在测试的执行过程中,Axon试图检测在测试的聚合中存在的任何非法状态变化。它通过将命令执行后的聚合状态与聚合的状态进行比较,如果它来自所有“given”和存储的事件。如果该状态不相同,这意味着状态更改发生在聚合的事件处理程序方法之外。在比较中,static和transient 字段被忽略,因为它们通常包含对资源的引用。
可以使用setReportIllegalStateChange方法在fixture的配置中切换检测。


最后一个阶段是验证阶段,允许你检查命令处理组件的活动。这完全是根据返回值和事件来完成的。
测试fixture允许您验证命令处理程序的返回值。您可以显式地定义预期的返回值,或者简单地要求方法成功返回。您还可以表示您期望CommandHandler抛出的任何异常。
另一个组件是已发布事件的验证。有两种匹配预期事件的方法。
第一是通过事件实例,它需要与实际的事件是行逐字的比较。将预期事件的所有属性与实际事件中的对应对象进行比较(使用equals())。如果其中一个属性不相等,则测试失败,并生成一个广泛的错误报告。
表达期望的另一种方式是使用的匹配器(Hamcrest库提供的)。匹配器接口规定了两个方法matches(Object)和describeTo(Description)。第一个返回一个布尔值,指示是否匹配或不匹配。第二个让你表达你的期望。例如,一个“GreaterThanTwoMatcher”可以添加“任何值大于2的事件“的描述。描述允许创建关于测试用例失败的错误消息.
创建事件列表的匹配器可能是繁琐和容易出错的工作。为了简化问题,Axon提供了一组匹配器允许你提供一组特定于事件的匹配器,并告诉Axon应该如何匹配列表。
下面是可用的事件列表匹配器和他们的目的的概述:
>List with all of: Matchers.listWithAllOf(event matchers...) 
如果所有提供的事件都匹配在实际事件列表中的至少一个事件,这个matcher将会成功。不管是否有多个匹配器匹配相同的事件,或如果列表中一个事件不匹配任何匹配器。
>List with any of: Matchers.listWithAnyOf(event matchers...)
如果一个或多个事件匹配器与实际的事件列表中一个或多个事件匹配,该匹配器将成功。一些匹配器甚至一个也不匹配,而另一个匹配多个。
>Sequence of Events: Matchers.sequenceOf(event matchers...)
使用此匹配器来验证实际事件匹配器和提供的事件匹配器有相同的顺序。如果匹配器与后一个事件相匹配,与前一个匹配器匹配的事件相匹配,该匹配器将成功。这意味着可能出现不匹配事件的“gaps”。
>Exact sequence of Events: Matchers.exactSequenceOf(event matchers...)
“事件的序列”匹配器的变化不允许不匹配事件的空隙。这意味着每个匹配器必须与事件后面的事件相匹配,与前一个匹配器匹配的事件相匹配。每个匹配器都应该与它前一个匹配器相对应的事件的后续一个事件相匹配.




为了方便起见,提供了一些普遍需要的事件匹配器。他们与单个事件实例相匹配:
>Equal Event: Matchers.equalTo(instance...)
验证given对象在语义上等于given事件,这个匹配器将比较实际和预期的对象的所有字段的值使用一个null-safe相等方法。这意味着可以比较事件,即使它们不实现equals方法。存储在given参数字段上的对象用equals进行比较,要求他们正确实现。
>No More Events: Matchers.andNoMore() or Matchers.nothing()
仅与空值匹配,这个匹配器可以作为最后一个匹配器添加到事件的准确顺序匹配器,以确保没有不匹配的事件依然存在。

由于匹配器传递一个事件消息列表,有时你只是想验证消息的有效负载。有匹配器来帮助你:
>Payload Matching: Matchers.messageWithPayload(payload matcher)
验证消息的有效负载匹配给定的有效载荷匹配器。
>Payloads Matching: Matchers.payloadsMatching(list matcher)
验证消息的有效负载匹配给定的有效载荷匹配器。给定的匹配器必须匹配列表包含的每个消息的有效负载。有效负载匹配匹配器通常用作外匹配器,以防止重复有效负载匹配器。
下面是一个简单的代码示例,以显示这些匹配器的使用。在这个例子中,我们预期共有两个事件发布。第一个事件必须是一个“ThirdEvent”,第二个是“aFourthEventWithSomeSpecialThings”。可能没有第三个事件,因为那样"andNoMore"匹配器会失败。
fixture.given(new FirstEvent(), new SecondEvent())
       .when(new DoSomethingCommand("aggregateId"))
       .expectEventsMatching(exactSequenceOf(
           // we can match against the payload only:
           messageWithPayload(equalTo(new ThirdEvent())),
           // this will match against a Message
           aFourthEventWithSomeSpecialThings(),
           // this will ensure that there are no more events
           andNoMore()
       ));

// or if we prefer to match on payloads only:
       .expectEventsMatching(payloadsMatching(
               exactSequenceOf(
                   // we only have payloads, so we can equalTo directly
                   equalTo(new ThirdEvent()),
                   // now, this matcher matches against the payload too
                   aFourthEventWithSomeSpecialThings(),
                   // this still requires that there is no more events
                   andNoMore()
               )
       ));
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值