ユニットテストは、ソフトウェア開発において重要なテスト手法の一つで、アプリケーションの個々の部品(ユニット)を検証するために使用されます。ここでは、ユニットテストの基本概念と実践的な方法を解説します。
ユニットテストとは
ユニットテストは、プログラムの最小単位である「ユニット」をテストするプロセスです。一般的には、関数やメソッドがユニットとして扱われます。以下のような特性を持ちます:
独立性: 他のコードや外部依存性に影響されない。
高速性: 短時間で実行可能。
再現性: 同じ入力で常に同じ結果を出力する。
ユニットテストのメリット
バグの早期発見: 小さな単位でテストを行うため、バグを迅速に特定できます。
リファクタリングの支援: テストが保証することで、安全にコードのリファクタリングが可能になります。
ドキュメントとしての役割: テストコード自体が、仕様や動作のドキュメントとなります。
Javaでのユニットテスト
以下では、Javaを使用してユニットテストを作成する方法を詳しく解説します。
基本的なテスト例
テスト対象コード (Calculator.java):
public class Calculator {
public int add(int a, int b) {
return a + b;
}
public int subtract(int a, int b) {
return a - b;
}
}
テストコード (CalculatorTest.java):
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
public class CalculatorTest {
@Test
public void testAdd() {
Calculator calculator = new Calculator();
assertEquals(5, calculator.add(2, 3));
assertEquals(0, calculator.add(-1, 1));
}
@Test
public void testSubtract() {
Calculator calculator = new Calculator();
assertEquals(1, calculator.subtract(3, 2));
assertEquals(-2, calculator.subtract(-1, 1));
}
}
実行手順
JUnitのセットアップ
Mavenの場合:
org.junit.jupiter junit-jupiter 5.9.0 testテストの実行
Mavenを使用してテストを実行します:
mvn test
テストケースの追加
新しいメソッドを追加した場合、その機能に対応するテストケースを作成します。
例: 掛け算機能の追加
Calculator.java
public int multiply(int a, int b) {
return a * b;
}
CalculatorTest.java
@Test
public void testMultiply() {
Calculator calculator = new Calculator();
assertEquals(6, calculator.multiply(2, 3));
assertEquals(0, calculator.multiply(0, 5));
}
ここで使われているassertEqualsはアサーションメソッドと呼ばれるものです。
アサーションメソッドとは、プログラムが期待どおりに動作しているかどうかを検証するためのメソッドです。
アサーションメソッドの目的
テストの自動化
人間が手動で結果を確認する代わりに、プログラムが自動的に正誤を判定します。
期待値と実際の結果の比較
代表的なアサーションメソッド(JUnitの例)
JUnitでよく使われるアサーションメソッドには以下のようなものがあります。
1. assertEquals(expected, actual)
説明: 期待値と実際の値が一致するかを確認します。
例:
assertEquals(5, calculator.add(2, 3));
2. assertNotEquals(unexpected, actual)
説明: 期待しない値と実際の値が異なるかを確認します。
例:
assertNotEquals(0, calculator.add(2, 3));
3. assertTrue(condition)
説明: 条件が true であることを確認します。
assertTrue(result > 0);
4. assertFalse(condition)
説明: 条件が false であることを確認します。
assertFalse(user.isAdmin());
5. assertNull(actual)
説明: 実際の値が null であることを確認します。
assertNull(user.getEmail());
6. assertNotNull(actual)
説明: 実際の値が null でないことを確認します。
assertNotNull(order.getOrderId());
7. assertSame(expected, actual)
説明: 2つのオブジェクトが同じインスタンスであることを確認します(参照の一致)。
assertSame(user1, user2);
8. assertNotSame(unexpected, actual)
説明: 2つのオブジェクトが異なるインスタンスであることを確認します。
assertNotSame(user1, user3);
アサーションが失敗した場合
アサーションが失敗すると、テストは中断され、エラーメッセージが表示されます。たとえば、次のコードが失敗する例を見てみましょう。
assertEquals(5, calculator.add(2, 2));
この場合、期待値が 5 ですが、add(2, 2) の結果が 4 だとすると、JUnitは次のようなエラーを表示します。
expected: <5> but was: <4>
これにより、add メソッドに何か問題があることがわかります。
Spring Bootでのユニットテスト
Spring Bootアプリケーションでは、特定のユニットやレイヤー(例: サービス層やリポジトリ層)をテストするためにさまざまなツールやアノテーションが用いられます。
よく使われるアノテーション
@Test
JUnitでテストメソッドを示すアノテーション。
@SpringBootTest
アプリケーション全体をロードしてテストを実行します。
@SpringBootTest
public class ApplicationTests {
@Test
void contextLoads() {
}
}
@MockBean
コンテキスト内でモックオブジェクトを提供します。
@MockBean
private MyService myService;
@WebMvcTest
コントローラ層のテストに特化しています。
@WebMvcTest(MyController.class)
public class MyControllerTest {
}
@DataJpaTest
リポジトリ層のテストを行うためのアノテーション。
@DataJpaTest
public class MyRepositoryTest {
}
サンプルテストコード
サービス層のテスト
サービスコード (MyService.java):
@Service
public class MyService {
public String greet(String name) {
return "Hello, " + name + "!";
}
}
テストコード (MyServiceTest.java):
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
public class MyServiceTest {
private final MyService myService = new MyService();
@Test
public void testGreet() {
assertEquals("Hello, John!", myService.greet("John"));
assertEquals("Hello, Jane!", myService.greet("Jane"));
}
}
コントローラ層のテスト
コントローラコード (MyController.java):
@RestController
@RequestMapping("/api")
public class MyController {
@GetMapping("/greet")
public String greet(@RequestParam String name) {
return "Hello, " + name + "!";
}
}
テストコード (MyControllerTest.java):
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.MockMvc;
@WebMvcTest(MyController.class)
public class MyControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void testGreet() throws Exception {
mockMvc.perform(get("/api/greet").param("name", "John"))
.andExpect(status().isOk())
.andExpect(content().string("Hello, John!"));
}
}
ベストプラクティス
テスト名をわかりやすくする
testAdd のように、テスト対象のメソッド名を含める。
異常系のテストを追加
エラーが予想される入力や境界値のテストを含める。
@Test
public void testAddWithOverflow() {
Calculator calculator = new Calculator();
assertThrows(ArithmeticException.class, () -> {
calculator.add(Integer.MAX_VALUE, 1);
});
}
モックとスタブの使用
外部依存を持つクラスの場合は、Mockitoなどのツールを使用して依存をモックする。
例: Mockitoを使用したモック
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;
public class ServiceTest {
@Test
public void testServiceMethod() {
Dependency mockDependency = mock(Dependency.class);
when(mockDependency.getData()).thenReturn("Mock Data");
Service service = new Service(mockDependency);
assertEquals("Mock Data", service.processData());
}
}
ユニットテストの重要性
ユニットテストを実施することで、コードの品質が向上し、変更に対する信頼性が高まります。定期的にテストを見直し、テストカバレッジを向上させましょう。