【テストコード(JUnit,Mokito)の書き方と例題】
こちらのnoteでは、下記の基本の次を前提とした例題と書き方を伝授しているnoteになります。下記のnoteを読んでいない方はまずこちらを読んで理解してからこちらのnoteに取り組みましょう。
こちらの記事を読んだ方は次の工程に入ります。
次の工程では、実際にJUnitとMokitoを使ったテストコードの練習に入りたいと思います。
【テストコードが書けるようになるためのステップ】
では、テストコードが書けるようになるためのステップをご紹介します。
1、複雑なビジネスロジックを理解する
まず、テストしたいクラスやメソッドの動作を深く理解します。どのような入力があり、期待される出力は何かを把握しましょう。
2、シナリオを考える
さまざまなシナリオを考え、正常系だけでなく異常系(例外処理など)も含めてテストケースを設計します。
3、モックとスタブを活用する
依存関係があるクラスやサービスをMockitoでモックして、外部の影響を排除します。
4、テストの実装
JUnitとMockitoを使って、考えたシナリオに基づいたテストメソッドを実装します。アサーションを使って期待される結果と実際の結果を比較します。
5、リファクタリング
テストが通ったら、テストコード自体もクリーンに保つためにリファクタリングを行います。
6、既存のテストコードを読む
オープンソースプロジェクトのテストコードを読んで、他の人がどのように複雑な処理をテストしているか学びます。
まあ、こんなの読んでいてもめんどくさいので早速練習問題に移ります。
【練習問題】
オンラインショップの注文処理のシナリオをテストコードを実際に書いてみましょう。
※要件(開発やテストにおいて、何を達成すべきか、どのような動作や結果が期待されているかを定義したもの)になります。
下記がテストするクラスになります。
public class OrderService {
private InventoryService inventoryService;
private EmailService emailService;
public OrderService(InventoryService inventoryService, EmailService emailService) {
this.inventoryService = inventoryService;
this.emailService = emailService;
}
public void placeOrder(String productId, int quantity) throws OutOfStockException {
if (!inventoryService.checkStock(productId, quantity)) {
throw new OutOfStockException("Product is out of stock");
}
inventoryService.reduceStock(productId, quantity);
emailService.sendOrderConfirmation(productId, quantity);
}
}
※テストケースの考案とは、ソフトウェアの特定の機能や振る舞いが正しく動作しているかを確認するための具体的なテストシナリオを設計することを指します。簡単に言えば、「このコードがどういう状況でどう動くかを試すために、どんな入力を与えて、何を確認すればいいか?」を考える作業です。
テストコードの雛形
下記の雛形を見てテストコードを書いてみてください。
@Test
void testPlaceOrder_Success() {
// Arrange
InventoryService mockInventoryService = mock(InventoryService.class);
EmailService mockEmailService = mock(EmailService.class);
OrderService orderService = new OrderService(mockInventoryService, mockEmailService);
String productId = "product123";
int quantity = 1;
when(mockInventoryService.checkStock(productId, quantity)).thenReturn(true);
// Act
orderService.placeOrder(productId, quantity);
// Assert
verify(mockInventoryService).reduceStock(productId, quantity);
verify(mockEmailService).sendOrderConfirmation(productId, quantity);
}
@Test
void testPlaceOrder_OutOfStock() {
// Arrange
InventoryService mockInventoryService = mock(InventoryService.class);
EmailService mockEmailService = mock(EmailService.class);
OrderService orderService = new OrderService(mockInventoryService, mockEmailService);
String productId = "product123";
int quantity = 1;
when(mockInventoryService.checkStock(productId, quantity)).thenReturn(false);
// Act & Assert
assertThrows(OutOfStockException.class, () -> orderService.placeOrder(productId, quantity));
}
具体的なヒントとして、
1. 要件の理解をする
注文処理の流れをしっかり理解してください。どのような条件で成功するのか、失敗するのかを明確にします。
2. シナリオの整理をする
正常系(成功時の処理)と異常系(失敗時の処理)を分けて、どのようなテストケースを作るべきか考えます。
3. モックの使用
InventoryServiceとEmailServiceをモックして、それぞれの振る舞い(在庫確認やメール送信)を定義します。これにより、外部依存性を排除します。
4. テストの実装
正常系と異常系のテストメソッドをそれぞれ実装します。特に、アサーションを使って期待される結果を確認すること。
5. 実行と改善
テストコードを書いたら、実行してみて結果を確認します。うまくいかない部分があれば、そこを改善することを考えます。
やらなくていいこと
InventoryServiceとEmailServiceは実装は書かなくていいです。
テストを書く際にはそのクラスをモックとして使います。
ただし、モックを作成するためにインターフェイスや抽象クラスの作成をしなければいけません。モックは、具体的な実装クラスではなく、インターフェイスや抽象クラスをもとに作成されます。
その場合分け(どっちを選択するか)ですが、プロジェクトの設計方針や必要性に応じて決まります。
インターフェイスの特徴として、
柔軟性:複数の実装を持つことができ、異なるロジックを持つ複数のクラスが同じインターフェイスを実装できます。これによりモジュール性が向上します。
テスト容易性:モックを作成する際に、インターフェースを使用することで、テストが容易になります。
クラスの特徴として、
具体的な実装: 実際のビジネスロジックを含む具体的なクラスを作成することができます。この場合、テスト対象のクラスが特定の実装に依存することになります。
簡易性: 小規模なプロジェクトや簡単なシナリオでは、インターフェースを使わずにクラスをそのまま使用することで、設計がシンプルになることがあります。
結論
インターフェイスとして実装:複数の異なる実装が必要な場合やテストでモックを使いたい場合に適しています。
クラスとして実装:シンプルなロジックや、特定のビジネスロジックを持たせたい場合に適しています。
配属された、配属している場合は、あまり有り得ないケースです。
テストというのは先に実装されているもの、しているのもを検証する工程のことなので。ただ、これは知っておきましょう。
解答はこちら。