日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

junit mockito_从工作中清除代码–使用JUnit 5,Mockito和AssertJ编写可执行规范

發布時間:2023/12/3 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 junit mockito_从工作中清除代码–使用JUnit 5,Mockito和AssertJ编写可执行规范 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

junit mockito

可執行規范是也可以用作設計規范的測試。 通過啟用通用語言(在DDD世界中,這也稱為無處不在的語言 ),它們使技術和業務團隊能夠進入同一頁面。 它們充當代碼的未來維護者的文檔。
在本文中,我們將看到一種編寫自動測試的自以為是的方式,該方法也可以用作可執行規范。

讓我們從一個例子開始。 假設我們正在為企業創建會計系統。 該系統將允許其用戶將收入和支出記錄到不同的帳戶中。 在用戶開始記錄收入和支出之前,他們應該能夠將新帳戶添加到系統中。 假設“添加新帳戶”用例的規范如下所示–

場景1

給定帳戶不存在 用戶添加新帳戶時 然后添加的帳戶具有給定的名稱 然后添加的帳戶具有給定的初始余額 然后添加的帳戶具有用戶的ID

方案2

給定帳戶不存在 當用戶添加初始余額為負的新帳戶時 然后添加新帳戶失敗

場景3

具有相同名稱的給定帳戶 用戶添加新帳戶時 然后添加新帳戶失敗

為了創建一個新帳戶,用戶需要在系統中輸入一個帳戶名和一個初始余額。 如果不存在具有給定名稱的帳戶并且給定的初始余額為正,則系統將創建該帳戶。

我們將首先寫下一個測試,該測試將捕獲第一個場景的第一個“ Given-When-Then”部分。 這就是它的樣子–

class AddNewAccountTest { @Test @DisplayName ( "Given account does not exist When user adds a new account Then added account has the given name" ) void accountAddedWithGivenName() { ????} }

@DisplayName批注是在JUnit 5中引入的。它為測試分配了易于理解的名稱。 這是我們執行此測試時看到的標簽,例如在像IntelliJ IDEA這樣的IDE中。

現在,我們將創建一個類,負責添加帳戶

class AddNewAccountService { void addNewAccount(String accountName) { } }

該類定義一個接受帳戶名稱并負責創建帳戶的方法,即將其保存到持久數據存儲中。 由于我們決定將此類稱為AddNewAccountService,因此我們還將測試重命名為AddNewAccountServiceTest以遵循JUnit世界中使用的命名約定。

現在,我們可以繼續編寫測試了–

class AddNewAccountServiceTest { @Test @DisplayName ( "Given account does not exist When user adds a new account Then added account has the given name" ) void accountAddedWithGivenName() { AddNewAccountService accountService = new AddNewAccountService(); accountService.addNewAccount( "test account" ); ????// What to test? } }

我們應該測試/驗證什么以確保正確實施該方案? 如果再次閱讀我們的規范,很明顯,我們想創建一個具有用戶指定名稱的“帳戶”,因此我們應該在此處進行測試。 為此,我們必須首先創建一個代表帳戶的類-

@AllArgsConstructor class Account { private String name; }

Account類只有一個名為name的屬性。 它將具有其他字段,例如用戶ID和余額,但是我們目前尚未對其進行測試,因此我們不會立即將它們添加到類中。

現在,我們已經創建了Account類,如何保存它,更重要的是,我們如何測試所保存的帳戶具有用戶指定的名稱? 有許多方法可以做到這一點,而我的首選方法是定義一個接口,該接口將封裝此保存操作。 讓我們繼續創建它–

interface SaveAccountPort { void saveAccount(Account account); }

AddNewAccountService將通過構造函數注入注入該接口的實現–

@RequiredArgsConstructor class AddNewAccountService { private final SaveAccountPort saveAccountPort; void addNewAccount(String accountName) { } }

為了進行測試,我們將在Mockito的幫助下創建一個模擬實現,這樣我們就不必擔心實際的實現細節了–

@ExtendWith (MockitoExtension. class ) class AddNewAccountServiceTest { @Mock private SaveAccountPort saveAccountPort; @Test @DisplayName ( "Given account does not exist When user adds a new account Then added account has the given name" ) void accountAddedWithGivenName() { AddNewAccountService accountService = new AddNewAccountService(saveAccountPort); accountService.addNewAccount( "test account" ); ????// What to test? } }

我們的測試設置現已完成。 現在,我們希望我們的測試方法(AddNewAccountService類的addNewAccount方法)調用SaveAccountPort的saveAccount方法,并將Account對象的名稱設置為傳遞給該方法的對象。 讓我們在測試中對此進行整理–

@ExtendWith (MockitoExtension. class ) class AddNewAccountServiceTest { @Mock private SaveAccountPort saveAccountPort; @Captor private ArgumentCaptor<Account> accountArgumentCaptor; @Test @DisplayName ( "Given account does not exist When user adds a new account Then added account has the given name" ) void accountAddedWithGivenName() { AddNewAccountService accountService = new AddNewAccountService(saveAccountPort); accountService.addNewAccount( "test account" ); BDDMockito.then(saveAccountPort).should().saveAccount(accountArgumentCaptor.capture()); BDDAssertions.then(accountArgumentCaptor.getValue().getName()).isEqualTo( "test account" ); } }

下面的行–

BDDMockito.then(saveAccountPort).should().saveAccount(accountArgumentCaptor.capture());

驗證一旦調用了被測試方法,即已調用SaveAccountPort的saveAccount方法。 我們還使用參數捕獲器捕獲傳遞到saveAccount方法的帳戶參數。 下一行–

BDDAssertions.then(accountArgumentCaptor.getValue().getName()).isEqualTo( "test account" );

然后驗證捕獲的帳戶參數與測試中通過的名稱相同。

為了使此測試通過,在我們的被測方法中需要的最少代碼如下:

@RequiredArgsConstructor class AddNewAccountService { private final SaveAccountPort saveAccountPort; void addNewAccount(String accountName) { saveAccountPort.saveAccount( new Account(accountName)); } }

這樣,我們的測試開始通過!

讓我們繼續進行第一個方案的第二個“ Then”部分,它說–

然后添加的帳戶具有給定的初始余額

讓我們寫另一個測試來驗證這一部分–

@Test @DisplayName ( "Given account does not exist When user adds a new account Then added account has the given initial balance" ) void accountAddedWithGivenInitialBalance() { AddNewAccountService accountService = new AddNewAccountService(saveAccountPort); accountService.addNewAccount( "test account" , "56.0" ); ??BDDMockito.then(saveAccountPort).should().saveAccount(accountArgumentCaptor.capture()); BDDAssertions.then(accountArgumentCaptor.getValue().getBalance()) .isEqualTo( new BigDecimal( "56.0" )); }

我們修改了addNewAccount方法以接受初始余額作為第二個參數。 我們還在帳戶對象中添加了一個稱為余額的新字段,該字段能夠存儲帳戶余額–

@AllArgsConstructor @Getter class Account { private String name; private BigDecimal balance; }

由于我們更改了addNewAccount方法的簽名,因此我們還必須修改我們的第一個測試–

@Test @DisplayName ( "Given account does not exist When user adds a new account Then added account has the given name" ) void accountAddedWithGivenName() { AddNewAccountService accountService = new AddNewAccountService(saveAccountPort); accountService.addNewAccount( "test account" , "1" ); BDDMockito.then(saveAccountPort).should().saveAccount(accountArgumentCaptor.capture()); BDDAssertions.then(accountArgumentCaptor.getValue().getName()).isEqualTo( "test account" ); }

如果我們現在運行新測試,它將由于我們尚未實現該功能而失敗。 現在就開始吧–

void addNewAccount(String accountName, String initialBalance) { saveAccountPort.saveAccount( new Account(accountName, new BigDecimal(initialBalance))); }

我們的兩個測試現在都應該通過。

由于我們已經進行了一些測試,現在該看看我們的實現,看看是否可以做得更好。 由于我們的AddNewAccountService非常簡單,因此我們無需在此做任何事情。 對于我們的測試,我們可以消除測試設置代碼中的重復項–兩個測試都實例化AddNewAccountService的實例,并以相同的方式在其上調用addNewAccount方法。 刪除還是保留重復項取決于我們的測試編寫方式-如果我們想使每個測試盡可能獨立,那么就讓它們保持原樣。 但是,如果我們對使用通用的測試設置代碼感到滿意,則可以按以下方式更改測試

@ExtendWith (MockitoExtension. class ) @DisplayName ( "Given account does not exist When user adds a new account" ) class AddNewAccountServiceTest { private static final String ACCOUNT_NAME = "test account" ; private static final String INITIAL_BALANCE = "56.0" ; @Mock private SaveAccountPort saveAccountPort; @Captor private ArgumentCaptor<Account> accountArgumentCaptor; @BeforeEach void setup() { AddNewAccountService accountService = new AddNewAccountService(saveAccountPort); accountService.addNewAccount(ACCOUNT_NAME, INITIAL_BALANCE); } @Test @DisplayName ( "Then added account has the given name" ) void accountAddedWithGivenName() { BDDMockito.then(saveAccountPort).should().saveAccount(accountArgumentCaptor.capture()); BDDAssertions.then(accountArgumentCaptor.getValue().getName()).isEqualTo(ACCOUNT_NAME); } @Test @DisplayName ( "Then added account has the given initial balance" ) void accountAddedWithGivenInitialBalance() { BDDMockito.then(saveAccountPort).should().saveAccount(accountArgumentCaptor.capture()); BDDAssertions.then(accountArgumentCaptor.getValue().getBalance()) .isEqualTo( new BigDecimal(INITIAL_BALANCE)); } }

請注意,我們還提取了@DisplayName的公共部分,并將其放在測試類的頂部。 如果我們不愿意這樣做,我們也可以保留原樣。

由于我們有多個通過的測試,因此從現在開始,每一次失敗的測試通過,我們都會停一會兒,看看我們的實現,并嘗試對其進行改進。 總而言之,我們的實施過程現在將包括以下步驟-

  • 在確保現有測試持續通過的同時添加失敗的測試
  • 通過失敗的測試
  • 暫停片刻,然后嘗試改善實施(代碼和測試)
  • 繼續,我們現在需要使用創建的帳戶存儲用戶ID。 按照我們的方法,我們將首先編寫一個失敗的測試以捕獲此錯誤,然后添加使失敗的測試通過的最少代碼量。 一旦失敗的測試開始通過,這就是實現的樣子

    @ExtendWith (MockitoExtension. class ) @DisplayName ( "Given account does not exist When user adds a new account" ) class AddNewAccountServiceTest { private static final String ACCOUNT_NAME = "test account" ; private static final String INITIAL_BALANCE = "56.0" ; private static final String USER_ID = "some id" ; private Account savedAccount; @BeforeEach void setup() { AddNewAccountService accountService = new AddNewAccountService(saveAccountPort); accountService.addNewAccount(ACCOUNT_NAME, INITIAL_BALANCE, USER_ID); BDDMockito.then(saveAccountPort).should().saveAccount(accountArgumentCaptor.capture()); savedAccount = accountArgumentCaptor.getValue(); } ??// Other tests..... @Test @DisplayName ( "Then added account has user's id" ) void accountAddedWithUsersId() { BDDAssertions.then(accountArgumentCaptor.getValue().getUserId()).isEqualTo(USER_ID); } } @RequiredArgsConstructor class AddNewAccountService { private final SaveAccountPort saveAccountPort; void addNewAccount(String accountName, String initialBalance, String userId) { saveAccountPort.saveAccount( new Account(accountName, new BigDecimal(initialBalance), userId)); } } @AllArgsConstructor @Getter class Account { private String name; private BigDecimal balance; private String userId; }

    既然所有測試都通過了,那就是改進的時間了! 請注意,addNewAccount方法已經接受了三個參數。 隨著我們引入越來越多的帳戶屬性,其參數列表也將開始增加。 我們可以引入一個參數對象來避免這種情況

    @RequiredArgsConstructor class AddNewAccountService { private final SaveAccountPort saveAccountPort; void addNewAccount(AddNewAccountCommand command) { saveAccountPort.saveAccount( new Account( command.getAccountName(), new BigDecimal(command.getInitialBalance()), command.getUserId() ) ); } @Builder @Getter static class AddNewAccountCommand { private final String userId; private final String accountName; private final String initialBalance; } } @ExtendWith (MockitoExtension. class ) @DisplayName ( "Given account does not exist When user adds a new account" ) class AddNewAccountServiceTest { // Fields..... @BeforeEach void setup() { AddNewAccountService accountService = new AddNewAccountService(saveAccountPort); AddNewAccountCommand command = AddNewAccountCommand.builder() .accountName(ACCOUNT_NAME) .initialBalance(INITIAL_BALANCE) .userId(USER_ID) .build(); accountService.addNewAccount(command); BDDMockito.then(saveAccountPort).should().saveAccount(accountArgumentCaptor.capture()); savedAccount = accountArgumentCaptor.getValue(); } // Remaining Tests..... }

    如果現在在我的IDEA中運行測試,這就是我所看到的–

    當我們嘗試在此視圖中閱讀測試描述時,我們已經可以很好地了解“添加新帳戶”用例及其工作方式。

    好的,讓我們繼續進行用例的第二種情況,這是一個驗證規則

    給定帳戶不存在

    當用戶添加初始余額為負的新帳戶時

    然后添加新帳戶失敗

    讓我們編寫一個新的測試來嘗試捕獲這一點–

    @ExtendWith (MockitoExtension. class ) @DisplayName ( "Given account does not exist When user adds a new account" ) class AddNewAccountServiceTest { // Other tests @Test @DisplayName ( "Given account does not exist When user adds a new account with negative initial balance Then add new account fails" ) void addNewAccountFailsWithNegativeInitialBalance() { AddNewAccountService accountService = new AddNewAccountService(saveAccountPort); AddNewAccountCommand command = AddNewAccountCommand.builder().initialBalance( "-56.0" ).build(); AddNewAccountCommand command = AddNewAccountCommand.builder().initialBalance( ).build(); accountService.addNewAccount(command); BDDMockito.then(saveAccountPort).shouldHaveNoInteractions(); } }

    我們可以通過幾種方法在服務中實施驗證。 我們可以拋出一個異常詳細說明驗證失敗,或者我們可以返回一個包含錯誤詳細信息的錯誤對象。 在此示例中,如果驗證失敗,我們將拋出異常–

    @Test @DisplayName ( "Given account does not exist When user adds a new account with negative initial balance Then add new account fails" ) void addNewAccountFailsWithNegativeInitialBalance() { AddNewAccountService accountService = new AddNewAccountService(saveAccountPort); AddNewAccountCommand command = AddNewAccountCommand.builder().initialBalance( "-56.0" ).build(); AddNewAccountCommand command = AddNewAccountCommand.builder().initialBalance( ).build(); assertThatExceptionOfType(IllegalArgumentException. class ) .isThrownBy(() -> accountService.addNewAccount(command)); BDDMockito.then(saveAccountPort).shouldHaveNoInteractions(); }

    此測試驗證以負余額調用addNewAccount方法時是否引發異常。 它還可以確保在這種情況下,我們的代碼不會調用SaveAccountPort的任何方法。 在我們開始修改我們的服務以通過此測試之前,我們必須重構一下我們的測試設置代碼。 這是因為在我們之前的重構中,我們將通用測試設置代碼移到了一個方法中,該方法現在可以在每次測試之前運行–

    @BeforeEach void setup() { AddNewAccountService accountService = new AddNewAccountService(saveAccountPort); AddNewAccountCommand command = AddNewAccountCommand.builder() .accountName(ACCOUNT_NAME) .initialBalance(INITIAL_BALANCE) .userId(USER_ID) .build(); accountService.addNewAccount(command); BDDMockito.then(saveAccountPort).should().saveAccount(accountArgumentCaptor.capture()); savedAccount = accountArgumentCaptor.getValue(); }

    現在,此設置代碼與我們剛剛添加的新測試直接沖突–在每次測試之前,它將始終使用有效的命令對象調用addNewAccount方法,從而導致調用SaveAccountPort的saveAccount方法,從而導致新測試失敗。

    為了解決此問題,我們將在測試類中創建一個嵌套類,在其中我們將移動現有的設置代碼和通過測試–

    @ExtendWith (MockitoExtension. class ) @DisplayName ( "Given account does not exist" ) class AddNewAccountServiceTest { @Mock private SaveAccountPort saveAccountPort; private AddNewAccountService accountService; @BeforeEach void setUp() { accountService = new AddNewAccountService(saveAccountPort); } @Nested @DisplayName ( "When user adds a new account" ) class WhenUserAddsANewAccount { private static final String ACCOUNT_NAME = "test account" ; private static final String INITIAL_BALANCE = "56.0" ; private static final String USER_ID = "some id" ; private Account savedAccount; @Captor private ArgumentCaptor<Account> accountArgumentCaptor; @BeforeEach void setUp() { AddNewAccountCommand command = AddNewAccountCommand.builder() .accountName(ACCOUNT_NAME) .initialBalance(INITIAL_BALANCE) .userId(USER_ID) .build(); accountService.addNewAccount(command); BDDMockito.then(saveAccountPort).should().saveAccount(accountArgumentCaptor.capture()); savedAccount = accountArgumentCaptor.getValue(); } @Test @DisplayName ( "Then added account has the given name" ) void accountAddedWithGivenName() { BDDAssertions.then(savedAccount.getName()).isEqualTo(ACCOUNT_NAME); } @Test @DisplayName ( "Then added account has the given initial balance" ) void accountAddedWithGivenInitialBalance() { BDDAssertions.then(savedAccount.getBalance()).isEqualTo( new BigDecimal(INITIAL_BALANCE)); } @Test @DisplayName ( "Then added account has user's id" ) void accountAddedWithUsersId() { BDDAssertions.then(accountArgumentCaptor.getValue().getUserId()).isEqualTo(USER_ID); } } ??@Test @DisplayName ( "When user adds a new account with negative initial balance Then add new account fails" ) void addNewAccountFailsWithNegativeInitialBalance() { AddNewAccountCommand command = AddNewAccountCommand.builder() .initialBalance( "-56.0" ) .build(); assertThatExceptionOfType(IllegalArgumentException. class ) .isThrownBy(() -> accountService.addNewAccount(command)); BDDMockito.then(saveAccountPort).shouldHaveNoInteractions(); } }

    這是我們采取的重構步驟–

  • 我們創建了一個內部類,然后用JUnit 5的@Nested批注標記內部類。
  • 我們破壞了最外面的測試類的@DisplayName標簽,并將“當用戶添加新帳戶時”部分移到了新引入的內部類中。 我們這樣做的原因是因為此內部類將包含一組測試,這些測試將驗證/驗證與有效帳戶創建方案有關的行為。
  • 我們將相關的設置代碼和字段/常量移到了這個內部類中。
  • 我們從新測試中刪除了“給定帳戶不存在”部分。 這是因為最外層測試類上的@DisplayName已包含此內容,因此這里再也沒有包含它。
  • 現在是在IntelliJ IDEA中運行測試時的樣子,

    從屏幕截圖中可以看到,我們的測試標簽也按照我們在測試代碼中創建的結構很好地進行了分組和縮進。 現在,讓我們修改服務以使失敗的測試通過–

    void addNewAccount(AddNewAccountCommand command) { BigDecimal initialBalance = new BigDecimal(command.getInitialBalance()); if (initialBalance.compareTo(BigDecimal.ZERO) < 0 ) { throw new IllegalArgumentException( "Initial balance of an account cannot be negative" ); } saveAccountPort.saveAccount( new Account( command.getAccountName(), initialBalance, command.getUserId() ) ); }

    這樣,我們所有的測試再次開始通過。 下一步是尋找可能的方法來改進現有的實現。 如果沒有,那么我們將繼續執行最終方案,這也是一個驗證規則–

    具有相同名稱的給定帳戶

    用戶添加新帳戶時

    然后添加新帳戶失敗

    和往常一樣,讓我們??編寫一個測試來捕獲這一點–

    @Test @DisplayName ( "Given account with the same name exists When user adds a new account Then add new account fails" ) void addNewAccountFailsForDuplicateAccounts() { AddNewAccountCommand command = AddNewAccountCommand.builder() .accountName( "existing name" ) .build(); AddNewAccountService accountService = new AddNewAccountService(saveAccountPort); assertThatExceptionOfType(IllegalArgumentException. class ) .isThrownBy(() -> accountService.addNewAccount(command)); BDDMockito.then(saveAccountPort).shouldHaveNoInteractions(); }

    我們現在必須弄清的第一件事是如何找到現有帳戶。 由于這將涉及查詢我們的持久數據存儲,因此我們將引入一個接口–

    public interface FindAccountPort { Account findAccountByName(String accountName); }

    并將其注入我們的AddNewAccountService –

    @RequiredArgsConstructor class AddNewAccountService { private final SaveAccountPort saveAccountPort; private final FindAccountPort findAccountPort; ??// Rest of the code }

    并修改我們的測試–

    @Test @DisplayName ( "Given account with the same name exists When user adds a new account Then add new account fails" ) void addNewAccountFailsForDuplicateAccounts() { String existingAccountName = "existing name" ; AddNewAccountCommand command = AddNewAccountCommand.builder() .initialBalance( "0" ) .accountName(existingAccountName) .build(); given(findAccountPort.findAccountByName(existingAccountName)).willReturn(mock(Account. class )); AddNewAccountService accountService = new AddNewAccountService(saveAccountPort, findAccountPort); assertThatExceptionOfType(IllegalArgumentException. class ) .isThrownBy(() -> accountService.addNewAccount(command)); BDDMockito.then(saveAccountPort).shouldHaveNoInteractions(); }

    對AddNewAccountService的最后更改也將需要對現有測試進行更改,主要是在我們實例化該類的實例的位置。 但是,我們將做的改變不止于此–

    @ExtendWith (MockitoExtension. class ) class AddNewAccountServiceTest { @Mock private SaveAccountPort saveAccountPort; @Mock private FindAccountPort findAccountPort; @Nested @DisplayName ( "Given account does not exist" ) class AccountDoesNotExist { private AddNewAccountService accountService; @BeforeEach void setUp() { accountService = new AddNewAccountService(saveAccountPort, findAccountPort); } @Nested @DisplayName ( "When user adds a new account" ) class WhenUserAddsANewAccount { private static final String ACCOUNT_NAME = "test account" ; private static final String INITIAL_BALANCE = "56.0" ; private static final String USER_ID = "some id" ; private Account savedAccount; @Captor private ArgumentCaptor<Account> accountArgumentCaptor; @BeforeEach void setUp() { AddNewAccountCommand command = AddNewAccountCommand.builder() .accountName(ACCOUNT_NAME) .initialBalance(INITIAL_BALANCE) .userId(USER_ID) .build(); accountService.addNewAccount(command); BDDMockito.then(saveAccountPort).should().saveAccount(accountArgumentCaptor.capture()); savedAccount = accountArgumentCaptor.getValue(); } @Test @DisplayName ( "Then added account has the given name" ) void accountAddedWithGivenName() { BDDAssertions.then(savedAccount.getName()).isEqualTo(ACCOUNT_NAME); } @Test @DisplayName ( "Then added account has the given initial balance" ) void accountAddedWithGivenInitialBalance() { BDDAssertions.then(savedAccount.getBalance()).isEqualTo( new BigDecimal(INITIAL_BALANCE)); } @Test @DisplayName ( "Then added account has user's id" ) void accountAddedWithUsersId() { BDDAssertions.then(accountArgumentCaptor.getValue().getUserId()).isEqualTo(USER_ID); } } @Test @DisplayName ( "When user adds a new account with negative initial balance Then add new account fails" ) void addNewAccountFailsWithNegativeInitialBalance() { AddNewAccountCommand command = AddNewAccountCommand.builder() .initialBalance( "-56.0" ) .build(); assertThatExceptionOfType(IllegalArgumentException. class ) .isThrownBy(() -> accountService.addNewAccount(command)); BDDMockito.then(saveAccountPort).shouldHaveNoInteractions(); } } @Test @DisplayName ( "Given account with the same name exists When user adds a new account Then add new account fails" ) void addNewAccountFailsForDuplicateAccounts() { String existingAccountName = "existing name" ; AddNewAccountCommand command = AddNewAccountCommand.builder() .initialBalance( "0" ) .accountName(existingAccountName) .build(); given(findAccountPort.findAccountByName(existingAccountName)).willReturn(mock(Account. class )); AddNewAccountService accountService = new AddNewAccountService(saveAccountPort, findAccountPort); assertThatExceptionOfType(IllegalArgumentException. class ) .isThrownBy(() -> accountService.addNewAccount(command)); BDDMockito.then(saveAccountPort).shouldHaveNoInteractions(); } }

    這是我們所做的–

  • 我們創建了另一個內部類,將其標記為@Nested,并將現有的通過測試移入其中。 這組測試測試在不存在具有給定名稱的帳戶時添加新帳戶的行為。
  • 我們已將測試設置代碼移至新引入的內部類中,因為它們也與“不存在具有給定名稱的帳戶”的情況有關。
  • 出于與上述相同的原因,我們還將@DisplayName注釋從頂級測試類移動到了新引入的內部類。
  • 重構之后,我們快速運行測試以查看一切是否按預期工作(測試失敗,通過測試通過),然后繼續修改我們的服務–

    @RequiredArgsConstructor class AddNewAccountService { private final SaveAccountPort saveAccountPort; private final FindAccountPort findAccountPort; void addNewAccount(AddNewAccountCommand command) { BigDecimal initialBalance = new BigDecimal(command.getInitialBalance()); if (initialBalance.compareTo(BigDecimal.ZERO) < 0 ) { throw new IllegalArgumentException( "Initial balance of an account cannot be negative" ); } if (findAccountPort.findAccountByName(command.getAccountName()) != null ) { throw new IllegalArgumentException( "An account with given name already exists" ); } saveAccountPort.saveAccount( new Account( command.getAccountName(), initialBalance, command.getUserId() ) ); } @Builder @Getter static class AddNewAccountCommand { private final String userId; private final String accountName; private final String initialBalance; } }

    我們所有的測試現在都是綠色的–

    由于我們的用例實現現已完成,因此我們將最后一次查看實現,以查看是否可以進行任何改進。 如果沒有,那么我們的用例實現現在就完成了!

    總而言之,這就是我們在本文中所做的–

  • 我們已經寫下了要實現的用例
  • 我們添加了一個失敗的測試,并使用易于理解的名稱進行標記
  • 我們添加了使測試通過失敗所需的最少代碼量
  • 一旦我們進行了一項以上的測試,在通過每項失敗的測試之后,我們查看了實現并試圖對其進行改進
  • 在編寫測試時,我們嘗試以某種方式編寫測試,以使用例規范反映在測試代碼中。 為此,我們使用了–
  • @DisplayName批注為我們的測試分配易于理解的名稱
  • @Nested用于按層次結構將相關測試分組,以反映我們的用例設置
  • 使用了Mockito和AssertJ的BDD驅動的API來驗證預期的行為
  • 我們什么時候應該遵循這種編寫自動化測試的風格? 該問題的答案與軟件工程中的其他所有用法問題相同-視情況而定。 當我使用具有復雜業務/域規則的應用程序時,我個人更喜歡這種樣式,該規則需要長期維護,為此需要與業務部門緊密合作,以及許多其他因素(例如,應用程序)架構,團隊采用率等)。

    與往常一樣,完整的示例已提交給Github 。

    直到下一次!

    翻譯自: https://www.javacodegeeks.com/2020/04/clean-code-from-the-trenches-writing-executable-specifications-with-junit-5-mockito-and-assertj.html

    junit mockito

    總結

    以上是生活随笔為你收集整理的junit mockito_从工作中清除代码–使用JUnit 5,Mockito和AssertJ编写可执行规范的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。