Memos About Salesforce

Salesforceにハマってたこと!

SFDC テスト例外処理/現在日付を使用ロジック カバー率アップ



SFDCのテストクラスを実装時に

元のクラスが例外処理を書く場所が

必要に応じて、あるでしょう

今回、その例外処理(Exception, catch)があるクラスのカバー率をアップするため

共有したいと思います。

欲しけりゃくれてやる・・・。

探せ!

この世の全てをそこに置いてきた〜笑

目次

クラスでは、日付処理、例外処理がある時に、

テストでは、その分岐を1つ1つテストしないとカバー率が上がらないでしょう

例えは、下記の日付と例外処理サンプルをみましょう

日付は処理日として使うと考えましょう

  • 処理日がシステム日付次第に変更するもの、
  • ロジックは処理日次第に分岐する

これだと、テストクラスを実施する日付によって、ロジックが分岐するになってしまい、

クラスのカバー率が日によって変更しちゃう

1:日付処理ロジック

pubic with sharing class myclass{
    //コンストラクタ
    public myclass(){
        
    }
    public  void myMethod(){
        String ZERO = '0';
        Date currentDate = Date.today();
        Integer thisYear = currentDate.year();
        Integer nextMon = currentDate.month() + 1;
        String setYear = '';
        String setMon = '';
        //↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
        //日付の値次第なので、nextMonが現在の日付による、ここからの分岐が現在の日付に依存
        if(nextMon > 12){
            setYear = String.valueOf(thisYear + 1);
            setMon = '1'.leftPad(2, ZERO);
        }else if(nextMon > 9){
            setYear = String.valueOf(thisYear);
            setMon = String.valueOf(nextMon);
        }else{
            setYear = String.valueOf(thisYear);
            setMon = String.valueOf(nextMon).leftPad(2, ZERO);
        }
        //↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
    }
}
1-1:クラス改善版
pubic with sharing class myclass{
    //テスト時のみアクセスできる項目
    @TestVisible private Date setTestDate;
    //コンストラクタ
    public myclass(){
        
    }
    public void myMethod(){
        String ZERO = '0';
        Date currentDate = Date.today();
        // テスト時に特別設定を許可する
        if(Test.isRunningTest() && this.setTestDate != NULL){
       		currentDate = this.setTestDate;
        }
        Integer thisYear = currentDate.year();
        Integer nextMon = currentDate.month() + 1;
        String setYear = '';
        String setMon = '';
        //↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
        //ここからの分岐が現在の日付に依存しても、テスト時に設定できるから、通るように設定すれば、OK
        if(nextMon > 12){
            setYear = String.valueOf(thisYear + 1);
            setMon = '1'.leftPad(2, ZERO);
        }else if(nextMon > 9){
            setYear = String.valueOf(thisYear);
            setMon = String.valueOf(nextMon);
        }else{
            setYear = String.valueOf(thisYear);
            setMon = String.valueOf(nextMon).leftPad(2, ZERO);
        }
        //↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
    }
}
1-2:テストクラス

テストクラスでは、日付ロジックに使う日付の値を自由に設定できれば、現在の日付に関係なく、カバー率をアップできる

@isTest
private class myclassTest {
    user excuteUser = createTestUser('スタッフ', 'システム管理者');
    /**
     * テスト内容: 日付設定検証
     */
    static testMethod void myMethodTest(){
        
        Test.startTest();
        	System.runAs(excuteUser) {
            	    myclass cls = new myclass();
                    cls.setTestDate = Date.newInstance(2018,10,11);//自由に設定
            	    cls.myMethod();
            	}
        Test.stopTest();
    }
    
    private static User createTestUser(String userName, String proName_JP){
        Profile profile;
        String setProfileId;
        profile = getStandardProfile(proName_JP);
        setProfileId = profile.id;
        User u = new User(
                            LastName = userName +  + String.valueOf(Datetime.now().second()),
                            Alias = 't1',
                            Email = 'sample1@my.test',
                            UserName = 'sample1@my.test' + String.valueOf(Datetime.now().second()),
                            CommunityNickName = 'smp1' + userName +  + String.valueOf(Datetime.now().second()),
                            ProfileId = setProfileId,
                            TimezoneSidKey = 'Asia/Tokyo',
                            LocaleSidKey = 'ja_JP',
                            EmailEncodingKey = 'ISO-2022-JP',
                            LanguageLocaleKey = 'ja'
                        );
        insert u;
        return u;
    }
}

2:例外処理ロジック

例外処理部分のカバー率をあげるには取引先のトリガーを例にさせてください、

間違えたら、コメントで指導をよろしくお願いいたします

ちょんとすれば、例外はあまり発生しないはずですが、

この例の構成はトリガクラス、トリガを補助するハンドラクラス、改善版テストクラスで共有します。

2-1:取引先のトリガクラス例
trigger AccountTrigger on Account  (after delete, after insert, after undelete, after update, before delete, before insert, before update) {

	AccountTriggerHandler handler = new AccountTriggerHandler();

	if(Trigger.isInsert && Trigger.isBefore){
		handler.onBeforeInsert(Trigger.new);

	}else if(Trigger.isInsert && Trigger.isAfter){
		handler.onAfterInsert(Trigger.new, Trigger.newMap);

	}else if(Trigger.isUpdate && Trigger.isBefore){
		handler.onBeforeUpdate(Trigger.new, Trigger.newMap, Trigger.old, Trigger.oldMap);

	}else if(Trigger.isUpdate && Trigger.isAfter){
		handler.onAfterUpdate(Trigger.new, Trigger.newMap, Trigger.old, Trigger.oldMap);

	}else if(Trigger.isDelete && Trigger.isBefore){
		handler.onBeforeDelete(Trigger.old, Trigger.oldMap);

	}else if(Trigger.isDelete && Trigger.isAfter){
		handler.onAfterDelete(Trigger.old, Trigger.oldMap);

	}else if(Trigger.isUnDelete){
		handler.onBeforeDelete(Trigger.new, Trigger.newMap);
	}
}
2-2:取引先のハンドラクラス
public with sharing class AccountTriggerHandler{
    //テストのためのプロパティを追加
    @testVisible private static Boolean isDMLException;
    private static final String THIS_CLASS_NAME = 'KeiyakuTriggerHandler';
    /**
     * @ コンストラクタ
    */
    public KeiyakuTriggerHandler(){
        //シンプルにするため、コンストラクタでは、今回、なにもしてあげない
    }
    
    /**
     * @ Event Action: Before Insert
     */
    public void OnBeforeInsert(Account[] accList){
        try{
            if(accList != NULL && !accList.isEmpty()){
                for(Account acc :accList){
                    acc.Name = 'test';
                }
            }
            //テスト中に限り、強制的にDmlExceptionを発行させる、!=NULLが大事だよ、ほかのテストクラスからこのクラスを発火してしまう時に!=nullがないと、エラーになる
            if(Test.isRunningTest() && isDMLException != NULL && isDMLException){
                insert new Account();
            }

        }catch(DMLException e){
            //ここの部分は通すには、工夫が必要
       String methodName = 'processing';
            String message = e.getMessage();
            // ここはカスタムオブジェクトを作成、ログをレコードとして記録だけ
            // めんどいから、このクラスは公開しないにさせて~
            UtilLog.outputError( THIS_CLASS_NAME, methodName, message );
        }
    }
}
2-3:ハンドラクラスクラス改善版
@isTest
private class AccountTriggerHandlerTest{
    user excuteUsert = createTestUser('スタッフ', 'システム管理者');
    /**
     * テスト内容: 取引先を登録する検証
     */
    static testMethod void beforeInsertTest(){
        Test.startTest();
        	System.runAs(excuteUsert) {
            	createAcc();
        	}
        Test.stopTest();
    }
    
    /**
     * テスト内容: 取引先を登録する検証
     */
    static testMethod void DMLTest(){
        // テスト字に特別設定するため
        AccountTriggerHandler.isDMLException = true;
        Test.startTest();
        	System.runAs(excuteUsert) {
            	createAcc();
        	}
        Test.stopTest();
    }
    private static void createAcc(){
        Account acc = new Account();
        acc.Name = 'test account';
        insert acc;
    }
    private static User createTestUser(String userName, String proName_JP){
        Profile profile;
        String setProfileId;
        profile = getStandardProfile(proName_JP);
        setProfileId = profile.id;
        User u = new User(
                            LastName = userName +  + String.valueOf(Datetime.now().second()),
                            Alias = 't1',
                            Email = 'sample1@my.test',
                            UserName = 'sample1@my.test' + String.valueOf(Datetime.now().second()),
                            CommunityNickName = 'smp1' + userName +  + String.valueOf(Datetime.now().second()),
                            ProfileId = setProfileId,
                            TimezoneSidKey = 'Asia/Tokyo',
                            LocaleSidKey = 'ja_JP',
                            EmailEncodingKey = 'ISO-2022-JP',
                            LanguageLocaleKey = 'ja'
                        );
        insert u;
        return u;
    }
}