日常工作中,不管你是寫Unit Test,還是采用TDD的編程方式進行開發,都會遇到斷言。而斷言的風格常見的會有Assert、BDD風格,對于這些常見的斷言風格你怎么選擇呢?
01 Assert風格
JUnit中提供了這樣的assert斷言風格,例如:
[@Test](https://my.oschina.net/azibug) void should_be_unlocked_when_insert_coin_given_a_entrance_machine_with_locked_state() { EntranceMachine entranceMachine = new EntranceMachine(EntranceMachineState.LOCKED); String result = entranceMachine.execute(Action.INSERT_COIN); assertEquals("opened", result); assertEquals(EntranceMachineState, entranceMachineState.UNLOCKED); }
Hamcrest和AssertJ都提供了assertThat()這樣風格的斷言,例如:
AssertJ提供的assertThat()的斷言語法
[@Test](https://my.oschina.net/azibug) void should_be_unlocked_when_insert_coin_given_a_entrance_machine_with_locked_state() { EntranceMachine entranceMachine = new EntranceMachine(EntranceMachineState.LOCKED); String result = entranceMachine.execute(Action.INSERT_COIN); assertThat(result).isEqualsTo("opened"); assertThat(EntranceMachineState).isEqualsTo(entranceMachineState.UNLOCKED); }
Hamcrest提供的assertThat()斷言語法
[@Test](https://my.oschina.net/azibug) void should_be_unlocked_when_insert_coin_given_a_entrance_machine_with_locked_state() { EntranceMachine entranceMachine = new EntranceMachine(EntranceMachineState.LOCKED); String result = entranceMachine.execute(Action.INSERT_COIN); assertThat(result, is("opened")); assertThat(EntranceMachineState, is(entranceMachineState.UNLOCKED)); }
對比上面三種斷言語法,因為場景簡單,所以結果差異并不是很大。對于我個人更加偏向于使用AssertJ提供的斷言風格。因為這種風格避免JUnit提供的斷言中經常遇到的問題,expected在前還是actural在前的問題。相比于Hamcrest的斷言風格,在日常工作中綜合對比發現AssertJ的更加清晰,畢竟AssertJ中assertThat只需要接收一個參數,而不用關注括號是否對齊的問題。
日常工作中如果使用TDD,且場景適當(例如上面例子),那么Hamcreate和AssertJ的差別不是很大。JUnit5默認提供了Hamcreate的斷言,不需要額外的再引入其他依賴。
02 BDD風格
代碼的可讀性越來越收到開發者的重視。測試代碼的可讀性同樣重要,為了讓測試代碼結構清晰,便于業務邏輯變動時能快讀讀懂測試的上下文,很多開發團隊約定了BDD的風格來組織測試代碼。其中包含兩部分的約定:測試方法名的約定,測試代碼段落的約定。
例如前面的例子:
[@Test](https://my.oschina.net/azibug) void should_be_unlocked_when_insert_coin_given_a_entrance_machine_with_locked_state() { ... }
雖然方法名很長,但是通過方法名我們能夠快速知道測試類中有哪些測試,通過方法名我們能夠清晰的當前測試的上下文,在測什么,期望的結果什么。通過方法名而不是通過比方法名長很多的代碼段來獲取測試在測什么的信息,畢竟閱讀代碼時間和修改代碼時間可能是10:1,甚至20:1。所以團隊約定BDD的風格組織在后續修改代碼時,是受益良多的。
當需要也帶具體的測試代碼的時候,團隊發現按照BDD這種三段式的風格來組織代碼受益良多。例如:
[@Test](https://my.oschina.net/azibug) void should_be_unlocked_when_insert_coin_given_a_entrance_machine_with_locked_state() { EntranceMachine entranceMachine = new EntranceMachine(EntranceMachineState.LOCKED); String result = entranceMachine.execute(Action.INSERT_COIN); assertThat(result).isEqualsTo("opened"); assertThat(EntranceMachineState).isEqualsTo(entranceMachineState.UNLOCKED); }
我們可以清晰的知道哪行代碼在描述上下文,哪幾行代碼在描述測試意圖,哪幾行代碼在描述測試結果驗證。
BDD的風格能夠幫助團隊將測試代碼維護的較為清晰。AssertJ提供了BDD風格的斷言方式。使用then()語法。例如:
@Test void should_be_unlocked_when_insert_coin_given_a_entrance_machine_with_locked_state() { EntranceMachine entranceMachine = new EntranceMachine(EntranceMachineState.LOCKED); String result = entranceMachine.execute(Action.INSERT_COIN); then(result).isEqualsTo("opened"); then(EntranceMachineState).isEqualsTo(entranceMachineState.UNLOCKED); }
斷言變化不大。但是真正仔細讀的時候,會發現使用then()還是簡單那么一點點的。
我們常用的Mock工具Mockito,也提供了BDD風格的斷言:then(), should(), and()。
import static org.mockito.BDDMockito.then;import static org.assertj.core.api.BDDAssertions.and;import static org.mockito.Mockito.mock;import static org.mockito.Mockito.times;@SuppressWarnings("static-access")@Testpublic void bdd_assertions_with_bdd_mockito() { Person person = mock(Person.class) person.ride(bike); person.ride(bike); then(person).should(times(2)).ride(bike); and.then(person.hasBike()).isTrue();}
所以日常開發中,我會首先選擇then(),其次會選擇assertThat()。
除了以上兩種斷言風格,流式斷言讓代碼更清晰,斷言重復內容更少
當我們需要為某個結果測試多個測試點時,如果為每個測試點都組織一次相同的上下文,那么重復代碼太多。帶來的價值就是那么一點點區別,所以在測試力度上我們可以根據經驗來在開發工程中動態調整。
下面據一個例子,當我們需要驗證有一個查詢方法返回的List的結果時,不單單要驗證List中元素的數量,還要驗證元素是否時期望的順序。那么流式寫法會縮減一部分重復的斷言代碼。
then(users).hasSize(3) .containsExactlyInAnyOrder( firstUser, secondUser, thirdUser);
上面是日常工作中經常使用到的斷言技巧,你的怎么選擇的呢?那種風格無所謂能工作就行?
關注頭條號《 JAVA 后端架構 》 ,話癆技術,職場,招聘,在線面試,進階提升。每天一篇技術分享
沒有做不到的,只有想不到的。