元のクラスが例外処理を書く場所が
必要に応じて、あるでしょう
今回、その例外処理(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; } }