Spring Bootにおける単体テストと結合テストの違い
はじめに
Spring Bootのテストと格闘していたら、テストコードにおいて単体テストと結合テストという概念があることを知り、なんとなく違いがわかったので、書き残しておきます。
単体テスト
Spring Bootにおける単体テスト(Unit Test)とは、他のオブジェクトやインフラストラクチャに依存しない形で実装するテストのことです。
他の要素に依存しないため、実行速度が速いという特徴があります。
例えば、以下のようなServiceの処理があったとします。
@Service
public class ItemService {
private final ItemRepository itemRepository;
public ItemService(ItemRepository itemRepository) {
this.itemRepository = itemRepository;
}
public Optional<Item> getItemById(Long id) {
return itemRepository.findById(id);
}
public Item saveItem(Item item) {
return itemRepository.save(item);
}
}
単体テストの場合、自身の処理のみをテストし、依存する外部の処理はMockなどを活用して全て代替処理として実行します。
以下、単体テストの実装例です。
@ExtendWith(MockitoExtension.class)
public class ItemServiceTest {
@Mock
private ItemRepository itemRepository;
@InjectMocks
private ItemService itemService;
@Test
public void getItemByIdTest() {
Item item = new Item();
item.setId(1L);
item.setName("Item1");
item.setPrice(100.0);
when(itemRepository.findById(1L)).thenReturn(Optional.of(item));
Optional<Item> found = itemService.getItemById(1L);
assertTrue(found.isPresent());
assertEquals("Item1", found.get().getName());
}
@Test
public void saveItemTest() {
Item item = new Item();
item.setName("NewItem");
item.setPrice(200.0);
when(itemRepository.save(any(Item.class))).thenReturn(item);
Item savedItem = itemService.saveItem(item);
assertNotNull(savedItem);
assertEquals("NewItem", savedItem.getName());
}
}
結合テスト
Spring Bootにおける結合テスト(Integration Test)とは、複数のコンポーネントやシステムが統合された状態で、アプリケーションが正しく動作するかを検証するテストです。
これには、データベース、ファイルシステム、外部サービスとの連携が含む場合があります。
実際の依存関係を使用して実行されるため、基本的に単体テストより長い実行時間がかかりますが、アプリケーションの各部分が互いに正しく連携していることを検証できます。
単体テストのところで用意したItemServiceに対して、結合テストを実装すると以下のようになります。
@SpringBootTest
public class ItemServiceIntegrationTest {
@Autowired
private ItemService itemService;
@Autowired
private ItemRepository itemRepository;
@Test
public void testGetItemById() {
// Given
Item item = new Item();
item.setName("Test Item");
item.setPrice(100.0);
item = itemRepository.save(item);
// When
Optional<Item> foundItem = itemService.getItemById(item.getId());
// Then
assertTrue(foundItem.isPresent());
assertEquals("Test Item", foundItem.get().getName());
assertEquals(100.0, foundItem.get().getPrice());
}
@Test
public void testSaveItem() {
// Given
Item newItem = new Item();
newItem.setName("New Item");
newItem.setPrice(200.0);
// When
Item savedItem = itemService.saveItem(newItem);
// Then
assertNotNull(savedItem.getId());
assertEquals("New Item", savedItem.getName());
assertEquals(200.0, savedItem.getPrice());
}
}
SpringBootの結合テストの場合、@SpringBootTest
アノテーションを使用して、Spring Bootの全アプリケーションコンテキストをテスト時にロードします。
これにより、ItemService
とItemRepository
が実際のアプリケーション環境と同様に動作することを保証します。
単体テストと結合テストの使い分け
実際にはプロジェクトの方針によりますが、基本的には、最初に実行時間が短く依存度が低い単体テストを中心に行い品質を確保し、その後に結合テストの比重を高めて全体の品質を保証するのが良いと考えます。
また、CI環境では、単体テストと結合テストの両方を自動的に実行することが望ましいです。
おわりに
単体テストと結合テスト、正直違いが感覚的に把握できてなかったのですが、実際のコードであれこれ試してみたことで、色々理解が進みました。満足です。
Discussion