Skip to main content
3 月 5 日~ 6 日にサンフランシスコで開催される TDX (Salesforce+ でも配信) で「Developer Conference for the AI Agent Era (AI エージェント時代に向けた開発者向けカンファレンス)」にぜひご参加ください。お申し込みはこちら

ネガティブテストを記述する

学習の目的

この単元を完了すると、次のことができるようになります。

  • ネガティブ単体テストとは何かを説明する。
  • ネガティブ単体テストを記述する。
メモ

メモ

日本語で受講されている方へ
Challenge は日本語の Trailhead Playground で開始し、かっこ内の翻訳を参照しながら進めていってください。Challenge での評価は英語データを対象に行われるため、英語の値のみをコピーして貼り付けるようにしてください。日本語の組織で Challenge が不合格だった場合は、(1) この手順に従って [Locale (地域)] を [United States (米国)] に切り替え、(2) [Language (言語)] を [English (英語)] に切り替えてから、(3) [Check Challenge (Challenge を確認)] ボタンをクリックしてみることをお勧めします。

翻訳版 Trailhead を活用する方法の詳細は、自分の言語の Trailhead バッジを参照してください。

ネガティブ単体テストを記述する理由

ネガティブテストというと、とても…否定的な響きがしますが、多くの開発者はネガティブテストの重要性を確信しています。次の動画で、ネガティブテストの概要と実行する理由をご覧ください。

ネガティブ単体テストの用途は、コードの全行がテストされたことを確認するだけではありません。実際のところ、ネガティブテストのほうがポジティブテストよりも重要なことがあります。ネガティブテストでは、無効なデータ、予期しないユーザー入力、コードベースの他の部分の変更がコードで適切に処理されているかどうかが示されます。さらに重要な点は、記述したコードの耐障害性がネガティブテストで確認されることです。 

コードで例外がどのように処理されるかをテストすることは、妥当な条件下でコードが意図したとおりに機能することを確認するのと同じくらい重要です。一括処理を想像してみてください。コードが毎晩午前 2 時に、カスタマーコミュニティで取引先責任者が入力した取引先の更新情報を処理するとします。コードが一括処理の最初のレコードで失敗し、例外を適切に処理しなければ、残りのレコードが処理されません。ネガティブテストを実行すれば、こうした状況を未然に防ぐことができます。

パターン

ポジティブテストでは、有効な入力を使用してコードをテストし、コードが適切に機能した場合にどうなるかが示されます。ネガティブテストの場合は、コードが無効なデータや例外を適切に処理することが示されます。こうしたテストは、必ずしも容易に達成されるとは限りません。単体テスト中に例外がスローされれば、そのテストが失敗となるためです。 

では、コードが例外をスローすれば合格となるテストを記述するにはどうすればよいでしょうか? さらに重要な点は、さまざまな例外種別が使用される中で、コードが期待どおりの種別の例外をスローした場合にのみ合格とするテストを記述するにはどうすればよいのでしょうか? ネガティブテストの一般的なパターンは次のようになります。

  1. テストデータを生成するか読み込みます。
  2. try/catch ブロックを開始します。
  3. Test.startTest() をコールします。
  4. コードを実行します。
  5. Test.stopTest() をコールします。
  6. 例外が期待どおりスローされるようにします。このために、必ず失敗する assert を次の行に追加します。このステートメントに達することはありません!
  7. 期待どおりの例外をキャッチし、例外メッセージが一致しているかどうか確認します。

この例の AccountWrapper クラスに、例外をスローするコード行があります。この動作を実行するテストは次のようになります。 

  1. VS Code を開きます。
  2. [Explorer (エクスプローラー)] サイドバーで、classes フォルダーをクリックします。
  3. [AccountWrapperTests] クラスを選択します。
  4. クラスの終了前に、次のコードを追加します。
    @IsTest
    static void testNegativeAccountWrapperAvgPriceOfOpps() {
      // GIVEN
      Account acct = [SELECT Id FROM Account LIMIT 1];
      List<Opportunity> opps = [
        SELECT Amount
        FROM Opportunity
        WHERE accountId = :acct.Id
      ];
      for(Opportunity o : opps) {
        o.Amount = 0;
      }
      update opps;
      AccountWrapper acctWrapper = new AccountWrapper(acct);
      // WHEN
      try {
        Test.startTest();
          acctWrapper.getRoundedAvgPriceOfOpps();
        Test.stopTest();
        Assert.fail('An exception should have been thrown');
      } catch (AccountWrapper.AWException e) {
        // THEN
        Assert.isTrue(
          e.getMessage().equalsIgnoreCase('no won opportunities'),
          'Exception message does not match: ' + e.getMessage()
        );
      }
    }
  5. [File (ファイル)] > [Save (保存)] をクリックします。
  6. 作業中のファイルを右クリックして、[SFDX: Deploy Source To Org (SFDX: 組織にソースをリリース)] を選択します。
  7. testNegativeAccountWrapperAvgPriceOfOpps メソッドに表示される [Run Test (テストを実行)] ボタンをクリックします。

コードのポイント

このテストでは、@TestSetup メソッドが作成した商談を基に構築していきます。ただし、AccountWrapper が無効なデータをどのように処理するかをテストするには、@TestSetup メソッドが作成した商談の金額がすべて 0 であることを確認する必要があります。このために、テストに for ループを使用して各商談を反復処理し、金額項目を 0 に設定します。  テストデータをこのように設定すると、AccountWrapper の getRoundedAvgPriceOfOpps() メソッドが、すべての商談の四捨五入された価格を 0 と算出します。その結果、コードが AWException (AccountWrapper クラスで定義されたカスタムの例外種別) をスローします。単体テストで、この AWException オブジェクトをキャッチします。

このような try/catch ブロックの問題は、適切に記述されていなければ、catch ブロックが例外の何らかのインスタンスまたはサブクラスをキャッチしてしまうことです。このベストプラクティスは、例外をキャッチしたときに、例外種別と例外のメッセージや詳細が期待どおりのものであることをテストで確認することです。この例では、AccountWrapper クラスで定義されている AWException というカスタムの例外種別が期待されています。 

コードに、例外をスローする可能性がある場所が複数あり、同じ種別の例外がスローされることも少なくありません。たとえば、validate メソッドが設定されている ContactService クラスの 3 ~ 4 か所で、異なる理由により ContactServiceException がスローされることがあります。テストで種別、メッセージ、詳細をチェックしなければ、誤って合格してしまうおそれがあります。これを偽陽性といい、偽陽性が多すぎると、テストの信頼性が損なわれる可能性があります。

ハンズオン Challenge

+500 ポイント

準備を始めましょう

この 単元 は各自のハンズオン組織で実行します。[起動] をクリックして開始するか、組織の名前をクリックして別の組織を選びます。

あなたの Challenge

Write a negative unit test
Everyone loves a good calculator. In your Trailhead Playground is the Calculator class. Write a negative unit test for the divide() method found inside.
  • Create a new class named CalculatorTests.
  • Write a negative test for the divide() method on the Calculator class. Call it testDivideByZero().
  • Run your unit test and confirm that the code coverage for the Calculator class is equal or greater than 20%. Note that Salesforce requests >75% of code coverage for Apex classes, but for the purpose of this HOC, we’ll check only 20%.
Salesforce ヘルプで Trailhead のフィードバックを共有してください。

Trailhead についての感想をお聞かせください。[Salesforce ヘルプ] サイトから新しいフィードバックフォームにいつでもアクセスできるようになりました。

詳細はこちら フィードバックの共有に進む