Test.startTest()・Test.stopTest()の意味と使い方

By | October 7, 2021

前書き

Salesforceでは一般に↓のようなお作法に則ってTestClassを書くべしと言われていますが、そもそもTest.startTest()とTest.stopTest()って何ぞや?という方もいらっしゃるかと思います。

なので、今回はTest.startTest()とtest.stopTest()の意味と使い方についてご紹介したいと思います。

@isTest
private class TestClass {
    @testSetup
    static void setup() {
        //テストデータ準備(※各メソッドに共通のデータを準備)
        Account testAccount = TestDataFactory.createAccount(true);
        List<Opportunity> oppList = TestDataFactory.createOpportunity(acc.id,5);
        insert oppList;
    }

    @isTest
    static void testMethod() {
        
    //テストデータ準備(※このメソッドに特有のデータを準備)
        Account accToUpdate = [SELECT Id,Name FROM Account LIMIT 1];
        accToUpdate.name = 'にゃん';
        update accToUpdate;

        Test.startTest();
      System.runAs(new User(ID = UserInfo.getUserID())) {
          //テストメソッド実行
      }
        Test.stopTest();

      //結果検証
       List<Opportunity> oppListToCheck = [SELECT Id FROM Opportunity];
       System.assertEquals(oppListToCheck.size(),5)
    }
}

Test.startTest()・test.stopTest()とは何か

結論から言うと、Test.startTest()・test.stopTest()は主に下記の二つの役割を有しています。

①Test.startTest()とTest.stopTest()の中が、その外とは別のガバナ制限のコンテクストにおいて動作するようにする。

②非同期処理を同期処理化して、Test.stopTest()の行で処理が必ず完了するようにする。

それぞれ見ていきます。

■Test.startTest()とTest.stopTest()の中が、その外とは別のガバナ制限のコンテクストにおいて動作するようにする。

文字通り、Test.startTest()とTest.stopTest()の中と外は、ガバナ制限のコンテクストが異なります。

いわば、その部分だけコンテクストが分離するようなイメージです。

@isTest
static void testMethod(){
 //95件のDOQLクエリ(※ガバナ制限まで残り5件)
 Test.startTest();
  //任意の処理(※ガバナ制限リセットにより、残り100件までSOQLクエリを発行可能)
  Test.stopTest();
  //3件のSOQLクエリ(※ガバナ制限まで残り2件)
}

■非同期処理を同期処理化して、Test.stopTest()の行で処理が必ず完了するようにする。

Test.startTest()の後に実行された全ての非同期コールはシステムによって収集され、Test.stopTest()の実行により、全ての非同期プロセスが同期的に処理されます。

つまり、通常はどのタイミングで終了するか分からない非同期処理は全て例外なくTest.stopTest()の次の行までに処理が完了しています。

@isTest
static void testMethod(){
 Test.startTest();
  //非同期処理
  Test.stopTest();
  //この時点で非同期処理が全て完了している
}

Test.startTest()・test.stopTest()の使い方

「ガバナ制限のコンテクストの分離」と「非同期処理の同期処理化」という二つの特徴から、記事の冒頭でもご紹介した典型的なApexTestClassにおいてなぜstartTest()がテストデータ準備よりも後にあり、stopTest()がAssertよりも前にあるのかが理解できるようになります。

■テストデータの準備がTest.startTest()よりも前である理由

Apexのテストでは、各TestClassの中でテストデータを準備する必要がありますが、Apexが動作する実際の環境ではデータは既に組織の中に用意されています。

つまり、テストデータの準備は実際のロジック動作とは無関係なものであり、ロジックそのもののパフォーマンスやLimitの検証から除外するべきものであるということができます。

このロジック単体のパフォーマンスやガバナ制限の検証から”テストデータの準備”を分離するという操作がまさに、Test.startTest()の前での”テストデータの準備”の配置に相当します。

■AssertがTest.stopTest()よりも後である理由

Assertは結果検証(期待した結果と実際の結果が一致しているどうかの確認)を目的として実行するものです。

従って、処置が完了していない段階でAssertをしても何の意味もありません。

先に見た通り、Test.stopTest()には「その段階で非同期処理を全て同期処理化して必ず終了させる」という意味があります。

そのため、非同期処理の結果をAssertによって検証する場合は、必ずAssertがTest.stopTest()よりも後ろに記述されている必要があります。

@isTest
private class TestClass {
    @testSetup
    static void setup() {
        //テストデータ準備(※各メソッドに共通のデータを準備)
        Account testAccount = TestDataFactory.createAccount(true);
        List<Opportunity> oppList = TestDataFactory.createOpportunity(acc.id,5);
        insert oppList;
    }

    @isTest
    static void testMethod() {
        
    //テストデータ準備(※このメソッドに特有のデータを準備)
        Account accToUpdate = [SELECT Id,Name FROM Account LIMIT 1];
        accToUpdate.name = 'にゃん';
        update accToUpdate;

        Test.startTest();
      System.runAs(new User(ID = UserInfo.getUserID())) {
          //テストメソッド実行
      }
        Test.stopTest();

      //結果検証
       List<Opportunity> oppListToCheck = [SELECT Id FROM Opportunity];
       System.assertEquals(oppListToCheck.size(),5)
    }
}