コードの分析およびテスト

ABAP Unit を使用したコードテストの実装

Objectives

After completing this lesson, you will be able to:
  • テストクラスを実装します。
  • ABAP ユニットテストを実行します。

ABAP Unit

モジュールテスト

プログラマがコードを記述または変更するたびに、プログラミングエラーが発生する可能性があります。そのため、テストはすべての開発プロジェクトの重要な部分です。

最新のプログラミングでは、コードは再利用可能なクラスおよびメソッドで構造化されています。潜在的なプログラミングエラーを検出してローカライズするには、各モジュール化単位の徹底的なテストが必要です。

例を見てみましょう。

ABAP Unit - ABAP でのモジュールテストの実装

ABAP Unit は、ABAP 言語でモジュールテストを実装するための手法です。テスト実行は、開発中にマニュアルでトリガすることも、大規模な定期的に自動的にトリガすることもできます。

ABAP ユニットテストは、特別に指定された ABAP クラスのメソッドとして実装されます。これらのテストメソッドはテストスクリプトとして機能し、このテストスクリプトを使用してテスト中のコードを実行し、結果を評価することができます。

引き続き例を見てみましょう。

テストが完了したら、いつでも必要な回数だけテストを実行できることが非常に重要です。最初の出荷前、本稼働コードに変更を適用した後、またはユーザが問題を報告する際のエラー分析です。テストを実行すると、マウスを数回クリックするだけで済みます。

ABAP Unit の重要な機能

ユニットテストクラス

さらに、クラス定義の FOR TESTING によって、テストクラスと通常の ABAP クラスが区別されます。このオプションは、ローカルクラスおよびグローバルクラスで使用できます。

追加 RISK LEVEL は、リスクレベルをテストに割り当てるために使用されます。このオプションがない場合は、リスクレベル CRITICAL がデフォルトで使用されます。

クライアントレベルの共通設定では、特定のリスクレベルでのテストの実行を禁止することができます。

以下のリスクレベル値を使用することができます。

CRITICAL

テストにより、システム設定またはカスタマイジングデータが変更されます。

DANGEROUS

テストで永続データが変更される

HARMLESS

テストでは、システム設定や永続データは変更されません。

加算 DURATION は、予定実行時間を指定します。クライアントレベルの共通設定を使用して、3 つの値に対する実行時間上限を定義することができます。

以下の期間値を使用することができます。

短縮

数秒の異常な実行時間が予想されます。

MEDIUM

約 1 分という顕著な実行時間が予想されます。

完全

1 分を超える非常に顕著な実行時間が予想されます。

テストクラスには、以下の 2 種類のメソッドがあります。

テストメソッド

テストメソッドは、メソッド名の後にオプション FOR TESTING を使用して定義されます。各テストメソッドは、1 つのテストを表します。ABAP Unit フレームワークは、関連するテストメソッドを呼び出すことでこのテストを実行します。テストメソッドにパラメータを含めることはできません。

ヘルパーメソッド

ヘルパーメソッドは、テストクラスの通常のメソッドです。ABAP Unit フレームワークでは呼び出されません。ヘルパーメソッドを使用して、テストメソッドのコードを構造化したり、複数のテストメソッドで同じ機能を再利用したりすることができます。ヘルパーメソッドには、任意の数のパラメータを含めることができます。

注記

ABAP Unit フレームワークでは、可視性が PRIVATE に設定されている場合でも、すべてのテストメソッドを呼び出すことができます。実際には、テストクラスのプライベートセクションでテストメソッドを定義し、テストクラスがコード内のどこにも直接呼び出されないようにすることをお奨めします。

テストクラスには、少なくとも 1 つのテストメソッドが含まれている必要があります。テストメソッドにパラメータを含めることはできません。

テストクラスのテストを実行すると、ABAP Unit フレームワークにより、テストクラスのすべてのテストが未定の順序で呼び出されます。

グローバル ABAP クラス内でローカルテストクラスを定義する場合は、専用の場所があります。通常のローカルクラスはローカルタイプタブで定義されますが、ローカルテストクラスはテストクラスタブで定義する必要があります。

図に示すように、ローカルテストクラスの定義部分と実装部分を生成するために、ADT にはコードテンプレート testClass が用意されています。

デモ、テストクラスの定義および実装方法では、このコードテンプレートを呼び出す方法を確認します。

ユニットテスト実装

サービスクラス CL_ABAP_UNIT_ASSERT

通常、テストメソッドの実装の構造は以下のとおりです。

  1. テスト中の本稼動コーディングの実行
  2. 結果の分析
  3. ABAP Unit フレームワークへの予期しない結果のレポート

ステップ 3 では、ABAP Unit フレームワークによってグローバルサービスクラス CL_ABAP_UNIT_ASSERT が提供されます。テストメソッドは、このクラスの静的メソッドを呼び出して、エラーをレポートし、テスト実行を制御します (たとえば、前提条件が満たされていないため、1 つまたは複数のテストをスキップします)。

  • method fail( ) は、無条件エラーをレポートします。通常、このメソッドの呼出は IF ... ENDIF. や TRY ... ENDTRY などの制御構造で囲まれ、条件下でのみ到達します。
  • assert_ check で始まるメソッドでは、特定の期待値が満たされない場合はエラーがレポートされます。
    • たとえば、メソッド assert_equals( ) では、2 つのデータオブジェクトの内容が比較され、異なる場合はエラーがレポートされます。
    • メソッド assert_different( ) は同じですが、データオブジェクトの内容が同じ場合はエラーがレポートされます。

メソッド fail( ) のパラメータ

詳細については、以下のビデオを視聴してください。

Assert-Methods のパラメータ

すべての assert メソッドには、オプションのインポートパラメータ MSG、LEVEL、および QUIT があり、常にメソッド fail( ) と同じ意味を持ちます。

また、ほとんどのアサーションメソッドには、検証対象のデータオブジェクトのインポートパラメータ ACT があります。

assert_equals( )assert_differres( ) などのメソッドを比較し、期待値に対するパラメータ EXP も持っています。

テストクラスの定義および実装方法

このデモでは、ローカルテストクラスを登録および実装する方法を確認します。

ユニットテスト実行

ABAP ユニットテストを実行するには、開発中のインタラクティブテストと ABAP テストコックピットを使用した一括テストの 2 つの方法があります。

ユニットテスト実行の詳細については、以下のビデオを視聴してください。

ABAP ユニットテストの結果

ADT では、最新の ABAP Unit テスト結果が ABAP Unit ビューに表示されます。

上部の概要には、実行されたテストメソッドの合計数と、テストの合計時間がミリ秒単位で表示されます。

サマリのチェックボックスを使用して、結果表示をフィルタリングします (失敗したテストのみ、警告のあるテストのみ、正常に終了したテストのみなど)。

結果自体は、グローバルクラスなどのリポジトリオブジェクトとともにツリーとして表示されます。第 2 レベルのノードはローカルテストクラスを表し、エンドポイントはテストメソッドに対応します。このアイコンは、成功したテストや失敗などを区別するのに役立ちます。

左クリックでテスト方法を選択し、右側に詳細を表示します。この例では、重大度 (重大度 medium の Critical Assertion Error)、パラメータ MSG の値 ('例外なし')、およびパラメータ DETAILS の値 (Details の下に展開されたテキスト) を確認することができます。

ユニットテストの実行方法と結果の分析方法

このデモでは、ユニットテストを実行し、結果を分析する方法を確認します。

テスト: 什器および前提条件

テスト什器のメソッド

テストを正しく実行するには、テストに特定の設定が必要になる場合があります。このようなテスト設定は、テスト什器と呼ばれます。テスト什器は、テストデータ、テストオブジェクト、およびリソースで構成される場合があります。

什器を登録および削除するには、テストクラスに追加メソッドを実装します。これらのメソッドの名称は事前定義されており、テストの実行時に ABAP 実行時環境によって自動的に呼び出されます。

以下の什器メソッドがあります。

  • SETUP

    このインスタンスメソッドは、テストクラスの各テストの前に呼び出されます。このメソッドは、テストごとに再登録する什器に使用します。

  • TEARDOWN

    このインスタンスメソッドは、テストクラスの各テスト後に呼び出されます。このメソッドを使用して、メソッド SETUP で行った変更を取り消します。TEARDOWN の使用は、SETUP が永続データ (システム設定、カスタマイジング、マスタデータなど) を変更する場合に特に重要です。

  • CLASS_SETUP

    この静的メソッドは、テストクラスの最初のテストの前に 1 回実行されます。この方法は、時間のかかる什器、および設定がどのテスト方法でも変更されないことが確実な什器にのみ使用します。

  • CLASS_TEARDOWN

    この静的メソッドは、テストクラスの最終テスト後に 1 回実行されます。このメソッドを使用して、メソッド CLASS_SETUP で行った変更を取り消します。

ABAP ユニットテストの制御ロジック

この図は、単一のテストクラスに対する ABAP UNIT のプログラムフローを示しています。

  • 最初に、テストクラスがプログラムメモリにロードされます。テストクラスに静的コンストラクタ (静的メソッド CLASS_CONSTRUCTOR) が含まれている場合、このメソッドは通常どおり実行されます。
  • 次に、このテストクラスのテストが実行されます。各テストは、テストクラスのインスタンスの登録から開始されます。テストクラスにインスタンスコンストラクタ (メソッド CONSTRUCTOR) が含まれている場合は、通常どおり実行されます。
  • 次に、ABAP Unit フレームワークによってテストメソッドが呼び出されます。
  • テスト後、インスタンスは破棄されます。

テストクラスに複数のテストメソッドがある場合は、テストごとに新規インスタンスが登録されます。

最後のテストメソッドの後、このテストクラスの処理が終了し、フレームワークは次のテストクラス (存在する場合) に進みます。

ABAP ユニットテストのフェーズモデル

この図は、ABAP ユニットテストのフェーズモデル、つまり、フレームワークが什器メソッドを呼び出す時点を示しています。

CLASS_SETUP は、静的コンストラクタの後、最初のテスト前に 1 回だけ呼び出されます。

CLASS_TEARDOWN は、フレームワークが次のテストクラスの処理を開始する前に呼び出されます。

SETUP は、インスタンスコンストラクタの後、テスト実行前に呼び出されます。

TEARDOWN は、フレームワークによってテストクラスインスタンスが破棄される前に呼び出されます。

前提条件を満たしていないレポート

失敗したテストをレポートするためにサービスクラス CL_ABAP_UNIT_ASSERT のメソッドを使用する方法を学習しました。

ただし、前提条件が満たされていない場合 (権限がない場合など) は、何をする必要がありますか。または、SETUP メソッドでテストの準備で問題が発生する状況が発生しますか。

このような状況に対してのみ、クラス CL_ABAP_UNIT_ASSERT には 1 つのメソッド skip( ) と、ASSUME_ で始まる複数のメソッドが含まれています。

メソッドは fail( ) と assert-methods とよく似ていますが、UNIT テスト結果では異なるように見えます。これにより、テストされたコーディングのエラーと、システム設定またはテスト設計のエラーを区別しやすくなります。

例を見てみましょう。

メソッド test_with_fail( ) により、メソッド cl_abap_unit_assert=>fail( ) が呼び出されます。このメソッドは失敗したテストとしてカウントされ、このメソッドのエラートレースではメッセージテキストの前に Critical Assertion Error が表示されます。

メソッド test_with_skip( ) により、メソッド cl_abap_unit_assert=>skip( ) が呼び出されます。このメソッドは中止されたテストとしてカウントされ、このメソッドのエラートレースではメッセージテキストの前に不足前提条件が表示されます。

注記

メソッド skip( ) および assume-methods にパラメータ QUIT および LEVEL はありません。前提条件を満たしていない場合、テストを続行する意味はなく、異なる重大度は区別されません。

SETUP を使用した複雑なユニットテストの実行方法

このデモでは、設定方法を含むより複雑なテストを実行する方法を確認します。

ABAP Unit テストの実装および実行

コードの出力が適切ではないことに気付きました。特に、次に利用可能な貨物フライトの日付が正しくないようです。ローカルクラス lcl_carrier のメソッド find_cargo_flight の ABAP ユニットテストを定義して実装し、この問題をさらに分析します。

テンプレート:

  • /LRN/CL_S4D401_ATS_CHECKED (グローバルクラス)

ソリューション:

  • /LRN/CL_S4D401_ATS_UNIT_TEST (グローバルクラス)

タスク 1: テンプレートのコピー (オプション)

テンプレートクラス /LRN/CL_S4D401_ATS_CHECKED をコピーします。前の演習問題を完了した場合は、このタスクをスキップし、クラス ZCL_##_SOLUTION の編集を続行することができます。

ステップ

  1. クラス /LRN/CL_S4D401_ATS_CHECKED を各自のパッケージのクラスにコピーします (推奨名: ZCL_##_SOLUTION、## はグループ番号)。

    1. Project Explorer で、クラス /LRN/CL_S4D401_ATS_CHECKED を右クリックしてコンテキストメニューを開きます。

    2. コンテキストメニューから Duplicate.... を選択します。

    3. Package 項目にパッケージの名称を入力します。名称項目に、名称 ZCL_##_SOLUTION (## はグループ番号) を入力します。

    4. Next を選択します。

    5. 移送依頼を確認し、Finish を選択します。

  2. コピーを有効化します。

    1. Ctrl + F3 を押してクラスを有効化します。

タスク 2: ローカルテストクラスの定義

グローバルクラスで、ローカルテストクラスを定義します (推奨名: ltcl_find_flights)。クラス lcl_carrier のメソッド find_cargo_flight のテストを定義し、実装します。

ステップ

  1. ローカルテストクラス ltcl_find_flights を登録します。

    1. テストクラス (存在しない) タブにナビゲートします。

    2. テストクラス登録... ボタンがタブに表示される場合は、それを選択します。

    3. 最初のソースコード行のコメントの末尾にカーソルを置き、Enter を押します。

    4. 新しいソースコード行に test と入力し、Ctrl + Space を押してコード完了を呼び出します。

    5. ソースコードテンプレート testClass - Test class (ABAP Unit) を選択し、Enter を押します。

    6. 予備クラス名 ltcl_ が強調表示されている間に、完全なクラス名 ltcl_find_flights を入力します。

  2. クイックフィックスを使用して、事前定義されたテストメソッドの名称を変更します (推奨名: test_find_cargo_flight)。

    1. first_test を右クリックし、Quick Fix を選択します。表示される一覧から Rename first_test を選択します。名前が強調表示されている間に、新しい名前 test_find_cargo_flight を入力します。

  3. メソッド test_find_cargo_flight を実装します。データベーステーブル /LRN/CARGOFLIGHT から、残存能力が 1 kg 以上の貨物フライトのキー項目 (carrier_id、connection_id、flight_date) および 2 つの空港 (airport_from_id および airport_to_id) を読み込みます。結果を適切なデータオブジェクトに保存します (推奨名: some_flight_data)

    ヒント

    残存能力は、2 つのテーブル項目 maximum_load と actual_load の差異として計算されます。
    1. コードを以下のように調整します。

      ABAP
      12345678910
      METHOD test_find_cargo_flight. SELECT SINGLE FROM /lrn/cargoflight FIELDS carrier_id, connection_id, flight_date, airport_from_id, airport_to_id WHERE maximum_load - actual_load >= 1 INTO @DATA(some_flight_data). ENDMETHOD.
  4. 適切なデータセットがテーブル /LRN/CARGOFLIGHT に存在しない場合は、テストが失敗としてレポートされます。

    1. コードを以下のように調整します。

      ABAP
      1234567891011121314
      METHOD test_find_cargo_flight. SELECT SINGLE FROM /lrn/cargoflight FIELDS carrier_id, connection_id, flight_date, airport_from_id, airport_to_id WHERE maximum_load - actual_load >= 1 INTO @DATA(some_flight_data). IF sy-subrc <> 0. cl_abap_unit_assert=>fail( `No suitable data in table /LRN/CARGOFLIGHT` ). ENDIF. ENDMETHOD.
  5. テーブル /LRN/CARGOFLIGHT から適切なレコードを読み込むことができた場合は、このレコードの carrier_id 値を使用して、クラス lcl_carrier のインスタンスを登録します (参照の推奨名: the_carrier)。

    1. メソッドの末尾に以下のコードを追加します。

      ABAP
      12
      DATA(the_carrier) = NEW lcl_carrier( i_carrier_id = some_flight_data-carrier_id ).
  6. クラス lcl_carrier のコンストラクタが例外レポートを発行した場合、テストは失敗となります。

    1. コードを以下のように調整します。

      ABAP
      123456
      TRY. DATA(the_carrier) = NEW lcl_carrier( i_carrier_id = some_flight_data-carrier_id ). CATCH cx_abap_invalid_value. cl_abap_unit_assert=>fail( `Unable to instantiate lcl_carrier` ). ENDTRY.
  7. インスタンス化が成功したら、lcl_carrier のインスタンスに対してメソッド find_cargo_flight を呼び出します。選択した貨物フライトの空港およびフライト日付を入力として使用します。最小空き貨物 (パラメータ i_cargo) を 1 に設定します。結果を適切なデータオブジェクトに保存します (推奨名: flight および days_later)。

    1. メソッドの末尾に以下のコードを追加します。

      ABAP
      12345678910
      the_carrier->find_cargo_flight( EXPORTING i_airport_from_id = some_flight_data-airport_from_id i_airport_to_id = some_flight_data-airport_to_id i_from_date = some_flight_data-flight_date i_cargo = 1 IMPORTING e_flight = data(flight) e_days_later = data(days_later) ).
  8. メソッドの出力を分析します。パラメータ e_flight によって有効なオブジェクト参照が返され、パラメータ e_days_later が 0 を返すように、適切な assert メソッドを呼び出します。

    注記

    パラメータ e_days_later によって値 0 が返されることは、メソッドによって要求日に適切なフライトが直接見つかったことを意味します。これは、既存のフライトのプロパティを入力として使用すると想定されます。
    1. メソッドの最後に、以下のコードを追加します。

      ABAP
      12345678910
      cl_abap_unit_assert=>assert_bound( act = flight msg = `Method find_cargo_flight does not return a result` ). cl_abap_unit_assert=>assert_equals( act = days_later exp = 0 msg = `Method find_cargo_flight returns wrong result` ).
  9. クラスを有効化します。

    1. Ctrl + F3 を押してクラスを有効化します。

タスク 3: ユニットテストの実行

ユニットテストを実行します。結果を分析し、テストでエラーが返された場合は、メソッド find_cargo_flight の実装を調整します。

注意

最大許容時間の 60 秒を超過しましたというメッセージでユニットテストが中止された場合は、予想実行時間を増やしてください。これを行うには、テストクラスの CLASS … DEFINITION 命令の DURATION オプションを DURATION SHORT から DURATION MEDIUM に変更します。

ステップ

  1. グローバルクラス ZCL_##_SOLUTION でユニットテストを実行します。

    1. Project Explorer で、クラス ZCL_##_SOLUTION を右クリックし、Run AsABAP Unit Test を選択します。

  2. テストが終了したら、ABAP Unit ビューで結果を分析します。

    1. まだ表示されていない場合は、画面下部のセクションにある ABAP Unit タブをクリックします。

    2. 失敗/エラーアイコンの横に値 1 が表示されます。

    3. ツリーで、テストメソッド名 (zcl_##_solutionltcl_find_flightstest_find_cargo_flight) を選択して、エラーの詳細を照会します。

  3. メソッド find_cargo_flight のコードを分析し、エラーを修正します。

    1. ツリーで、test_find_cargo_flight をダブルクリックして、テストメソッドの実装にナビゲートします。

    2. メソッド find_cargo_flight の呼出まで下にスクロールします。メソッド名にカーソルを置き、F3 を押してその実装にナビゲートします。

    3. days_later の計算を見つけます。ここでは、フライトの日付が提供された日付から差し引かれます。これは、逆である必要があります。

    4. エラーを修正するには、この文の前にコメント記号を追加します。

      ABAP
      1
      * DATA(days_later) = i_from_date - flight->flight_date.

      これを以下のコードに置き換えます。

      ABAP
      1
      DATA(days_later) = flight->flight_date - i_from_date.
  4. クラスを有効化し、ユニットテストを再実行して、エラーがなくなったことを確認します。

    1. Ctrl + F3 を押してクラスを有効化します。

    2. ABAP Unit タブのツールバーからテスト再実行を選択します。

    3. テストでゼロエラー/エラーが表示されることを確認します。

  5. グローバルクラスをコンソールアプリとして実行し、次に利用可能な貨物フライトの日付が合理的に将来近くなるようにします。

タスク 4: CLASS_SETUP メソッドを実装します (オプション)。

lcl_carrier のインスタンスを必要とするテストクラスに、さらにテストメソッドを追加する予定です。テストのパフォーマンスを改善するには、クラス lcl_carrier の時間のかかるインスタンス化を class_setup メソッドに移動します。これは、このメソッドがテストクラスのすべてのテストに対して 1 回だけ実行されるためです。

ステップ

  1. テストクラス ltcl_find_flights でプライベート静的メソッド class_setup (パラメータなし) を定義し、クイックフィックスを使用してメソッド実装を追加します。

    1. クラス ltcl_find_flights のプライベートセクションに以下のコードを追加します。

      ABAP
      1
      CLASS-METHODS class_setup.
    2. class_setup を右クリックし、Quick Fix を選択します。

    3. 候補リストから、class_setup の実装を追加を選択します。

  2. テストクラスで、クラス lcl_carrier (推奨名: the_carrier) のインスタンスへの参照を保存できるプライベート静的属性を宣言します。

    1. クラス ltcl_find_flights のプライベートセクションに以下のコードを追加します。

      ABAP
      1
      CLASS-DATA the_carrier TYPE REF TO lcl_carrier.
  3. データベーステーブル /LRN/CARGOFLIGHT から 1 つのレコードを保存できるプライベート静的属性を宣言します (推奨名: some_flight_data)。

    1. クラス ltcl_find_flights のプライベートセクションに以下のコードを追加します。

      ABAP
      1
      CLASS-DATA some_flight_data TYPE /lrn/cargoflight.
  4. SELECT 命令およびクラス lcl_carrier のインスタンス化をメソッド test_find_cargo_flight からメソッド class_setup に移動します。データベースのデータを静的属性 some_flight_data に格納し、lcl_carrier のインスタンスへの参照を静的属性 the_carrier に保存します。

    1. メソッド test_find_cargo_flight の実装から以下のコードをカットし、それをメソッド class_setup の実装にペーストします。

      ABAP
      1234567891011121314151617
      SELECT SINGLE FROM /lrn/cargoflight FIELDS carrier_id, connection_id, flight_date, airport_from_id, airport_to_id WHERE maximum_load - actual_load >= 1 INTO @DATA(some_flight_data). IF sy-subrc <> 0. cl_abap_unit_assert=>fail( `No suitable data in table /LRN/CARGOFLIGHT` ). ENDIF. TRY. DATA(the_carrier) = NEW lcl_carrier( i_carrier_id = some_flight_data-carrier_id ). CATCH cx_abap_invalid_value. cl_abap_unit_assert=>fail( `Unable to instantiate lcl_carrier` ). ENDTRY.
    2. INTO @DATA(some_flight_data)INTO CORRESPONDING FIELDS OF @some_flight_data に置き換えます。

    3. DATA(the_carrier)the_carrier に置き換えます。

    4. これで、メソッド class_setup のコードは以下のようになります。

      ABAP
      123456789101112131415161718192021
      METHOD class_setup. SELECT SINGLE FROM /lrn/cargoflight FIELDS carrier_id, connection_id, flight_date, airport_from_id, airport_to_id WHERE maximum_load - actual_load >= 1 INTO CORRESPONDING FIELDS OF @some_flight_data. IF sy-subrc <> 0. cl_abap_unit_assert=>fail( `No suitable data in table /LRN/CARGOFLIGHT` ). ENDIF. TRY. the_carrier = NEW lcl_carrier( i_carrier_id = some_flight_data-carrier_id ). CATCH cx_abap_invalid_value. cl_abap_unit_assert=>fail( `Unable to instantiate lcl_carrier` ). ENDTRY. ENDMETHOD.
  5. クラスを有効化し、ユニットテストを再度実行して、引き続き機能することを確認します。

    1. Ctrl + F3 を押してクラスを有効化します。

    2. 以前と同様にユニットテストを実行します。