Salesforce開発入門 Vol.3 ～Apex単体テストコード実装編～

こんにちは！
LIFULLエンジニアの吉永です。

前回の記事に引き続き、直近でSalesforce Service Cloud（以降SFSCと略称します）関連の開発を行っているので、今回はApexを使ってカスタムオブジェクトへのCRUD操作を行うREST APIエンドポイント実装時の単体テストコード実装からテスト実施までの流れを紹介したいと思います。
前回の記事は[こちら](https://qiita.com/YoshinagaYuta/items/2ba170a89fc7bf122774)

## Apexのテストコードについて

公式ドキュメントは[こちら](https://developer.salesforce.com/docs/atlas.ja-jp.234.0.apexcode.meta/apexcode/apex_testing.htm)

Apexでは作成したコードをリリースする為にカバレッジ75%が単体テストでカバーされていて、かつすべてのテストが成功している必要があります。
※この他にも色々満たすべき要件がありますが、その他の細かい仕様は[公式ドキュメント](https://developer.salesforce.com/docs/atlas.ja-jp.234.0.apexcode.meta/apexcode/apex_testing_intro.htm)を参照してください。

なので、Apexではテストコードを書かずにリリースすることができませんので、必ず実装する必要があります。
昨今はGitHub Actionsに代表されるCIツールの充実と単体テストを容易に実装して、実行することが出来る環境が整っているので、テストコードのないプロダクトの方が珍しいかと思いますが、普段あまりテストコードを書かない人でも、Apexを通じて単体テストを実装してテスト実行するまでの流れを簡単に説明できればと思います。

## テストコード実装

まずは開発者コンソールを開いてテストコード実装用のApexクラスファイルを作成します。
なお、今回テストコードを実装するのは[前記事にて実装](https://qiita.com/YoshinagaYuta/items/2ba170a89fc7bf122774#get%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E5%AE%9F%E8%A3%85)したREST APIのエンドポイントソースに対して行っていきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/3708cf7f-6e63-7935-a28b-6346f50d9f36.png)
[File]→[New]→[Apex Class]を選択し、ファイル名を`MyOrderRestApi_TEST`と入力して、OKを押します。

### テストデータ作成

テストは常に同じ結果にならなければならないので、テストを行う際は事前にテストデータを用意し、そのデータがある前提でREST APIの動作が想定通りになっているか？などを確認していきます。
JUnitとDBUnitを用いてテストを実装したことがある人にはある程度イメージが湧くかと思いますが、Apexでもテストコード実装前に実行するメソッドを用いてテストデータを用意しておくことが可能となっています。
まずは、Apexのテストで事前に実行されるテストデータ作成コードを見てみましょう。

```apex
@IsTest(seeAllData=false)
public class MyOrderRestApi_TEST {
    @testSetUp
    static void createTestData() {
        // テスト用の商品データ登録
        MyItem__c myItem = new MyItem__c();
        myItem.Name = 'ITEM0001';
        myItem.ItemName__c = 'りんご';
        myItem.Price__c = 100;
        insert myItem;

        MyItem__c myItem2 = new MyItem__c();
        myItem2.Name = 'ITEM0002';
        myItem2.ItemName__c = 'ばなな';
        myItem2.Price__c = 150;
        insert myItem2;

        MyItem__c myItem3 = new MyItem__c();
        myItem3.Name = 'ITEM0003';
        myItem3.ItemName__c = 'いちご';
        myItem3.Price__c = 498;
        insert myItem3;

        // テスト用の注文データ作成
        MyOrder__c myOrder = new MyOrder__c();
        myOrder.Name = 'Order0001';
        insert myOrder;

        MyOrder__c myOrder2 = new MyOrder__c();
        myOrder2.Name = 'Order0002';
        insert myOrder2;
        
        // テスト用の注文明細データ登録
        MyOrderDetail__c myOrderDetail = new MyOrderDetail__c();
        myOrderDetail.Name = 'OrderDetail0001';
        myOrderDetail.MyOrder__c = myOrder.ID;
        myOrderDetail.MyItem__c = myItem.ID;
        myOrderDetail.Count__c = 1;
        insert myOrderDetail;

        MyOrderDetail__c myOrderDetail2 = new MyOrderDetail__c();
        myOrderDetail2.Name = 'OrderDetail0002';
        myOrderDetail2.MyOrder__c = myOrder2.ID;
        myOrderDetail2.MyItem__c = myItem2.ID;
        myOrderDetail2.Count__c = 1;
        insert myOrderDetail2;

        MyOrderDetail__c myOrderDetail3 = new MyOrderDetail__c();
        myOrderDetail3.Name = 'OrderDetail0003';
        myOrderDetail3.MyOrder__c = myOrder2.ID;
        myOrderDetail3.MyItem__c = myItem3.ID;
        myOrderDetail3.Count__c = 2;
        insert myOrderDetail3;
    }    
}
```

ポイントとしては、`@testSetUp`アノテーションをメソッドの前に付けることで、テストコード実行前にこのメソッドが呼び出されるようになります。
※`@testSetUp`アノテーションの公式ドキュメントは[こちら](https://developer.salesforce.com/docs/atlas.ja-jp.218.0.apexcode.meta/apexcode/apex_classes_annotation_testsetup.htm)

また、`@IsTest(seeAllData=false)`もApexテストクラス実装時には必要なアノテーションなので、クラス宣言前に付与します。
※`@IsTest`アノテーションの公式ドキュメントは[こちら](https://developer.salesforce.com/docs/atlas.ja-jp.210.0.apexcode.meta/apexcode/apex_classes_annotation_isTest.htm)

これでテストコード実行前にこの状態のカスタムオブジェクトがセットアップされるので、テストコードではこの状態である前提でREST APIの実装コードを実行した際の検証コードを組んでいけばよいことになります。

### doGetの正常系テストコード実装

続いて、doGetメソッドの正常系のテストコードを実装します。

```apex
    @isTest
    static void doGetTest() {
        // 正常系
        RestResponse res = new RestResponse();
        RestRequest req = new RestRequest();
        req.requestURI = '/myorder/Order0001';
        req.httpMethod = 'GET';
        RestContext.response = res;
        RestContext.request = req;

        // doGetメソッドを呼び出す
        MyOrderRestApi.doGet();
        // res.responseBodyに格納されているJSONをMyOrder__cクラスへデシリアライズする
        MyOrder__c result = (MyOrder__c)JSON.deserialize(res.responseBody.toString(), MyOrder__c.class);

        System.assertEquals(200, res.statusCode);
        System.assertEquals('Order0001', result.Name);
        System.assertEquals(1, result.MyOrderDetails__r.size());
        System.assertEquals('OrderDetail0001', result.MyOrderDetails__r[0].Name);
        System.assertEquals(1, result.MyOrderDetails__r[0].Count__c);
        System.assertEquals('りんご', result.MyOrderDetails__r[0].MyItem__r.ItemName__c);
        System.assertEquals(100, result.MyOrderDetails__r[0].MyItem__r.Price__c);
    }
```

先ほどの`MyOrderRestApi_TEST`に上記のコードを実装します。
ポイントとしては、`@isTest`アノテーションをメソッドの前に付与することです。
また、ApexではJUnitほどモックやスタブのライブラリが充実はしていないのですが、それらがなくてもとても簡単に対象のメソッドを呼び出すことが可能です。
REST APIエンドポイントなので、JUnitや昨今のWebアプリFWだと疑似的にHTTPリクエストを発生させてという手法に慣れている人も多いかもしれませんが、Apexではテスト対象のクラスのstaticメソッドを呼び出すだけという形になります。
よって、`RestResponse`や`RestRequest`という本来はSFSCが事前に用意してくれているオブジェクトをテストコード内で生成、各種値を設定して、`MyOrderRestApi.doGet`を呼び出し、メソッドの処理終了後に`RestRequest`オブジェクト内に格納されたレスポンスをデシリアライズして各種アサートを実行するという流れになります。

### Apexテストの実行方法

それでは先ほどのテストコードを実行したいと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/cd7f47a9-6c5a-e3b7-c8f9-63dcdb3d0ea1.png)
開発者コンソールでテストコードを実装したApexを開いておき、画面右上の[Run Test]を押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/31ea5eaa-fccd-3782-f4df-e6f5ca9a139f.png)
テストが実行されるので開発者コンソールのTestタブに切り替え、テスト結果を確認します。

Failuresが0なら全テストOKということになるので、試しに`res.statusCode`の期待値を400に書き換え、再度実行してみましょう。

```apex
System.assertEquals(400, res.statusCode);
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/d29be5b9-e4ae-d20a-b1bb-de91bbaf01e9.png)
Testタブの一番下に最新のテスト結果が表示され、今度はFailuresが1となりました。
ここでどのテストで失敗したかの詳細を確認するにはテスト結果の`doGetTest`をダブルクリックします。
すると、テストコードの何行目でどんなエラーが発生したのかを確認することができます。

### Apexのテストカバレッジの確認方法

実装したテストでテスト対象のクラスのコードをどれくらい網羅できているかを確認する方法についても紹介します。
開発者コンソールでテスト対象のクラスを開くので、今回は`MyOrderRestApi.apxc`を開いてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/b8207869-55c7-e3a7-60dd-1d172caf2eb1.png)
そして、画面左上の`Code Coverage None`のプルダウンリストをクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/e5594426-2894-46ff-8ca5-f8201d5fdd24.png)
プルダウンが`Code Coverage: All Tests xx%`に切り替わり、コード内でテストで通過済みの行を青、未通過の行を赤で表示してくれます。
カバレッジの数値は`All Tests xx%`のxx%の部分で確認可能で、この数値を最低でも75%まで引き上げないとApexはリリースできないということになります。

### Apexテストコードの肉付け

続いて、カバレッジの確認方法で未通過だった個所をテストコードに肉付けをして通過するようにしたいと思います。
テストコードに下記の異常系の確認を追加します。

```apex
        // 異常系 パラメータ不正
        req.requestURI = '/myorder/Order000';
        // doGetメソッドを呼び出す
        MyOrderRestApi.doGet();

        System.assertEquals(400, res.statusCode);
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/dae1380b-95cb-83c2-60e8-a6975a8cb26c.png)
テスト実行後にカバレッジを確認すると、パーセンテージが少し上がり、先ほど赤背景だった行が青に変わっていることが確認できます。

更にもう一つの未通過コードを通過させる為に、今度は下記をテストコードに追加します。

```apex
        // 異常系 存在しないレコード
        req.requestURI = '/myorder/Order0000';
        // doGetメソッドを呼び出す
        MyOrderRestApi.doGet();

        System.assertEquals(404, res.statusCode);
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/690018/ae15455b-5a25-5525-db57-47fdd42e4f09.png)
テスト実行後にカバレッジを確認すると、パーセンテージが少し上がり、最後に残っていた赤背景の行がなくなったことを確認できると思います。

このような要領でテストコードを肉付けしていき、未通過の行をなるべく無くしていく作業を行っていきます。

ただし、テストコードはただ通過させれば良いわけではなく、分岐があるならその分岐を網羅するようにコードの経路に着目してテストケースを考えるホワイトボックスと呼ばれるテストや、入出力のみに着目してテストケースを考えるブラックボックステストなど、色々なテストケースを組み合わせて、品質の高いコードを作りこんでいくことが重要です。
なので、カバレッジに関してはApexが求める最低限の75%をクリアしているか？を思い付くテストケース実装後に確認という流れで良いかなと、個人的には思います。

## まとめ
いかがでしたでしょうか。
今回はApexでテストコードの実装、実行、テストコードのカバレッジ確認を行うまでの流れを紹介しました。

今ではテストコードを実装していないプロダクトは有り得ないというくらいに、テストコードを実装するのが当たり前になってきていますが、私が数年前に従事していた組み込み系ではテストコードを実装することは少なく、実装後にまとめてブラックボックステストで洗い出したテストケースを手動でe2eテストするというのが当たり前でしたし、前職で開発・メンテしていた2010年頃に開発開始していたPHPのプロダクトではテストコードはまったくなかったので、レガシーなプロダクトや環境ではまだ割とそういう場面もあるのかなと思います。

知人のエンジニアは手動テストが嫌いだったので、テスト自体に嫌な印象を持ち、テストコードを実装も嫌だなぁと思いながら実装していた時期があったと言いますが、カバレッジを確認したり、テストコードでバグを発見できたりという体験を通して、今ではプロダクションコードよりもテストコードを書く時間の方が楽しいと言ってました。

もし、普段テストコードをあまり書く機会がないという人がこの記事を読んでテストコード実装を体験し、面白いなと思ってもらえたら幸いです。

最後までご覧いただき、ありがとうございました。
それではまた次の記事でお会いしましょう。