generate-abap-unit-test について
generate-abap-unit-testは、ABAP開発者向けのクラス単位のテスト生成スキルです。依存関係を分析してテストケースを作り、インターフェースベースのテストダブルを使ったABAP Unitコードを生成し、プレビュー、書き込み、アクティベーション、実行まで支援します。
主な機能
- 01クラスのソース、メソッド、依存関係を取得
- 02分岐、エラー、出力に基づくケース設計
- 03インターフェースベースのテストダブル
- 04ABAP Unitテストクラスの生成
- 05コード書き込み、アクティベーション、実行
活用シーン
- 01ABAPクラスの公開メソッド向けテスト生成
- 02メソッドの分岐とエラー経路のカバー
- 03注入された依存関係を分離したクラスロジックのテスト
- 04テスト実行と失敗原因の調査
MCP 対応
ARC-1でSAPクラスの読み取りと更新、コードのアクティベーション、単体テストの実行を行います。また、mcp-sap-docsでABAP Unitとテストダブルのパターンを調べます。MCPクライアント向けのサービス提供については記載されていません。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ arc-mcp/ arc-1 --skill generate-abap-unit-testインストール後、Claude Code で/generate-abap-unit-testを入力して読み込まれたか確認します。
詳細
ABAP Unit テストの生成
依存関係の分析、テストダブル、メソッド単位の手術的な変更を用いて、クラスの ABAP Unit テストを生成します。
このスキルは、ARC-1(SAP システムへのアクセス)と mcp-sap-docs(ドキュメントとベストプラクティス)を組み合わせ、ABAP クラス向けに SAP Joule の「Unit Test Generation」機能を再現します。CDS エンティティを対象とする CDS 単体テストスキルとは異なり、このスキルは ABAP クラスを対象とし、CDS Test Double Framework の代わりにインターフェースベースのテストダブルを使用します。
スマートデフォルト(暗黙に適用し、質問しない)
| 設定 | デフォルト | 理由 |
|---|---|---|
| テストクラス名 | ltc_<short_class_name> | グローバルクラスの ABAP Unit テストは、ローカルの testclasses インクルード(CCAU)に配置します |
| テスト対象メソッド | すべてのパブリックメソッド | 一覧を確認した後、ユーザーが対象を絞り込めます |
| パッケージ | $TMP | 迅速なプロトタイピングに適しています |
| リスクレベル | HARMLESS | テストダブルは実データを変更しません |
| 実行時間 | SHORT | 単体テストは高速であるべきです |
| モック戦略 | インターフェースベースのテストダブル | 依存性注入に最適なパターンです |
入力
ユーザーはテスト対象の ABAP クラス(例:ZCL_TRAVEL_HANDLER)を指定します。
必須なのはクラス名のみです。ユーザーがクラス名だけを指定した場合は、スマートデフォルトを適用してすぐに進めます。
ユーザーは任意で次の項目を指定できます。
- テストクラス名(デフォルト:対象クラスの
testclassesインクルード内のローカルクラスltc_<short_class_name>) - テスト対象メソッド(デフォルト:すべてのパブリックメソッド)
- パッケージ(デフォルト:
$TMP) - 移送依頼(
$TMP以外のパッケージでのみ必要)
ステップ 1:クラスのコンテキストを収集する
クラス、そのメソッド、依存関係、既存のテストを読み取ります。
1a. クラスのソース全体を読み取る
メタデータとすべてのインクルード(既存のテストを含む)を 1 回の呼び出しで取得できるため、構造化形式を優先してください。
SAPRead(type="CLAS", name="<class_name>", format="structured")
説明、カテゴリ、パッケージなどのメタデータと、分割されたソース(main、testclasses、definitions、implementations、macros)を含む JSON が返されます。testclasses フィールドには既存のテストコードが含まれます(存在する場合)。別途取得せずに、現在のテスト範囲を分析するのに役立ちます。
メインソースのみを取得する場合は、次を使用します。
SAPRead(type="CLAS", name="<class_name>")
1b. シグネチャを含むすべてのメソッドを一覧表示する
SAPRead(type="CLAS", name="<class_name>", method="*")
シグネチャと可視性(public/protected/private)を含むすべてのメソッドが返されます。これを使ってクラスの API を把握し、テスト対象のメソッドを特定します。
1c. 依存関係のコンテキストを取得する
SAPContext(type="CLAS", name="<class_name>")
すべての依存関係を自動的に抽出し、それぞれの圧縮されたパブリック API コントラクトを取得します。これにより、次の情報が得られます。
- 実装されたインターフェースとそのメソッド
- スーパークラスの階層
- 注入された依存関係(コンストラクターのパラメーター)
- 使用されているクラスとそのパブリック API
- アクセスされるデータベーステーブル
依存関係の連鎖が深い複雑なクラスでは、depth=2 を使用します。
SAPContext(type="CLAS", name="<class_name>", depth=2)
1d. (任意)既存のテストクラスを読み取る
ステップ 1a で format="structured" を使用した場合、testclasses フィールドに既存のテストコードがすでに含まれています(存在しない場合は null)。その場合、このステップは省略してください。
それ以外の場合は、テストクラスを個別に取得します。
SAPRead(type="CLAS", name="<class_name>", include="testclasses")
テストクラスが存在しない場合、取得に失敗することがありますが、問題ありません。テストが存在する場合は、カバレッジの重複を避け、既存のテストパターンに従えるように内容を分析してください。
ステップ 2:メソッドを分析し、テストケースを提案する
各パブリックメソッドについて、ソースコードを分析し、テスト可能なシナリオを特定します。
依存関係の分類
クラスが使用する依存関係を分類します。
| カテゴリ | 確認する対象 | テスト戦略 |
|---|---|---|
| モック可能 | インターフェース(if_*、zif_*)を介したコンストラクター注入 | そのインターフェースを実装するテストダブルを作成します |
| スタブ可能 | データベースアクセス(SELECT、INSERT、UPDATE、DELETE) | SETUP でフィクスチャデータを準備するか、DB レイヤーにテストダブルを使用します |
| 透過的 | 内部の private/protected ヘルパーメソッド | モックしません。通常どおり実行します |
| フレームワーク | SAP フレームワーククラスへの静的呼び出し(例:cl_abap_context_info) | 注入可能なインターフェースでラップするか、制約を受け入れます |
メソッドごとの分析
各パブリックメソッドについて、以下を特定します。
- 分岐:IF/CASE/COND/SWITCH 文 — 各分岐にテストが必要です
- エラーパス:RAISE EXCEPTION、TRY-CATCH ブロック — エラーシナリオをテストします
- 戻り値:メソッドが生成するもの — 出力を検証します
- 状態の変更:インスタンス属性またはデータベースへの副作用 — 変更を検証します
- 使用される依存関係:呼び出されるモック可能な依存関係 — 期待値を設定します
ユーザーへの出力
特定したテストケースを、メソッドごとにグループ化した番号付きリストで提示します。
Identified test cases for ZCL_TRAVEL_HANDLER:
Method: CREATE_TRAVEL (public)
1. [HAPPY] test_create_travel_success — Create travel with valid data, verify travel ID returned
2. [ERROR] test_create_no_customer — Missing customer, expect CX_TRAVEL_ERROR
3. [BRANCH] test_create_with_discount — Discount > 0 triggers calculation path
Method: VALIDATE_DATES (public)
4. [HAPPY] test_dates_valid — End date after begin date
5. [ERROR] test_dates_reversed — Begin date after end date, expect exception
6. [EDGE] test_dates_same_day — Same begin and end date, should be valid
ユーザーに次のように尋ねます。「どのテストケースを生成しますか?(すべて/特定の番号/スキップするものはありますか?)」
ユーザーが「すべて」と答えた場合、または希望を示さずに応答した場合は、すべて生成します。
ステップ 3:テストフレームワークのリファレンスを取得する
mcp-sap-docs を使って、最新の ABAP Unit とテストダブルのパターンを取得します。
search("ABAP Unit cl_abap_unit_assert")
search("test double interface mock ABAP")
取得したドキュメントを、生成するコードパターンの参考にしてください。次の点を確認してください。
cl_abap_unit_assertの正しいアサーションメソッド名(assert_equals、assert_initial、assert_not_initial、assert_bound、fail)- インターフェースのテストダブルを作成するパターン
FOR TESTINGクラスとメソッドの構文- リスクレベルと実行時間のアノテーション
ステップ 4:テストクラスを生成する
次の構造に従って、完全な ABAP テストクラスを生成します。
テンプレート
"! @testing <CLASS_UNDER_TEST>
CLASS <test_class_name> DEFINITION
FINAL
FOR TESTING
DURATION SHORT
RISK LEVEL HARMLESS.
PUBLIC SECTION.
PROTECTED SECTION.
PRIVATE SECTION.
CLASS-DATA cut TYPE REF TO <class_under_test>.
" Test doubles for mockable dependencies
CLASS-DATA mock_<dep1> TYPE REF TO <interface_1>.
CLASS-DATA mock_<dep2> TYPE REF TO <interface_2>.
CLASS-METHODS class_setup RAISING cx_static_check.
CLASS-METHODS class_teardown.
METHODS setup.
"! <description of test case 1>
METHODS <test_method_1> FOR TESTING RAISING cx_static_check.
"! <description of test case 2>
METHODS <test_method_2> FOR TESTING RAISING cx_static_check.
" ... one method per selected test case
ENDCLASS.
CLASS <test_class_name> IMPLEMENTATION.
METHOD class_setup.
" One-time initialization — create test doubles
" If using a test double framework:
" mock_<dep1> = cl_abap_testdouble=>create( '<interface_1>' ).
" Or create a local test double class (see below)
ENDMETHOD.
METHOD class_teardown.
" Clean up
ENDMETHOD.
METHOD setup.
" Per-test CUT instantiation with fresh state
" Inject test doubles via constructor
cut = NEW <class_under_test>(
io_<dep1> = mock_<dep1>
io_<dep2> = mock_<dep2>
).
ENDMETHOD.
METHOD <test_method_1>.
" Arrange — configure mock behavior
" cl_abap_testdouble=>configure_call( mock_<dep1>
" )->returning( VALUE #( <expected_return> ) )->and_expect( )->is_called_once( ).
" mock_<dep1>-><method>( ).
" Act — call the method under test
DATA(lv_result) = cut-><method>( <params> ).
" Assert — verify expected behavior
cl_abap_unit_assert=>assert_equals(
act = lv_result
exp = <expected_value>
msg = '<assertion message>' ).
ENDMETHOD.
METHOD <test_method_error>.
" Arrange — set up conditions that trigger the error
" Act & Assert — expect exception
TRY.
cut-><method>( <invalid_params> ).
cl_abap_unit_assert=>fail( msg = 'Expected exception was not raised' ).
CATCH <exception_class> INTO DATA(lx_error).
" Optionally verify exception details
cl_abap_unit_assert=>assert_not_initial(
act = lx_error->get_text( )
msg = 'Exception message should not be empty' ).
ENDTRY.
ENDMETHOD.
" ... more test methods
ENDCLASS.
ローカルテストダブルのパターン
cl_abap_testdouble を利用できない場合、またはインターフェースが複雑な場合は、ローカルテストダブルクラスを作成します。
" Local test double — defined in the test include
CLASS lcl_mock_<dep> DEFINITION FOR TESTING.
PUBLIC SECTION.
INTERFACES <interface>.
DATA last_<method>_input TYPE <param_type>.
DATA <method>_result TYPE <return_type>.
ENDCLASS.
CLASS lcl_mock_<dep> IMPLEMENTATION.
METHOD <interface>~<method>.
last_<method>_input = <param>.
<return_param> = <method>_result.
ENDMETHOD.
ENDCLASS.
テストデータの生成ルール
テストデータを生成する際は、次の原則に従ってください。
- 最小限のデータ:シナリオの実行に必要な最小限の値を使用します。通常、テストごとに 1~3 件のレコードを使用します。
- わかりやすい値:期待される結果を簡単に検証できる値を選びます。
- 計算の場合:37.83 - 4.27 ではなく、100.00 - 10.00 = 90.00 を使用します
- ステータスの確認の場合:IF/CASE 式で使用されているステータス値をそのまま使用します
- 文字列操作の場合:短く、見分けやすい文字列を使用します
- 型の一致:ABAP の型に正確に合わせます。
- NUMC フィールドには
'001'(先頭にゼロを付けた文字列)を使用します - DEC/CURR フィールドには
'100.00'を使用します - DATS フィールドには
'20260101'を使用します - ABAP_BOOL には
abap_true/abap_falseを使用します
- NUMC フィールドには
- 決定性:
sy-datum、sy-uzeit、cl_abap_context_info=>get_system_date( )、乱数値は使用しないでください - 独立性:各テストメソッドを自己完結させます。セットアップメソッドで CUT の状態を毎回初期化します
命名規則
- テストクラス:クラスの
testclassesインクルード内にあるローカルクラスltc_<short_class_name> - テストメソッド:
test_<method>_<scenario>(例:test_create_success、test_validate_no_date) - メソッド名には snake_case を使用します(テストメソッドの ABAP 規約)
- メソッド名は 30 文字未満にします
- モック変数:
mock_<short_dep_name>(例:mock_persistence、mock_validator)
ステップ 5:プレビューして確認する
生成した testclasses インクルードのソースコード全体をユーザーに表示し、次のように尋ねます。
「生成した testclasses インクルードはこちらです。SAP システムに書き込みますか?(はい/先に編集/キャンセル)」
ユーザーが編集を希望する場合は、先に反映してから次に進みます。
ステップ 6:書き込み、有効化、テスト
6-pre. 生成コードの lint チェック(任意)
書き込む前に、lint ルールに照らして生成コードを検証します。
SAPLint(action="lint", source="<generated_testclasses_include>", name="<class_under_test>")
続行する前に、lint で見つかった問題を修正します。有効な場合(デフォルト:オン)、書き込み前の lint 検証も自動的に実行されます。
6a. ローカルテストクラスを対象クラスの testclasses インクルードに書き込む
SAPWrite(action="update", type="CLAS", name="<class_under_test>", include="testclasses", source="<generated_testclasses_include>", transport="<transport>")
これはローカルインクルードへの書き込みであり、独立したグローバルクラスの作成ではありません。ロックハンドルを渡したり、ABAP ソースにロック/ロック解除のコードを追加したりしないでください。ARC-1 が ADT のロック → CCAU インクルードが存在しない場合は初期化 → 書き込み → ロック解除の手順を内部で実行します。新規クラスの場合、ARC-1 は書き込み前に不足している testclasses インクルードを自動作成します。
6b. 既存のテストを更新する
SAPWrite(action="update", type="CLAS", name="<class_under_test>", include="testclasses", source="<full_updated_testclasses_include>", transport="<transport>")
既存のローカルテストメソッドを少し修正する場合は、インクルードの作成後にメソッド編集を使用します。
SAPWrite(action="edit_method", type="CLAS", name="<class_under_test>", method="<ltc_class>~<failing_method>", source="<fixed_method_body>")
注: 更新アクションでは、transport の指定を推奨しますが、必須ではありません。明示的に transport を指定しない場合、ARC-1 はロックによって提供された corrNr を自動的に引き継ぎます。
6c. テスト対象クラスを有効化する
SAPActivate(type="CLAS", name="<class_under_test>")
有効化すると、行番号を含む詳細なエラー/警告メッセージを含む、構造化された応答が返されます。これらを使って、問題の正確な箇所を特定してください。
6d. 単体テストを実行する
SAPDiagnose(action="unittest", type="CLAS", name="<class_under_test>")
6e. 結果を報告し、失敗を修正する
ユーザーに次の内容を示します。
- 成功/失敗/スキップしたテストの数
- 失敗がある場合:アサーションメッセージと、失敗したテストメソッド
- 失敗に対する修正案
テストが失敗した場合:
- 失敗メッセージを分析します
- モックの設定、アサーション、テスト対象クラス(CUT)のロジックのどれに問題があるかを判断します
- メソッド編集を使用して、該当するテストメソッドを修正します。
SAPWrite(action="edit_method", type="CLAS", name="<class_under_test>", method="<ltc_class>~<failing_method>", source="<fixed_source>") - 再度有効化して実行します
エラー処理
よくある問題と修正方法
| エラー | 原因 | 修正方法 |
|---|---|---|
class_setup が失敗する | CUT のインスタンス化が誤っています。コンストラクターのパラメーターが不足しているか、型が誤っています | CUT のコンストラクターシグネチャを確認し、必要な依存関係がすべて注入されていることを確認します |
| モックの注入に失敗する | コンストラクターパラメーターの型が一致していません。モックが期待されるインターフェースを実装していません | インターフェース名を確認し、CUT がインターフェースではなく具象クラスを要求しているかを確認します |
| アサーションが失敗する | 期待値またはテストデータが誤っています | CUT メソッドのロジックを再確認し、期待値または入力データを調整します |
| 有効化エラー | 生成されたコードに構文エラーがあります。タイプミス、誤った型、変数宣言の欠落などです | 有効化エラーを読み、構文を修正してから再度有効化します |
cl_abap_testdouble が見つからない | システムでテストダブルフレームワークを利用できません(古いリリースの場合) | 代わりにローカルテストダブルクラスのパターンを使用します |
| CUT にメソッドが見つからない | メソッドがプライベートまたは保護されています。テストから呼び出せません | パブリックメソッドのみをテストします。保護されたメソッドの場合は FRIENDS 句を使用します |
testclasses/CCAU インクルードが見つからない | 新規クラスには ABAP Unit インクルードがまだありません | SAPWrite(action="update", type="CLAS", include="testclasses", source=...) を使用します。ARC-1 が自動的に初期化します |
edit_method でメソッドが見つからない | ローカルの ltc_* メソッドがまだ存在しないか、誤ったローカルクラス名が使用されています | まず update include="testclasses" で testclasses インクルード全体を書き込み、その後、既存のメソッドに edit_method を使用します |
注記
BTP とオンプレミスの違い
- BTP:ローカルの
ltc_*テストクラスをカスタム Z*/Y* クラスに記述します。ABAP Cloud 構文を使用する必要があり、従来の ABAP ステートメントは使用できません。モックに使用できるのは、リリース済み API のみです。cl_abap_testdoubleを利用できます。コンストラクターインジェクションが標準的なパターンです。 - オンプレミス:制限なく柔軟に利用できます。従来の ABAP を使用できます。
cl_abap_testdoubleは 7.51 以降で利用できます。それ以前のシステムでは、ローカルテストダブルクラスを使用します。
このスキルでは対応しないこと
- CDS 単体テストではありません:CDS エンティティには、generate-cds-unit-test スキルを使用します(CDS Test Double Framework を使用します)。
- 統合テストではありません:このスキルでは、エンドツーエンドのフローではなく、依存関係をモックした分離された単体テストを生成します。
- パフォーマンステストではありません:パフォーマンス分析には ABAP profiler(SAPDiagnose)を使用します。
- RAP ビヘイビアーテストではありません:RAP BO のテストダブルには、別のパターンが必要です。
このスキルを使用する場面
- クラスのパブリックメソッドに、複雑なビジネスロジックが含まれている場合
- クラスが依存性注入(インターフェースを指定したコンストラクターパラメーター)を使用している場合。モックに適しています。
- ユーザーが TDD の出発点としてテストカバレッジを求めている場合
- クラスをリファクタリングする際に、変更前に回帰テストを作成したい場合
- パブリックメソッドのないクラスや、単なるデータコンテナー(構造体/定数)には使用しないでください。Step 2 の分析でテスト可能なシナリオ(分岐、エラーパス、意味のある戻り値)が見つからない場合は、次のようにユーザーに警告してください。「このクラスには、テスト対象となる複雑なパブリックメソッドがありません。ここで単体テストを行う価値があるか検討してください。」