Part 6: テストクラスでのエラーハンドリングとカスタム例外の活用

## **Part 6: テストクラスでのエラーハンドリングとカスタム例外の活用**  

前回は **ガバナ制限を考慮したエラーハンドリング** と **バッチ処理の例外処理** について解説しました。  
今回は **テストクラスでのエラーハンドリング** と **カスタム例外の活用** に焦点を当てます。  

---

## **1. テストクラスでのエラーハンドリング**  

Salesforce では、**Apex コードの 75% 以上をカバーするテストクラスが必須** です。  
特に **エラーハンドリングを適切にテストしないと、実際の運用時に問題が発生する可能性** があります。

---

### **1.1. `System.assertException()` で例外発生を確認**
**例外が正しくスローされるかを確認するには `System.assertException()` を使います。**

#### **✅ OK 例: `try-catch` のテスト**
```apex
@isTest
private class SampleTest {
    @isTest
    static void testExceptionHandling() {
        try {
            MyClass.methodThatThrowsException();
            System.assert(false, '例外が発生するはずなのに、発生しませんでした');
        } catch (CustomException e) {
            System.assertEquals('エラーメッセージ', e.getMessage());
        }
    }
}
```
このテストでは、**例外が発生することを前提に `System.assertException()` で検証** しています。

---

### **1.2. `Test.startTest()` / `Test.stopTest()` を使ったテスト**
バッチ処理や非同期処理のエラーハンドリングをテストする場合、  
**`Test.startTest()` / `Test.stopTest()` を使って非同期処理を強制実行** します。

#### **✅ OK 例: バッチ処理の例外テスト**
```apex
@isTest
private class BatchErrorTest {
    @isTest
    static void testBatchErrorHandling() {
        // テストデータ作成
        List<Account> accounts = new List<Account>();
        for (Integer i = 0; i < 200; i++) {
            accounts.add(new Account(Name = 'Test Account ' + i));
        }
        insert accounts;

        // バッチ実行
        Test.startTest();
        BatchApexExample batch = new BatchApexExample();
        Database.executeBatch(batch, 50);
        Test.stopTest();
        
        // エラーログを検証
        List<ErrorLog__c> logs = [SELECT Message__c FROM ErrorLog__c];
        System.assert(logs.size() > 0, 'エラーログが作成されているはず');
    }
}
```
このテストでは、**バッチ処理のエラーを再現し、エラーログが作成されることを検証** しています。

---

## **2. カスタム例外 (`CustomException`) の活用**  

標準の `Exception` だけでなく、**カスタム例外を作成するとエラーハンドリングがより明確になります**。  

---

### **2.1. カスタム例外の定義**
Apex では **`extends Exception` を使って独自の例外を作成** できます。

#### **✅ OK 例: カスタム例外 `InvalidDataException`**
```apex
public class InvalidDataException extends Exception {}
```
このように **特定のエラーを識別するためのカスタム例外を定義** できます。

---

### **2.2. カスタム例外の活用**
**データのバリデーション時に `InvalidDataException` を使う** ことで、  
エラーの種類を明確に分けられます。

#### **✅ OK 例: メソッドでカスタム例外をスロー**
```apex
public class AccountService {
    public static void validateAccount(Account acc) {
        if (String.isEmpty(acc.Name)) {
            throw new InvalidDataException('アカウント名が空です');
        }
    }
}
```
この `validateAccount()` メソッドは、**アカウント名が空なら `InvalidDataException` をスロー** します。

---

### **2.3. カスタム例外を使ったテスト**
カスタム例外が **正しく発生することを検証** します。

#### **✅ OK 例: `InvalidDataException` のテスト**
```apex
@isTest
private class AccountServiceTest {
    @isTest
    static void testValidateAccount() {
        try {
            Account acc = new Account();
            AccountService.validateAccount(acc);
            System.assert(false, '例外が発生するはずなのに、発生しませんでした');
        } catch (InvalidDataException e) {
            System.assertEquals('アカウント名が空です', e.getMessage());
        }
    }
}
```
**このテストでは、`validateAccount()` が正しく例外をスローすることを確認** しています。

---

## **3. 例外処理のベストプラクティス**
### **3.1. 例外の種類を明確に分ける**
- **`DmlException`** → DML 操作の失敗時 (`insert/update/delete` のエラー)
- **`QueryException`** → SOQL クエリのエラー (`List has no rows for assignment`)
- **`LimitException`** → ガバナ制限 (`Too many SOQL queries` など)
- **`CustomException`** → 独自のエラー (`InvalidDataException` など)

---

### **3.2. `try-catch` を使う際のポイント**
- **キャッチする例外は最小限にする**  
  ```apex
  try {
      insert account;
  } catch (DmlException e) {
      System.debug('DMLエラー: ' + e.getMessage());
  }
  ```
  **NG 例:** `Exception e` で全てキャッチしてしまう
  ```apex
  try {
      insert account;
  } catch (Exception e) { // すべての例外をキャッチするのは危険
      System.debug('エラー: ' + e.getMessage());
  }
  ```
  すべての例外をキャッチすると、**本来のエラー原因が分かりにくくなる** ため注意。

---

### **3.3. バッチ処理で例外を記録**
- **エラーが発生したレコードはスキップし、エラーログを記録**
  ```apex
  global void execute(Database.BatchableContext bc, List<Account> scope) {
      List<Account> successList = new List<Account>();
      
      for (Account acc : scope) {
          try {
              insert acc;
              successList.add(acc);
          } catch (DmlException e) {
              ErrorLog__c log = new ErrorLog__c(Message__c = e.getMessage());
              insert log;
          }
      }
  }
  ```

---

## **まとめ**
今回は **テストクラスでのエラーハンドリング** と **カスタム例外の活用** について解説しました。

- **`System.assertException()` を使って例外発生を確認**
- **`Test.startTest()` / `Test.stopTest()` で非同期処理をテスト**
- **カスタム例外 (`CustomException`) を作成し、明確なエラーハンドリング**
- **`try-catch` で適切に例外をキャッチし、ログを記録**

> **次の Part 7** では、**エラーハンドリングの設計パターン (エラーログ管理・リトライ機構) を解説** します！