Apexテストについて
- 各Apexクラスやメソッドが想定通りに動作することを確認するために必要
- コードカバレッジは最低75%以上かつApexテストでエラーが無ければリリースは可能
- 基本的にはカバレッジ100%を目指す
- Exceptionやコールアウトなど、テストコードだけでは通せない箇所も存在するため、そこはレビュアーと相談する
- バッチのテストは、非同期なので、検証の実行方法は下記を参照する
https://qiita.com/naoto_koyama/items/292eff57c780b2ee8c6b
ポイント:TestのStartとStopの間にDatabase.executeBatch(bc,nn)で実行する
// テストの開始
Test.startTest();
AccountBatch batchable = new AccountBatch();
Database.executeBatch(batchable);
Test.stopTest();
Apexテストでよく使用するアノテーション
@isTest
Apexクラス、ApexメソッドがApexテストに使用されるものだと示すもの
(例)
@isTest
private class hogeClassTest {
@isTest static void test_fugaMethod() {
}
@isTest static void test_piyoMethod() {
}
}
@TestVisible
private変数やメソッドなどをテストクラスから直接呼び出して確認する際に使用するもの
(テストクラスではない他のクラスからはアクセスできないため、普段の操作に影響はない)
(例)
// テスト対象のApexクラス
public class hogeClass {
@TestVisible private static Integer fugaNum = 1;
private static Integer piyoNum = 2;
@TestVisible private static String fugaMethod() {
return 'fugafuga';
}
private static String piyoMethod() {
return 'piyopiyo';
}
}
// Apexテストクラス
@isTest
private class hogeClassTest {
@isTest static void test_fugaMethod() {
// @TestVisibleが付いているため参照可能
Integer num = hogeClass.fugaNum;
System.assertEquales(1, num);
// @TestVisibleが付いているため参照可能
String result = hogeClass.fugaMethod();
System.assertEquales('fugafuga', result);
}
@isTest static void test_piyoMethod() {
// @TestVisibleが付いていないためエラーとなる
Integer num = hogeClass.piyoNum;
// @TestVisibleが付いているためエラーとなる
String result = hogeClass.piyoMethod();
}
}
@TestSetup
そのApexテストクラス内で共通して使いたいテストレコードの作成時に使用する
項目の値が異なるようなレコードは、個別のメソッド内で作成した方がわかりやすい場合もある
(例)
@isTest
private class hogeClassTest {
@TestSetup static void setUp() {
Account acc = new Account(Name = 'Test Account');
insert acc;
}
@isTest static void test_fugaMethod() {
// 作成済みのテストレコードを取得
Account acc = [SELECT Id, Name FROM Account WHERE Name = 'Test Account'];
System.assertEquals(1, acc.size());
}
@isTest static void test_piyoMethod() {
// 各メソッドで参照可能
Account acc = [SELECT Id, Name FROM Account WHERE Name = 'Test Account'];
System.assertEquals(1, acc.size());
}
}
Assertについて
各テストメソッドでは、メソッドを呼んで通ったからおしまいではなく、
Assertを使用して期待通りの結果となっていることを確認することが重要
Assertが無いと、期待通りになっていなくてもテストメソッド自体は正常に終了しているためエラーとならない
Assertを使用しないNG例
// テスト対象のApexクラス
public with sharing class Hoge {
public static String init(String param) {
if (String.isEmpty(param)) {
// paramが空の場合エラー
return 'エラー';
}
return '正常';
}
}
// Apexテストクラス
@isTest
public with sharing class HogeTest {
@isTest
public static void test_init_success() {
String result = Hoge.init('');
// 正常のテストを実施するメソッドだが、これでは実際に通っているのはエラーのルート
// Asesrtが無いため期待通りの結果にならないことに気が付けない
}
}
Assertを使用する例
// テスト対象のApexクラス
public with sharing class Hoge {
public static String init(String param) {
if (String.isEmpty(param)) {
// paramが空の場合エラー
return 'エラー';
}
return '正常';
}
}
// Apexテストクラス
@isTest
public with sharing class HogeTest {
@isTest
public static void test_init_success() {
String result = Hoge.init('');
// Assertを記載しているためここでエラーとなり期待値誤りに気が付ける
System.AssertEquals('正常', result);
}
}
テストレコードについて
基本的にApexテストクラスから実レコードにアクセスすることはできないが、一部例外のオブジェクトやアノテーションを使用することでアクセスすることが可能
実レコードが参照可能なオブジェクトの例
- User
- Profile
- PermissionSet
プロファイル、権限セットについては実レコードを使用
ユーザはテスト用のレコードを用意し、上記プロファイル、権限セットを割り当てることでテスト用ユーザを作成するのが一般的
System.runAs
を使用しない場合、テスト実行ユーザの権限で実行されるため、基本的にはシステム管理者で実行される
想定ユーザとなるように権限を設定したユーザレコードを用意してテストすること、で画面上での動作と差異が出ることが少なくなるのでオススメ
@isTest(SeeAllData=true) アノテーションについて
Apexクラス、Apexテストメソッドに指定することで、実レコードのクエリができるようになる
作成、変更(更新)、削除もできるが、ロールバックされる
クラスに指定した場合、そのApexテストクラス内すべてが SeeAllData=true
となる
有効的な使い方は不明(どうしても用意が難しいレコードがある場合に使用する?)
仮に実レコードを使用している場合、その条件に合うレコードが環境に存在しないと想定通りに動作しないため、
環境毎(各Sandobox、本番)に同じレコードを用意しておく必要があり、動作の保証が大変な気がする
(例)
@isTest
private class hogeClassTest {
@isTest(SeeAllData=true)
static void test_fugaMethod() {
// 環境に存在するレコードを取得
Account acc = [SELECT Id, Name FROM Account WHERE Name = 'Hoge Account'];
System.assertEquals(1, acc.size());
}
}
テストレコードの作成について
- 標準的なレコードの作成は共通クラス
TestDataFactory
のようなところに集約させ、
各Apexメソッドから呼び出して作成する - 項目の値が異なるものを用意する場合、各Apexメソッドで用意する or 共通クラス内に用意する
NG例1
同じオブジェクトに対してレコードの指定、insertのメソッドが複数存在する
仮にこのオブジェクトに必須項目が増えた場合、それぞれのメソッドでその項目に値を設定する必要が出てくるため少々メンテナンス性が低い
public static Hoge__c createHogeA() {
Hoge__c hoge = new Hoge__c (
fuga__c = 'fugafuga',
piyo__c = 100
);
insert hoge;
return hoge;
}
public static Hoge__c createHogeB() {
Hoge__c hoge = new Hoge__c (
fuga__c = 'fuganfugan',
piyo__c = 100
);
insert hoge;
return hoge;
}
public static Hoge__c createHogeC() {
Hoge__c hoge = new Hoge__c (
fuga__c = 'fufugaga',
piyo__c = 100
);
insert hoge;
return hoge;
}
NG例2
共通メソッドの呼び出し後、未設定の項目を個別に設定してupdateをかけている
他のテストクラスへの影響度合いやバリエーションのテストにもよるが、
同じ項目を同じ値で設定するなら共通クラスに項目を追加して対応する方が冗長な記述とならなくて済む
// テストデータ作成用クラス
@isTest
public class TestDataFactory {
public static Hoge__c createHoge() {
Hoge__c hoge = new Hoge__c (
fuga__c = 'fugafuga'
);
insert hoge;
return hoge;
}
}
// Apexテストクラス
// 同じ項目を同じ値で更新している
@isTest
public with sharing class RMS_UtilTest {
@isTest
public static void test_hogeMethodA() {
Hoge__c hoge = TestDataFactory.createHoge();
hoge.piyo__c = 100;
update hoge;
・・・
}
@isTest
public static void test_hogeMethodB() {
Hoge__c hoge = TestDataFactory.createHoge();
hoge.piyo__c = 100;
update hoge;
・・・
}
}
改善案
- insertは1つのメソッドにしてしまい、バリエーションが必要な項目は引数で値をもらったりすることで対応したい
- ただし、あまりにも細かいバリエーションが必要な場合は、個別のテストクラス、テストメソッド内で用意した方が賢明な場合もあるので、ケースバイケースにはなる
→ 気になる場合はレビュアーと相談して決めると良い
NG例1に対する改善案1
insert自体は1つのprivateメソッドにしてしまい、共通クラス内からそのメソッドを呼び出す
各ApexメソッドはcreateHogeA~Cを呼び出すことで、必要なレコードを用意できる
public static Hoge__c createHogeA() {
return createHoge('fugafuga');
}
public static Hoge__c createHogeB() {
return createHoge('fuganfugan');
}
public static Hoge__c createHogeC() {
return createHoge('fufugaga');
}
private static Hoge__c createHoge(String name) {
Hoge__c hoge = new Hoge__c (
fuga__c = name,
piyo__c = 100
);
insert hoge;
return hoge;
}
- メリット
- 設定項目の引数が増えた場合、既存クラスへの影響が少ない
→ 共通クラス内の修正だけで済むことが多い
- 設定項目の引数が増えた場合、既存クラスへの影響が少ない
- デメリット
- バリエーションを用意するためにメソッドがどんどん増えていく
NG例1に対する改善案2
insertするメソッドに引数を用意しておき、各Apexメソッドが呼び出す際に引数を渡す
public static Hoge__c createHoge(String name) {
Hoge__c hoge = new Hoge__c (
fuga__c = name,
piyo__c = 100
);
insert hoge;
return hoge;
}
- メリット
- レコード作成用のメソッドは基本的に1つだけで良くなる
- デメリット
- 設定項目の引数が増えた場合、既存クラスへの影響が多い
→ 共通クラスの修正だけでなく、呼び出している既存クラスも修正が必要になる
- 設定項目の引数が増えた場合、既存クラスへの影響が多い
NG例2に対する改善案
共通クラスの方に設定項目を用意しておく
// テストデータ作成用クラス
@isTest
public class TestDataFactory {
public static Hoge__c createHoge() {
Hoge__c hoge = new Hoge__c (
fuga__c = 'fugafuga',
piyo__c = 100
);
insert hoge;
return hoge;
}
}
// Apexテストクラス
@isTest
public with sharing class RMS_UtilTest {
@isTest
public static void test_hogeMethodA() {
Hoge__c hoge = TestDataFactory.createHoge();
・・・
}
@isTest
public static void test_hogeMethodB() {
Hoge__c hoge = TestDataFactory.createHoge();
・・・
}
}
- メリット
- 冗長な記述が無くなる
- デメリット
- 既存クラスへの影響調査は必要
- バリエーションが必要な場合は各テストメソッドでのupdateはやむを得ない