Claude Media
Claude CodeでABAP Unitのテストを既存Zクラスに足す — 実行はSAP側に残す分担

Claude CodeでABAP Unitのテストを既存Zクラスに足す — 実行はSAP側に残す分担

abapGit管理の既存Zクラスに、ABAP UnitのテストをClaude Codeで足す依頼文と、本体を触らせないhooks・権限の設定例です。実行と判定はSAP側に残します。

abapGitで管理している既存のZクラスにABAP Unitのテストを足すなら、Claude Codeに任せるのは「テストクラスのファイルを書き足すこと」だけにします。実行と判定は、クラスが動いているSAPシステム側に残します。Claudeはgitのリポジトリ内のファイルしか見えず、ABAPの実行環境を持たないからです。この分担にすると、書かれたテストが通るかどうかを推測で語らせずに済みます。

この記事は、リポジトリがabapGitでSAPシステムと同期されていて、ABAP in Eclipse(ADT)などでテストを実行できる環境を前提にします。

テストの追加でClaudeに任せる範囲と、SAP側に残す範囲

作業を3つに分けると、誰が何をするかがはっきりします。

手順

テスト追加の流れと担当

  1. 1

    読む(Claude)

    リポジトリ上の本体クラスと既存のテストクラスのファイルを読み、未テストの分岐を洗い出させます。

  2. 2

    書く(Claude)

    テストクラスのファイルにだけ、テストメソッドを足させます。本体クラスのファイルは変更させません。

  3. 3

    取り込んで走らせる(人とSAP)

    abapGitでSAPシステムに取り込み、SAP側でABAP Unitを実行します。失敗メッセージはそのままClaudeに貼り戻します。

Claudeに「テストが通ることを確認して」と頼んでも、リポジトリのファイルだけでは確認できません。確認できないことを確認したように書かせないために、実行の手前で作業を区切ります。

abapGitのリポジトリで触るファイルを特定する

abapGitはSAPのオブジェクトを、1つ以上のファイルに分けてgitに置きます。クラスの場合、ファイル名は<オブジェクト名>.<オブジェクトタイプ>.<パート>.<拡張子>の形です。abapGitのドキュメントにあるクラスの例では、ABAP in Eclipseやソースベースのクラスビルダーで編集できる部分が、次のファイルに対応します。

ファイル中身
zcl_example.clas.abap中身クラス本体
zcl_example.clas.locals_def.abap中身ローカル定義
zcl_example.clas.locals_imp.abap中身ローカル実装
zcl_example.clas.testclasses.abap中身テストクラス
zcl_example.clas.macros.abap中身マクロ

ほかにメタデータを持つzcl_example.clas.xmlが1つあります。つまり、ABAP Unitのテストを足す対象は*.clas.testclasses.abapの1ファイルです。

見落としやすいのは、中身が空のファイルは存在しないという点です。ドキュメントには「Files do not exist if empty」とあり、テストをまだ書いていないクラスにはこのファイルが無いことになります。

フォルダの位置は.abapgit.xmlで決まります。abapGit自身の例ではSTARTING_FOLDERが/src/、FOLDER_LOGICがPREFIXです。リポジトリごとに違うので、依頼の前に.abapgit.xmlを読ませるか、パスを人が指定しておくと探索の無駄が減ります。

テストファイルがまだ無いクラスの扱い

テストファイルが無いクラスについて、手で新規作成してよいかは、abapGitのドキュメントに手順の記載がありません。同じドキュメントには、gitの側でファイルを作る方法はSAPシステムで取りうるオブジェクト名の制約を受けるため、オブジェクトは常にSAPシステムで作ってからpushすることを強く勧める、という趣旨の説明があります。

この記述は主にファイル名の制約の話ですが、運用としては次の順が無難です。

  1. SAP側でテストクラスを含むクラスを一度保存する(空のテストクラスで構いません)
  2. abapGitでpushし、*.clas.testclasses.abapができたことを確認する
  3. その後でClaudeにテストメソッドを足させる

依頼文の型

「このクラスのテストを書いて」だけでは、対象の範囲もファイルも定まりません。次のように、触るファイル・触らないファイル・書き方の条件・実行しないことを入れます。

zcl_order_discount の ABAP Unit を足してください。
 
- 編集してよいのは src/zcl_order_discount.clas.testclasses.abap だけです
- src/zcl_order_discount.clas.abap と .clas.xml は読むだけで、変更しないでください
- 既存のテストメソッドは残し、未テストの分岐に対応するメソッドを足してください
- 既存のテストクラスと同じ書き方(RISK LEVEL / DURATION / アサート方法)に合わせてください
- 実行はできないので、「通る」とは書かず、足したメソッドと狙った分岐の一覧を返してください

最後の1行が重要です。実行できない前提を依頼文に書いておくと、Claudeの報告が「どの分岐を狙ったか」に寄ります。

CLAUDE.mdに書いておく規約

毎回依頼文に書くより、CLAUDE.mdに置いたほうが安定します。Claudeはセッションの開始時にCLAUDE.mdを読みます。

## ABAP Unit
- テストは *.clas.testclasses.abap にだけ書く。本体(*.clas.abap)は変更しない
- テストクラスは FOR TESTING で宣言し、DURATION と RISK LEVEL を必ず付ける
- 既存のテストクラスの書き方(アサートの呼び方)に合わせる
- テストの実行はSAP側で人が行う。実行結果を推測で書かない

足されるテストの形

既存のテストクラスの書き方に合わせさせると、出てくる形はそろいます。abapGitのリポジトリにある文字列マップのテストクラスを見ると、次の特徴があります。

  • クラスはFOR TESTINGで宣言し、DURATION SHORTとRISK LEVEL HARMLESSを付ける
  • テストメソッドはMETHODS simple FOR TESTING RAISING ...のように宣言する
  • 検証はcl_abap_unit_assert=>assert_equals( act = ... exp = ... )で行う

例として、割引率を返すクラスにテストを足す形を挙げます。クラスとメソッドの名前は説明用の架空のもので、公式の例ではありません。

CLASS ltcl_discount DEFINITION
  FINAL
  FOR TESTING
  DURATION SHORT
  RISK LEVEL HARMLESS.
  PRIVATE SECTION.
    METHODS gold_gets_20_percent FOR TESTING.
    METHODS zero_amount_returns_zero FOR TESTING.
ENDCLASS.
 
CLASS ltcl_discount IMPLEMENTATION.
 
  METHOD gold_gets_20_percent.
    DATA(lo_cut) = NEW zcl_order_discount( ).
    cl_abap_unit_assert=>assert_equals(
      act = lo_cut->calc( iv_amount = 1000 iv_tier = 'GOLD' )
      exp = 200 ).
  ENDMETHOD.
 
  METHOD zero_amount_returns_zero.
    DATA(lo_cut) = NEW zcl_order_discount( ).
    cl_abap_unit_assert=>assert_equals(
      act = lo_cut->calc( iv_amount = 0 iv_tier = 'GOLD' )
      exp = 0 ).
  ENDMETHOD.
 
ENDCLASS.

既存のテストクラスがある場合は、新しいクラスを作らず、そのクラスのPRIVATE SECTIONにメソッド宣言を足し、IMPLEMENTATIONに本体を足す形になります。レビューでは、クラス宣言の属性(DURATIONやRISK LEVEL)が変わっていないかを差分で見ます。

数字だけ満たす弱いテストの見分け方

レビューで見るのは、メソッド名と期待値が分岐の意図に対応しているかです。

  • 期待値を、実装を読んだ結果から逆算してそのまま書いている(仕様の根拠が無い)
  • 例外が起きる分岐を、正常系のアサートだけで通している
  • 1つのメソッドに複数の分岐をまとめ、どれが失敗したか分からない

仕様が分からない分岐は、期待値を推測で埋めさせず、Claudeに質問として返させてください。仕様をコードから読む手順はClaude Codeでテストから仕様を読むが参考になります。

本体クラスを触らせない設定

依頼文とCLAUDE.mdは、あくまでお願いです。本体のファイルを変更できない状態にするには、権限のdenyルールか、hooksを使います。

権限のdenyルールで本体ファイルを守る

Editのdenyルールは、Claudeの組み込みのファイル編集ツールと、sedやtee、リダイレクトなどClaude Codeが認識するBashのファイル操作に適用されます。.claude/settings.jsonに次を置きます。

{
  "permissions": {
    "deny": [
      "Edit(**/*.clas.abap)",
      "Edit(**/*.clas.xml)"
    ]
  }
}

パスはgitignoreと同じパターン構文で、**/は任意の深さにマッチします。*.clas.abapは.clas.abapで終わるファイルだけに当たるため、.clas.testclasses.abapは対象外のはずです。設定したら、本体を編集させる依頼を一度投げて、拒否されることを確認してください。

Writeツールにパスのルールを書いても一致しません。Claude CodeはEdit(path)のルールで検査するため、Writeで書き込む場合もEditのルールを使います。

Pythonなどのスクリプトが内部でファイルを書き換えるケースは、denyルールの対象外です。すべてのプロセスをOSレベルで止めたい場合は、サンドボックスを有効にします。

PostToolUseのhookでテストの書き方を検査する

テストファイルを編集した直後に、決めた書き方を満たしているかを機械的に確かめるには、PostToolUseのhookが使えます。PostToolUseはツールの実行が成功した後に動き、終了コード2で返すと、標準エラー出力がClaudeに渡されます。ツールはすでに実行済みなので、取り消しではなくフィードバックです。

.claude/settings.jsonには次のように書きます。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "if": "Edit(**/*.clas.testclasses.abap)",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/check-abap-unit.sh",
            "args": []
          }
        ]
      }
    ]
  }
}

ifにはパーミッションルールの構文で、フックを動かす条件を書けます。ツールイベントでだけ評価され、条件に一致したときだけコマンドが動きます。

.claude/hooks/check-abap-unit.shは次のとおりです。

#!/bin/bash
f=$(jq -r '.tool_input.file_path')
 
for kw in "FOR TESTING" "DURATION" "RISK LEVEL"; do
  if ! grep -q "$kw" "$f"; then
    echo "$f に '$kw' がありません。テストクラスの属性を確認してください。" >&2
    exit 2
  fi
done
exit 0

file_pathは常に絶対パスで渡されます。このスクリプトは単純な文字列検査なので、ファイル内のどこかに語があれば通ります。構文の正しさや、テストが通るかどうかは確かめません。それはSAP側の仕事です。

SAP側での実行と、結果の貼り戻し

ファイルが書き終わったら、abapGitで取り込み、SAPシステム側でABAP Unitを実行します。実行はSAPシステム側のABAP Unit実行機能で行い、結果をClaude Codeへ貼り戻します。

Claudeに戻すのは、実行結果のうち次の3点です。

  1. 失敗したテストメソッド名
  2. 期待値と実際の値(アサートのメッセージ)
  3. 構文エラーなら、メッセージと行番号

貼り戻しの依頼文は短く済みます。

次のテストが失敗しました。メッセージを読み、
テストクラスと本体のどちらに原因がありそうか切り分けてください。
本体は変更せず、テストの修正案だけを出してください。
 
[ここに失敗メッセージ]

原因が本体にありそうなら、本体の修正は別の依頼に分けます。テストを足す依頼と本体を直す依頼を混ぜると、差分のレビューが難しくなります。同様の分担でJavaのテストを足す方法はClaude CodeでJUnit 5とMockitoのテストを書くに、Salesforce側のカバレッジを測ってから足す流れはClaude CodeでApexテストクラスのカバレッジを75%に上げる手順にあります。

つまずきやすい点

  • テストファイルを手で新規作成した: 手順の記載がないため、SAP側で作ってからpushする流れに寄せます
  • 差分に本体の変更が混ざる: denyルールを置いていても、git diff --statでテストファイル以外が変わっていないかは毎回確認します。Bash経由のスクリプトによる書き換えはdenyルールの対象外です
  • hookが動かない: スクリプトに実行権限を付けます(chmod +x)。jqも必要です
  • ifの条件が効いているか分からない: Writeで書かせたときにも動くか、空のテストファイルで一度試してください

まとめ

Claude Codeに任せるのはテストクラスのファイル編集までで、実行と判定はSAPシステムに残す分担にすると、確認できないことを確認済みとして報告させずに済みます。分担は依頼文に書くだけでなく、CLAUDE.mdで常時伝え、denyルールで本体の編集を止め、hookで書き方の抜けを検査する三段で固定できます。最初の1クラスでは、差分を人が読んで、期待値の根拠が仕様にあるかを確かめてから、同じ型を他のクラスへ広げてください。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. code.claude.com/docs/en/hooks
  2. code.claude.com/docs/en/permissions
  3. code.claude.com/docs/en/memory
  4. docs.abapgit.org/user-guide/reference/folders-filenames.html
  5. docs.abapgit.org/user-guide/reference/file-formats.html
  6. docs.abapgit.org/user-guide/repo-settings/dot-abapgit.html
この記事を共有:XはてブLinkedIn