AxonFramework測試

CQRS最大的好處之一,尤其是事件溯源就事件和命令而言,單純地表達測試是可能的。這兩個功能組件,事件和命令對領域專家或業務所有者都有明確的含義。這不僅意味着測試表達就事件和命令而言有明確的功能含義,這也意味着他們不依靠任何實現選擇。

本章描述的特性需要axon-test模塊,可通過配置maven依賴(使用axon-test 和test

本章中描述的固件可用於任何測試框架,如JUnit和TestNG。

命令組件測試

在任何CQRS基礎架構中命令處理組件通常是最複雜的。比其他組件更復雜,這也意味着該組件有額外的與測試相關的需求。

雖然更復雜,但是命令的API處理組件相當容易。它有一個命令進來,然後事件出去。在某些情況下,可能有一個查詢作爲命令執行的一部分。除此之外,命令和事件是API的唯一部分。這意味着可以在事件和命令的基礎上完整地定義測試場景。典型地,以:
• given過去的某些事件,
• when 執行這個命令,
• expect 這些事件將被髮布和/或存儲

Axon Framework提供了一個測試固件,使你能夠做到這一點。AggregateTestFixture允許你配置某些基礎設施,包括必要的命令處理器和存儲庫,並以given-when-then形式的事件和命令來表達你的場景。

下面的示例展示了用JUnit 4對given-when-then測試固件的使用:

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));
        /*
        These four lines define the actual scenario and its expected
        result. The first line defines the events that happened in the
        past. These events define the state of the aggregate under test.
        In practical terms, these are the events that the event store
        returns when an aggregate is loaded. The second line defines the
        command that we wish to execute against our system. Finally, we
        have two more methods that define expected behavior. In the
        example, we use the recommended void return type. The last method
        defines that we expect a single event as result of the command
        execution.
        /*
    }
}

given-when-then測試固件定義了三個階段:配置、執行和驗證。每個階段由不同的接口表示:分別是,FixtureConfiguration, TestExecutor 和 ResultValidator。固件類的靜態newGivenWhenThenFixture()方法提供了對第一個的引用,進而可能提供驗證,等等。

注意
爲了最好地利用這些階段之間的遷移,最好使用這些方法提供的流式接口,如上面的示例所示。

在配置階段(即在提供第一個“given”之前),你提供了執行測試所需的構件。作爲固件的一部分提供事件總線、命令總線和事件存儲的專用版本。有accessor方法來獲得對它們的引用。任何命令處理器不直接在聚合上註冊,需要顯式地使用registerAnnotatedCommandHandler 方法配置。除了帶註解的命令處理器外,你還可以配置各種組件和設置,定義應該如何設置測試周圍的基礎設施。

一旦固件配置好,你就可以定義“given”事件。測試固件將用DomainEventMessage包裝這些事件。如果“given”事件實現消息,消息的有效負載和元數據將被納入DomainEventMessage,否則given事件作爲有效負載。DomainEventMessage 的序列號順序,從0開始。

或者,你也可以爲“given”場景提供命令。在這種情況下,在執行實際的測試命令時,這些命令生成的事件將被用於事件源聚合。使用“givenCommands(…)”方法提供命令對象。

執行階段允許你提供一個針對命令處理組件執行的命令。對調用處理程序的行爲(無論是在聚合或外部處理程序)進行監控,並與在驗證階段註冊的預期進行比較。

注意
在執行測試過程中,Axon試圖檢測測試中的所有在聚合上的非法狀態的更改。它通過將聚合的狀態與命令執行後的聚合狀態進行比較,如果它從所有“given”和存儲的事件溯源。如果狀態不相同,這意味着狀態變化發生在聚合事件處理器方法之外。比較時將忽略靜態和瞬態字段,因爲它們通常包含對資源的引用。
可以使用setReportIllegalStateChange方法在固件的配置中切換檢測。

最後一個階段是驗證階段,允許你檢查命令處理組件的活動。這完全是根據返回值和事件來完成的。

測試固件允許你驗證命令處理程序的返回值。你可以顯式地定義預期的返回值,或者簡單地要求成功返回該方法。你也可以表達任何你期望的CommandHandler拋出的異常。

另一個組件是對已發佈事件的驗證。有兩種匹配預期事件的方法。

第一是通過事件實例,它需要與實際的事件是行逐字的比較。將預期事件的所有屬性與實際事件中的對應對象進行比較(使用equals())。如果其中一個屬性不相等,則測試失敗,並生成一個廣泛的錯誤報告。

表達期望的另一種方式是使用的匹配器(Hamcrest庫提供的)。匹配器接口規定了兩個方法matches(Object)和describeTo(Description)。第一個返回一個布爾值,指示是否匹配或不匹配。第二個讓你表達你的期望。例如,一個“GreaterThanTwoMatcher”可以添加“任何值大於2的事件“的描述。描述允許創建關於測試用例失敗的錯誤消息。

創建事件列表的匹配器可能是繁瑣和容易出錯的工作。爲了簡化問題,Axon提供了一組匹配器允許你提供一組特定於事件的匹配器,並告訴Axon應該如何匹配列表。

下面是可用的事件列表匹配器和他們的目的的概述:
* List with all of: Matchers.listWithAllOf(event matchers…)
如果所有的事件匹配器與真實事件列表中至少一個事件匹配,該匹配器將成功。不管是否有多個匹配器匹配相同的事件,或如果列表中一個事件不匹配任何匹配器。

  • List with any of: Matchers.listWithAnyOf(event matchers…)
    如果一個或多個事件匹配器與實際的事件列表中一個或多個事件匹配,該匹配器將成功。一些匹配器甚至一個也不匹配,而另一個匹配多個。

  • Sequence of Events: Matchers.sequenceOf(event matchers…)
    使用此匹配器來驗證實際事件匹配器和提供的事件匹配器有相同的順序。如果匹配器與後一個事件相匹配,與前一個匹配器匹配的事件相匹配,該匹配器將成功。這意味着可能出現不匹配事件的“gaps”。
    如果評估事件之後,更多的匹配器是可用的,他們都是匹配“null”。它是由事件的匹配器來決定是否接受。

  • 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()
               )
       ));
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章