【Salesforce】Apexテストクラスの書き方（ベストプラクティス）

#テスト用のデータはTestDataFactoryクラスで作る
TestDataFactoryにテストレコード作成処理を纏めます。
こうすることで、各々のテストクラスで行うテストレコード作成を共通化します。

【サンプルコード】

```java
public with sharing class TestDataFactory {

    public static User createTestUser(String name){
        Profile profile = getStandardProfile();
        String profileId = profile.id;

        User user = new User();
        user.Email = name + 'test@xx.xxx.com';
        user.UserName = name + 'test@xx.xxx.com';
        user.LastName = name;
        user.Alias = user.UserName.split('@')[0].left(8);
        user.ProfileId = profileId;
        user.EmailEncodingKey = 'ISO-2022-JP';
        user.LanguageLocaleKey = 'ja';
        user.LocaleSidKey = 'ja_JP';
        user.TimeZoneSidKey = 'Asia/Tokyo';
        insert user;
        return user;
    }

    public static Profile getStandardProfile(){
        List<Profile> profiles = new List<Profile>();
        profiles =  [
            SELECT Id 
            FROM Profile
            WHERE Name IN ('標準ユーザ','Standard User' )
            LIMIT 1];
        return profiles[0];
    }

    public static void assignPermissionSet (Id userId, String permissionSetName){
        PermissionSet permissionSet = [SELECT Id FROM PermissionSet WHERE Name = :permissionSetName];
        PermissionSetAssignment permissionSetAssignment = new PermissionSetAssignment(PermissionSetId = permissionSet.Id, AssigneeId = userId);
        Insert permissionSetAssignment;
    }

    public static List<Contact> createContactWithAccount( Integer numContacts ){
        List<Account> accounts = new List<Account>();

        for( Integer i=0; i<numContacts; i++ ){
            Account account = new Account(Name='TestAccount' + i);
            accounts.add(account);
        }
        insert accounts;

        List<Contact> contacts = new List<Contact>();

        for( Integer i=0; i<numContacts; i++ ){
            Contact contact = new Contact(LastName='TestContact' + i, Account=accounts[i]);
            contacts.add(contact);
        }
        insert contacts;

        return contacts;

    }

    public static List<Contact> createContactWithoutAccount( Integer numContacts ){

        List<Contact> contacts = new List<Contact>();

        for( Integer i=0; i<numContacts; i++ ){
            Contact contact = new Contact(LastName='TestContact' + i);
            contacts.add(contact);
        }
        insert contacts;

        return contacts;

    }


    public static List<Contact> createDddContactWithAccount( Integer numContacts ){
        List<Account> accounts = new List<Account>();

        for( Integer i=0; i<numContacts; i++ ){
            Account account = new Account(Name='TestAccount' + i);
            accounts.add(account);
        }
        insert accounts;

        List<Contact> contacts = new List<Contact>();

        for( Integer i=0; i<numContacts; i++ ){
            Contact contact = new Contact(LastName='TestContact' + i, Account=accounts[i], LeadSource = 'DDD');
            contacts.add(contact);
        }
        insert contacts;

        return contacts;

    }

    public static List<Contact> createDddContactWithoutAccount( Integer numContacts ){

        List<Contact> contacts = new List<Contact>();

        for( Integer i=0; i<numContacts; i++ ){
            Contact contact = new Contact(LastName='TestContact' + i, LeadSource = 'DDD');
            contacts.add(contact);
        }
        insert contacts;

        return contacts;

    }

}



```

#テストメソッド名にルールを
テストメソッドは以下の規則で名前を付けましょう。
名前だけでどのようなテストを行うかを明示的に示します。

【命名規則】
テスト対象のメソッド名＿シナリオ＿期待される振る舞い

【サンプルコード】

```java

    @isTest
    static void createNewContact_CorrectParams_ReturnId(){

        Account parentAccount = new Account();
        parentAccount.Name = 'TestAccount';
        parentAccount.Phone = '11111111111';

        Contact newContact = new Contact();
        newContact.LastName = 'Test';
        newContact.FirstName = 'Contact';

        ContactApplicationService contactApplicationService = new ContactApplicationService();

        Id newContactId;

        Test.startTest();
        System.runAs(getTestUser()){
            newContactId= contactApplicationService.createNewContact(parentAccount,newContact);
        }
        Test.stopTest();

        Contact resultContact = [ 
            SELECT Id
            FROM Contact 
            WHERE Account.Name =: parentAccount.Name AND Account.Phone =: parentAccount.Phone AND LastName =: newContact.LastName AND FirstName =: newContact.FirstName
            ];

        System.assertEquals( newContactId, resultContact.Id );

    }
```



#マジックStringを避ける
マジックStringとはこういった値です。
これだとTestという値がどのような影響を及ぼすかは分かりません。

```java
Id newAccountId = AccountService.create( 'Test' );
```
ではこれだとどうでしょう？

```java
static final String ACCOUNT_NAME = 'Test';
Id newAccountId = AccountService.create( ACCOUNT_NAME );
```
Testという値が、取引先オブジェクトの名前であることが直ぐに分かります。
コーディングした人以外にも値の意味が分かるように、定数に値の用途を吹き込みます。


#テスト対象の処理をTest.startTest()とTest.stopTest()で囲む
テストメソッド内の準備で、DML処理やSOQLを発行する事はあるかと思います。
その際、ガバナ制限である、1トランザクションあたりのDML処理上限数やSOQL発行上限数を消化してしまいます。
準備時の消費はテストに換算すべきでないので、Test.startTest()によりリセットします。
Test.stopTest()をつける事も忘れずに。

【サンプルコード】

```java

        Test.startTest();
        Id newContactId = contactApplicationService.createNewContact(parentAccount,newContact);
        Test.stopTest();

```

#System.runAsでテスト対象のユーザを指定する
System.runAs無しで実行すると、システム実行となり権限を考慮したテストが出来なくなります。
なので、必ずSystem.runAsにてテスト対象のユーザを指定して実行します。

【サンプルコード】

```java
        Test.startTest();
        System.runAs(getTestUser()){
            Id newContactId = contactApplicationService.createNewContact(parentAccount,newContact);
        }
        Test.stopTest();
```


#assertEqualsは1テストに1つ

assertEqualsを複数入れてしまうと、何をテストしたいかが不明確になります。
あと、最初のassertEqualsで失敗すると次のassertEqualsが評価されないためテストもし難くなります。