ユニットテストJavaを例に

**ユニットテストは、ソフトウェア開発において重要なテスト手法の一つで、アプリケーションの個々の部品（ユニット）を検証するために使用されます**。ここでは、ユニットテストの基本概念と実践的な方法を解説します。

ユニットテストとは

ユニットテストは、プログラムの最小単位である「ユニット」をテストするプロセスです。**一般的には、関数やメソッドがユニットとして扱われます。以下のような特性を持ちます**：

独立性: 他のコードや外部依存性に影響されない。

高速性: 短時間で実行可能。

再現性: 同じ入力で常に同じ結果を出力する。

**ユニットテストのメリット**

バグの早期発見: 小さな単位でテストを行うため、バグを迅速に特定できます。

リファクタリングの支援: テストが保証することで、安全にコードのリファクタリングが可能になります。

ドキュメントとしての役割: テストコード自体が、仕様や動作のドキュメントとなります。

Javaでのユニットテスト

以下では、Javaを使用してユニットテストを作成する方法を詳しく解説します。

基本的なテスト例

**テスト対象コード (Calculator.java):**
```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):**
```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の場合：

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.9.0</version>
    <scope>test</scope>
</dependency>

テストの実行

Mavenを使用してテストを実行します：

mvn test

**テストケースの追加**

新しいメソッドを追加した場合、その機能に対応するテストケースを作成します。

例: 掛け算機能の追加
```java
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)**
説明: 期待値と実際の値が一致するかを確認します。
例:
```java
assertEquals(5, calculator.add(2, 3));
```

**2. assertNotEquals(unexpected, actual)**
説明: 期待しない値と実際の値が異なるかを確認します。
例:
```java
assertNotEquals(0, calculator.add(2, 3));
```

**3. assertTrue(condition)**
説明: 条件が true であることを確認します。
```java
assertTrue(result > 0);
```

**4. assertFalse(condition)**
説明: 条件が false であることを確認します。
```java
assertFalse(user.isAdmin());
```

**5. assertNull(actual)**
説明: 実際の値が null であることを確認します。
```java
assertNull(user.getEmail());
```

**6. assertNotNull(actual)**
説明: 実際の値が null でないことを確認します。
```java
assertNotNull(order.getOrderId());
```

**7. assertSame(expected, actual)**
説明: 2つのオブジェクトが同じインスタンスであることを確認します（参照の一致）。
```java
assertSame(user1, user2);
```

**8. assertNotSame(unexpected, actual)**
説明: 2つのオブジェクトが異なるインスタンスであることを確認します。
```java
assertNotSame(user1, user3);
```
アサーションが失敗した場合
アサーションが失敗すると、テストは中断され、エラーメッセージが表示されます。たとえば、次のコードが失敗する例を見てみましょう。
```java
assertEquals(5, calculator.add(2, 2));
```
この場合、期待値が 5 ですが、add(2, 2) の結果が 4 だとすると、JUnitは次のようなエラーを表示します。

```yaml
expected: <5> but was: <4>
```
これにより、add メソッドに何か問題があることがわかります。



# Spring Bootでのユニットテスト

Spring Bootアプリケーションでは、特定のユニットやレイヤー（例: サービス層やリポジトリ層）をテストするためにさまざまなツールやアノテーションが用いられます。

よく使われるアノテーション

```java
@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):
```java
@Service
public class MyService {
    public String greet(String name) {
        return "Hello, " + name + "!";
    }
}
```

テストコード (MyServiceTest.java):
```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):
```java
@RestController
@RequestMapping("/api")
public class MyController {

    @GetMapping("/greet")
    public String greet(@RequestParam String name) {
        return "Hello, " + name + "!";
    }
}
```

テストコード (MyControllerTest.java):
```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 のように、テスト対象のメソッド名を含める。

異常系のテストを追加

エラーが予想される入力や境界値のテストを含める。
```java
@Test
public void testAddWithOverflow() {
    Calculator calculator = new Calculator();
    assertThrows(ArithmeticException.class, () -> {
        calculator.add(Integer.MAX_VALUE, 1);
    });
}
```

モックとスタブの使用

外部依存を持つクラスの場合は、Mockitoなどのツールを使用して依存をモックする。

例: Mockitoを使用したモック
```java
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());
    }
}
```

ユニットテストの重要性

ユニットテストを実施することで、コードの品質が向上し、変更に対する信頼性が高まります。定期的にテストを見直し、テストカバレッジを向上させましょう。