Lv. 3 の解答
アウトライン
1. Apex テストクラスを作成
1-1. Apex テストを実行して現在のコードカバー率を確認します。
$Env:SFDX_IMPROVED_CODE_COVERAGE = "true"
sfdx force:apex:test:run -c -l RunLocalTests -r human -u demo
なお、環境変数
SFDX_IMPROVED_CODE_COVERAGE
に対して
true
が設定されているかどうかを確認したい場合は、次のコマンドを実行してみてください。
$Env:SFDX_IMPROVED_CODE_COVERAGE
=== Test Summary
NAME VALUE
─────────────────── ─────────────────────────────────────────────────────────
Outcome Passed
Tests Ran 44
Passing 44
Failing 0
Skipped 0
Pass Rate 100%
Fail Rate 0%
Test Run Coverage 96%
Org Wide Coverage 90%
(...以下省略...)
AccountTriggerServiceTest.cls
1-2.
AccountTriggerService.cls
のテストクラスを作成します。
sfdx force:apex:class:create -d force-app/test/default/classes -n AccountTriggerServiceTest -t ApexUnitTest
AccountTriggerServiceTest.cls
@isTest(SeeAllData=false)
private class AccountTriggerServiceTest {
private static AccountTriggerService service = new AccountTriggerService();
private static FAT_CommonTriggerHandler handler = FAT_CommonTriggerHandler.create(
Account.class
);
@isTest
static void addPrefixToName() {
List<Account> accounts = new List<Account>();
Account account = new Account();
account.Name = 'Demo';
accounts.add(account);
Test.startTest();
service.addPrefixToName(accounts);
Test.stopTest();
System.assertEquals('[サンプル] Demo', accounts[0].Name, 'addPrefixToName');
}
@isTest
static void setCustomerPriorityHigh() {
List<Account> accounts = new List<Account>();
Account account = new Account();
account.Rating = 'Hot';
accounts.add(account);
Test.startTest();
service.setCustomerPriority(accounts);
Test.stopTest();
System.assertEquals(
'High',
accounts[0].CustomerPriority__c,
'setCustomerPriorityHigh'
);
}
@isTest
static void setCustomerPriorityMedium() {
List<Account> accounts = new List<Account>();
Account account = new Account();
account.Rating = 'Warm';
accounts.add(account);
Test.startTest();
service.setCustomerPriority(accounts);
Test.stopTest();
System.assertEquals(
'Medium',
accounts[0].CustomerPriority__c,
'setCustomerPriorityMedium'
);
}
@isTest
static void setCustomerPriorityLow() {
List<Account> accounts = new List<Account>();
Account account = new Account();
account.Rating = 'Cold';
accounts.add(account);
Test.startTest();
service.setCustomerPriority(accounts);
Test.stopTest();
System.assertEquals(
'Low',
accounts[0].CustomerPriority__c,
'setCustomerPriorityLow'
);
}
@isTest
static void onBeforeInsert() {
List<Account> accounts = new List<Account>();
handler.newObjects = accounts;
Test.startTest();
service.onBeforeInsert(handler);
Test.stopTest();
System.assertNotEquals(null, handler, 'onBeforeInsert');
}
@isTest
static void onBeforeUpdate() {
List<Account> accounts = new List<Account>();
handler.newObjects = accounts;
Test.startTest();
service.onBeforeUpdate(handler);
Test.stopTest();
System.assertNotEquals(null, handler, 'onBeforeInsert');
}
@isTest
static void onBeforeDelete() {
Test.startTest();
service.onBeforeDelete(handler);
Test.stopTest();
System.assertNotEquals(null, handler, 'onBeforeDelete');
}
@isTest
static void onAfterInsert() {
Test.startTest();
service.onAfterInsert(handler);
Test.stopTest();
System.assertNotEquals(null, handler, 'onAfterInsert');
}
@isTest
static void onAfterUpdate() {
Test.startTest();
service.onAfterUpdate(handler);
Test.stopTest();
System.assertNotEquals(null, handler, 'onAfterUpdate');
}
@isTest
static void onAfterDelete() {
Test.startTest();
service.onAfterDelete(handler);
Test.stopTest();
System.assertNotEquals(null, handler, 'onAfterDelete');
}
@isTest
static void onAfterUndelete() {
Test.startTest();
service.onAfterUndelete(handler);
Test.stopTest();
System.assertNotEquals(null, handler, 'onAfterUndelete');
}
}
1-3. コードをフォーマットします。
yarn prettier
1-4. スクラッチ組織へプッシュします。
sfdx force:source:push -u demo
1-5. Apex テストを実行して現在のコードカバー率を確認します。
$Env:SFDX_IMPROVED_CODE_COVERAGE = "true"
sfdx force:apex:test:run -c -l RunLocalTests -r human -u demo
=== Test Summary
NAME VALUE
─────────────────── ─────────────────────────────────────────────────────────
Outcome Passed
Tests Ran 55
Passing 55
Failing 0
Skipped 0
Pass Rate 100%
Fail Rate 0%
Test Run Coverage 97%
Org Wide Coverage 96%
(...以下省略...)
2. Apex テストクラスを作成
2-1. AccountTrigger.trigger のコードカバー率を向上させるために起動条件を追加します。 安心してください、カスタムメタデータ型で制御しているため実際の動作には影響ありません。
trigger AccountTrigger on Account(
before insert,
before update,
before delete,
after insert,
after update,
after delete,
after undelete
) {
FAT_CommonTriggerHandler handler = FAT_CommonTriggerHandler.create(
Account.class
);
handler.invoke();
}
AccountTestUtils.cls
2-2.
AccountTrigger.trigger
のテストクラスを作成します。
sfdx force:apex:class:create -d force-app/test/default/classes -n AccountTestUtils -t ApexUnitTest
AccountTestUtils.cls
@SuppressWarnings('PMD.ApexDoc')
@isTest(SeeAllData=false)
public with sharing class AccountTestUtils {
public static List<Account> createNormalAccounts() {
List<Account> accounts = new List<Account>();
Account account1 = new Account();
account1.Name = 'Demo1';
account1.Rating = 'Hot';
accounts.add(account1);
return accounts;
}
public static List<Account> selectAccounts() {
return [
SELECT Id, Name, Rating, CustomerPriority__c
FROM Account
ORDER BY Name ASC
LIMIT 50000
];
}
public static void insertAccounts(List<Account> accounts) {
List<Database.SaveResult> results = Database.insert(accounts, false);
}
public static void updateAccounts(List<Account> accounts) {
List<Database.SaveResult> results = Database.update(accounts, false);
}
public static void deleteAccounts(List<Account> accounts) {
List<Database.DeleteResult> results = Database.delete(accounts, false);
}
public static void undeleteAccounts(List<Account> accounts) {
List<Database.UndeleteResult> results = Database.undelete(accounts, false);
}
}
AccountTriggerTest.cls
sfdx force:apex:class:create -d force-app/test/default/classes -n AccountTriggerTest -t ApexUnitTest
AccountTriggerTest.cls
@isTest(SeeAllData=false)
private class AccountTriggerTest {
@testSetup
static void setup() {
List<Account> accounts = AccountTestUtils.createNormalAccounts();
AccountTestUtils.insertAccounts(accounts);
}
@isTest
static void invokeUpdate() {
List<Account> accounts = AccountTestUtils.selectAccounts();
Test.startTest();
AccountTestUtils.updateAccounts(accounts);
Test.stopTest();
System.assertNotEquals(0, accounts.size(), 'invokeUpdate');
}
@isTest
static void invokeDelete() {
List<Account> accounts = AccountTestUtils.selectAccounts();
AccountTestUtils.deleteAccounts(accounts);
Test.startTest();
AccountTestUtils.undeleteAccounts(accounts);
Test.stopTest();
System.assertNotEquals(0, accounts.size(), 'invokeDelete');
}
@isTest
static void invokeException() {
// TODO
}
}
2-3. コードをフォーマットします。
yarn prettier
2-4. スクラッチ組織へプッシュします。
sfdx force:source:push -u demo
2-5. Apex テストを実行して現在のコードカバー率を確認します。
$Env:SFDX_IMPROVED_CODE_COVERAGE = "true"
sfdx force:apex:test:run -c -l RunLocalTests -r human -u demo
=== Test Summary
NAME VALUE
─────────────────── ─────────────────────────────────────────────────────────
Outcome Passed
Tests Ran 58
Passing 58
Failing 0
Skipped 0
Pass Rate 100%
Fail Rate 0%
Test Run Coverage 99%
Org Wide Coverage 99%
(...以下省略...)
コードカバー率 100%まであと 1%です。後ほど対応しましょう。
...いかがでしょう、だいぶテストクラスが書きやすくなっていませんか? もしもトリガからビジネスロジックを分離していなかったら、ビジネスロジックをテストするためにわざわざ insert などして無理やり Apex トリガを起動させないといけませんでした。
今回はビジネスロジックに集中してテストコードを書けば良くなっているので、ちゃんと細かく単体テストしてる気持ちになれるのではないでしょうか。 将来ビジネスロジックが複雑になったとしても、ホワイトボックステストを書きやすいですよね。