Apexテストデータ作成の汎用化

#1.テストメソッドの雛形
```java

@isTest
private class BatchAddressMasterTest {
    @isTest
    static void testXXX() {
        // テストユーザ作成
        User testUser = TestUtil.createUser('testUser');
        
        // ユーザモード
        System.runAs(testUser){
            /* ＝＝＝＝＝＝テストデータ準備＝＝＝＝＝＝ */
            // 各SObjectデータ作成
            
            /* ＝＝＝＝＝＝テストメソッド起動＝＝＝＝＝＝ */
            Test.startTest();
            // 対象メソッドを呼び出す
            Test.stopTest();
            
            /* ＝＝＝＝＝＝結果検証＝＝＝＝＝＝ */      
            // 実行結果と期待値の検証
        }
    }
}
```
#2.テストデータ作成の汎用化イメージ図
![image.png](https://qiita-image-store.s3.amazonaws.com/0/332907/4ed27e9d-257e-16cd-2907-1564789ec565.png)
#3.TestUtility
```java

/**
 * テストクラスの共通処理
 * 
 */
@isTest
public class TestUtility {
    /* ＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝ */
    /*         雛形テストデータ作成
    /* ＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝ */
    /** TestDataFactoryNewの共通処理 */
    /** テストデータパータン宣言 */
    public enum TestDataPattern {INIT_REQUIRED, INIT_ALL}
    
    /** カスタム例外 */
    private class TestDataFactoryException extends Exception {}
    
    /** 初期値設定 */
    public static InitValueSetting initValue = new InitValueSetting();
    public class InitValueSetting {
        public String nameValue; // Name:String
        public String urlValue; // URL：URL
        public Boolean checkboxValue; // チェックボックス：Boolean
        public String textValue; // テキスト：String
        public String textencryptedValue; // テキスト(暗号化)：EncryptedString
        public String textareaValue; // テキストエリア：TextArea、リッチテキストエリア：TextArea、ロングテキストエリア：TextArea
        public Decimal percentValue; // パーセント：Percent
        public String mailValue; // メール：Email
        public Time timeValue; // 時間：TIME
        public Decimal numberValue; // 数値：Double、地理位置情報(Latitude):Double、地理位置情報(Longitude):Double
        public Decimal currencyValue; // 通貨：Currency
        public String phoneValue; // 電話：Phone
        public Date dateValue; // 日付：Date
        public Datetime datetimeValue; // 日付/時間：DateTime
        
        // インスタンス変数初期化
        {
            nameValue = '1';
            urlValue = 'https://www.salesforce.com';
            checkboxValue = FALSE;
            textValue = '1';
            textencryptedValue = '1';
            textareaValue = '1';
            percentValue = 1;
            mailValue = 'xxxxx@test.co.jp';
            timeValue = Time.newInstance(0, 0, 0, 0);
            numberValue = 1;
            currencyValue = 1;
            phoneValue = '03-5255-3410';
            dateValue = system.today();
            datetimeValue = Datetime.now();
        }
    }
    
    /**
     * @description オブジェクトインスタンスの初期化
     * @parameter Schema.SObjectType
     * @parameter TestDataPattern
     * @return sObject
     */
    public static sObject getInitSObjectInstance(Schema.SObjectType sObjectType, TestDataPattern ptn) {
        // テストデータパータンにの有効性チェック
        if (ptn != TestDataPattern.INIT_REQUIRED && ptn != TestDataPattern.INIT_ALL) {
            throw new TestDataFactoryException('テストデータパターン不明のため、雛形データを作成できません。');
        }
        
        // sObjectのインスタンス作成
        sObject sObj = sObjectType.newSObject();
        
        Map<String, Schema.SObjectField> mapField = sObjectType.getDescribe().fields.getMap();
        for (SObjectField sObjField : mapField.values()) {
            Schema.DescribeFieldResult dfr = sObjField.getDescribe();
            
            // 現在のユーザが項目を編集できる場合、またはカスタムオブジェクトの主従関係項目である子レコードの親を別の親レコードに変更できる場合は true、できない場合は、false を返します。
            if (dfr.isUpdateable()) {
                // デフォルト値なし
                if (sObj.get(dfr.getSobjectField()) == null) {
                    // 必須項目のみ判定
                    if (ptn == TestDataPattern.INIT_REQUIRED && dfr.isNillable()) {
                        continue;
                    }
                    
                    // 項目の初期値設定
                    initFieldValue(sObj, dfr);
                }
            }
        }
        
        return sObj;
    }
    
    /**
     * @description 項目の初期値設定
     * @parameter sObject
     * @parameter DescribeFieldResult
     * @return void
     */
    private static void initFieldValue(sObject sObj, DescribeFieldResult dfr) {
        // Name:String
        if (dfr.isNameField()) {
            sObj.put(dfr.getSObjectField(), initValue.nameValue);
            return;
        }
        
        // URL：URL
        if(dfr.getType()==Schema.DisplayType.URL){
            sObj.put(dfr.getSObjectField(), initValue.urlValue);
            return;
        }
        
        // チェックボックス：Boolean
        if(dfr.getType()==Schema.DisplayType.Boolean){
            sObj.put(dfr.getSObjectField(), initValue.checkboxValue);
            return;
        }
        
        // テキスト：String
        if(dfr.getType()==Schema.DisplayType.String){
            sObj.put(dfr.getSObjectField(), initValue.textValue);
            return;
        }
        
        // テキスト(暗号化)：EncryptedString
        if(dfr.getType()==Schema.DisplayType.EncryptedString){
            sObj.put(dfr.getSObjectField(), initValue.textencryptedValue);
            return;
        }
        
        // テキストエリア：TextArea、リッチテキストエリア：TextArea、ロングテキストエリア：TextArea
        if(dfr.getType()==Schema.DisplayType.TextArea){
            sObj.put(dfr.getSObjectField(), initValue.textareaValue);
            return;
        }
        
        // パーセント：Percent
        if(dfr.getType()==Schema.DisplayType.Percent){
            sObj.put(dfr.getSObjectField(), initValue.percentValue);
            return;
        }
        
        // メール：Email
        if(dfr.getType()==Schema.DisplayType.Email){
            sObj.put(dfr.getSObjectField(), initValue.mailValue);
            return;
        }
        
        // 時間：Time
        if(dfr.getType()==Schema.DisplayType.Time){
            sObj.put(dfr.getSObjectField(), initValue.timeValue);
            return;
        }
        
        // 数値：Double、地理位置情報：(Latitude):Double、地理位置情報：(Longitude):Double
        if(dfr.getType()==Schema.DisplayType.Double){
            sObj.put(dfr.getSObjectField(), initValue.numberValue);
            return;
        }
        
        // 通貨：Currency
        if(dfr.getType()==Schema.DisplayType.Currency){
            sObj.put(dfr.getSObjectField(), initValue.currencyValue);
            return;
        }
        
        // 電話：Phone
        if(dfr.getType()==Schema.DisplayType.Phone){
            sObj.put(dfr.getSObjectField(), initValue.phoneValue);
            return;
        }
        
        // 日付：Date
        if(dfr.getType()==Schema.DisplayType.Date){
            sObj.put(dfr.getSObjectField(), initValue.dateValue);
            return;
        }
        
        // 日付/時間：DateTime
        if(dfr.getType()==Schema.DisplayType.DateTime){
            sObj.put(dfr.getSObjectField(), initValue.datetimeValue);
            return;
        }
        
        // 選択リスト：Picklist
        if(dfr.getType()==Schema.DisplayType.Picklist){
            List<Schema.PicklistEntry> picklistEntries = dfr.getPicklistValues();
            for (Schema.PicklistEntry item : picklistEntries) {
                if (item.isActive()) {
                    sObj.put(dfr.getSObjectField(), item.getValue());
                    break;
                }
            }
            return;
        }
        
        // 複数選択リスト：MultiPicklist
        if(dfr.getType()==Schema.DisplayType.MultiPicklist){
            List<Schema.PicklistEntry> picklistEntries = dfr.getPicklistValues();
            for (Schema.PicklistEntry item : picklistEntries) {
                if (item.isActive()) {
                    sObj.put(dfr.getSObjectField(), item.getValue());
                    break;
                }
            }
            return;
        }
    }
    
    /* ＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝ */
    /*         テストデータ取得
    /* ＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝ */
    /** SOQL文Template */
    private static String strQueryTemplate = 'select {fields} from {sobject} {where} order by id asc';

    /** 部分的な項目を取得 */
    public static List<sObject> getTesDataSelectFields(Schema.SObjectType sObjType) {
        return getTesDataSelectFields(sObjType, null, null);
    }
    
    public static List<sObject> getTesDataSelectFields(Schema.SObjectType sObjType, List<Schema.SObjectField> selectFields) {
        return getTesDataSelectFields(sObjType, selectFields, null);
    }
    
    public static List<sObject> getTesDataSelectFields(Schema.SObjectType sObjType, List<Id> idList) {
        return getTesDataSelectFields(sObjType, null, idList);
    }
    
    public static List<sObject> getTesDataSelectFields(Schema.SObjectType sObjType, List<Schema.SObjectField> selectFields, List<Id> idList) {
        // SOQLの取得項目セット
        Set<String> fieldNameSet = new Set<String>();
        
        if (selectFields != null && selectFields.size() > 0) {
            for (Schema.SObjectField item : selectFields) {
                fieldNameSet.add(item.getDescribe().getName().toLowerCase());
            }
        }
        
        // SOQL取得項目が存在しない場合、Idを設定する
        if (fieldNameSet.isEmpty()) {
            fieldNameSet.add('Id');
        }
        
        // 検索用SOQL文作成
        String strQuery = strQueryTemplate;
        
        // オブジェクト設定
        strQuery = strQuery.replace('{sobject}', sObjType.getDescribe().getName());
        
        // 取得項目設定
        strQuery = strQuery.replace('{fields}', String.join(new List<String>(fieldNameSet), ','));
        
        // where条件設定
        if (idList != null && idList.size() > 0) {
            strQuery = strQuery.replace('{where}', 'where id=:idList');
        } else {
            strQuery = strQuery.replace('{where}', '');
        }
        
        // SOQL発行
        return Database.Query(strQuery);
    }
    
    /** 全項目を取得 */
    public static List<sObject> getTesDataAllFields(Schema.SObjectType sObjectType) {
        return getTesDataAllFields(sObjectType, null, null);
    }
    
    public static List<sObject> getTesDataAllFields(Schema.SObjectType sObjType, List<Schema.SObjectField> referenceFields) {
        return getTesDataAllFields(sObjType, referenceFields, null);
    }
    
    public static List<sObject> getTesDataAllFields(Schema.SObjectType sObjType, List<Id> idList) {
        return getTesDataAllFields(sObjType, null, idList);
    }
    
    public static List<sObject> getTesDataAllFields(Schema.SObjectType sObjType, List<Schema.SObjectField> referenceFields, List<Id> idList) {
        // SOQLの取得項目セット
        Set<String> fieldNameSet = new Set<String>();
        
        // SObjectのすべて項目を取得する
        Map< String, Schema.sObjectField > sObjectFieldMap = sObjType.getDescribe().fields.getMap();
        for(String fieldName : sObjectFieldMap.keySet()){
            fieldNameSet.add(fieldName.toLowerCase());
        }
        
        // 参照先項目
        if (referenceFields != null && referenceFields.size() > 0) {
            for (Schema.SObjectField item : referenceFields) {
                fieldNameSet.add(item.getDescribe().getName().toLowerCase());
            }
        }
        
        // 検索用SOQL文作成
        String strQuery = strQueryTemplate;
        
        // オブジェクト設定
        strQuery = strQuery.replace('{sobject}', sObjType.getDescribe().getName());
        
        // 取得項目設定
        strQuery = strQuery.replace('{fields}', String.join(new List<String>(fieldNameSet), ','));
        
        // where条件設定
        if (idList != null && idList.size() > 0) {
            strQuery = strQuery.replace('{where}', 'where id=:idList');
        } else {
            strQuery = strQuery.replace('{where}', '');
        }
        
        system.debug(strQuery);
        
        // SOQL発行
        return Database.Query(strQuery);
    }
}
```
#4.TestDataFactory
```java

/**
 * 各オブジェクトのテストデータ生成Factory
 * 
 */
@isTest
public class TestDataFactory {
    /**
     * テストデータ：XXXXXX
     */
    public static XXXXXX__c getTestDataXXXXXX(TestUtility.TestDataPattern testDataPattern) {
        // テストデータのTemplateデータを取得する
        XXXXXX__c testData = (XXXXXX__c)TestUtility.getInitSObjectInstance(XXXXXX__c.sObjectType, testDataPattern);
        
        // テストデータの個別設定部分
        // 例：文字列(YYYYMMDD,YYYYMM,YYYYなど)、制御項目と連動項目、範囲データ等
        
        return testData;
    }
}
```