まえおき
Saleforceには75%以上カバレッジがないとクラスをデプロイできないという規約がある。
素晴らしい規約。だが単体テストを"カバレッジを通すため"に実装する
様々な禁忌が犯されているケースは少なくない。
「なんのための単体テストなん?なんのための規約なん?」
そんな光景を発見しては治しの連続。
ちゃんとTDDや単体テストの書き方について勉強されてる方には恥ずかしい限りだが
以下の願いをこめて単体テストの治し方について書くことにした。
- 単体テストを"単体テスト"として書く人が少しでも増えるように
- 禁忌のケースを見つけても素通りせず"呼吸するように治す"人が増えるように
※ほぼjavaクラスとJUnitと同じなのでjavaラベルつけた。
対象の実行クラス
Dto1__c というオブジェクトのtrigger内のafter update時に起動するこんなクラスだとします。
public with sharing class DtoHandler{
public static void execute(Dto__c dto){
if(dto.a == 'x'){
// dto2オブジェクト にinsertする処理
insert new Dto2();
}
}
}
治す前
こんなでした。
@isTest static void testExecute(){
Dto__c dto = new Dto(a='x', b='y');
insert dto;
update dto:
}
お、おう。トリガーでこう動くんだからまあカバレッジは上がるよね。
trigger DtoTrigger on Dto__c (after update) {
DtoHandler.execute(Trigger.New);
munyamunya();
}
えっと単体テストがfailしないってことはトリガーが失敗せずに実行される。
ってことまでは担保できてそうだ。(担保?)
これはtriggerのテストなので testDtoTrigger() にしよう。
で、testExecute()は今の単体テストだとmunyamunya() も実行してるけど?
っていうかデータ更新だけ実施して何がわかるの?ってことで。
治した後
/*
* DtoTriggerのテストメソッド
*/
@isTest static void testDtoTrigger(){
Test.startTest();
Dto__c dto = new Dto(a = 'x');
insert dto;
//DtoTrigger.trigger updateの実行
//expect:update実行時にerror発生しない
dto.a = 'y';
update dto;
Test.stopTest();
}
/*
* executeメソッドのテストメソッド
*/
@isTest static void testExecute(){
Test.startTest();
//dto生成
Dto__c dto = new Dto(a='y');
//a = 'x'以外の場合レコードはinsertされない
DtoHandler.execute(dto);
Dto2__c dto2_1 = [SELECT Id FROM Dto2__c];
System.assertEquals(0,dto2_1.size());
//a = 'x'の場合dto2のレコードがinsertされる
dto.a = 'y';
DtoHandler.execute(dto);
Dto2__c dto2_2 = [SELECT Id FROM Dto2__c];
System.assertEquals(1,dto2_2.size());
Test.stopTet();
}
治し方ポイント
- 単体テストのDTOに余計な項目は入れない。
- トリガー単位ではなく、もっと単体(メソッド単位)でのテストをする。
- コメントや書き方を工夫して"何のテストをしてるのか"極力わかりやすくする。
- トリガーのテストは超最小限(にできる実装の方がテストの粒度を細かく実施できる。)
つぶやき
TDDに慣れてない人や、TDDやりたいけどいまいちスピードが出ない。。という人は
このように残念な単体テストをひたすら直し続けると自ずと
「あ、単体テストから書いた方がわかりやすい」ってなると思う。
せっかく良い規約があるんだからちゃんと活用しましょ。