Azure DevOpsでJUnitのTagsを活用する方法:PublishTestResults@2の制約とTest Plans/Test Point自動連携(REST API)

Azure DevOps パイプラインで JUnit(XML)を PublishTestResults@2 で取り込んでいると、「JUnit の Tags を Test Results で見たい」「そのまま Test Plans の Test Point に自動反映したい」という要望が必ず出てきます。本記事では、標準機能の限界を踏まえたうえで、現場で破綻しにくいワークアラウンドと、自動紐付けを実現する設計・実装の勘所を整理します。

目次

JUnit の <property name=”Tags”> が Test Results に表示されない理由

結論から言うと、Azure DevOps の PublishTestResults@2 タスクで JUnit XML を取り込んでも、<testcase><properties><property name="Tags" value="..." /> のようなカスタムメタデータは、Test Results(Tests タブ)上で「タグ」として表示されません。これは「JUnit XML をすべて解釈する」のではなく、Azure DevOps 側が “表示・分析に必要な一部フィールドだけ” をマッピングしているためです。

Microsoft Q&A の受理済み回答でも、PublishTestResults@2 は JUnit スキーマの一部のみを解析し、<properties> 配下のようなカスタム情報は Test Results の Web UI に出ない、と明言されています。つまり「JUnit の Tags をそのまま UI に出す」公式ルートは現時点では用意されていません。

加えて、Test Run / Test Result に “カスタムフィールド” を持たせる仕組み自体は用意されていますが、少なくとも現状は UI 表示がサポートされていない(将来的に表示を追加予定)という位置づけです。カスタムフィールドを使って「タグ相当」を保存することはできても、UI で見える化できない点が落とし穴です。

やりたいことJUnit XML に書けるPublishTestResults@2 が取り込むTests / Test Results 画面で見える備考
テスト名◯(name)◯◯一覧・検索のキーになる
クラス名◯(classname)◯◯(詳細で参照)ランナーにより出力差あり
実行時間◯(time)◯◯スイート timestamp などと合わせて計算されるケースあり
失敗メッセージ / スタックトレース◯(<failure> など)◯◯トリアージに重要
独自 Tags(<properties>)◯△(取り込み対象外として扱われがち)×「タグ表示」は未サポート
添付ファイル(JUnit)◯(仕組みあり)◯(条件あり)◯(添付として)JUnit 添付は sprint 229 以降など条件あり

まずゴールを分解する:タグ表示と Test Plans 連携は別問題

「JUnit のタグを Test Results で見たい」と「Test Plans(Test Case / Test Point)へ自動反映したい」は、似ているようで別の層の課題です。

  • Test Results(Tests タブ):パイプライン実行結果の可視化が中心。JUnit を取り込むが “メタデータの表現力” は限定的。
  • Azure Test Plans:テスト管理(Test Plan / Suite / Case / Point)を “Work Item として” 維持する世界。タグ運用やフィルタが得意。

このため、現実的には次のどれか(または併用)に落とし込むと運用が安定します。

優先したいこと現実解強み弱み
まず Test Results で“タグっぽく”絞り込みたいテスト名にタグを埋め込む最小コスト / すぐ効く本物のタグではない(表記ゆれに弱い)
詳細なレポート(タグ・履歴・グラフ)を見たい外部レポート(Allure / ReportPortal 等)を併用表現力が高い参照先が分散する
Test Plans を SSOT にし、Test Point を自動更新したいマッピング層 + REST API で Test Run / Test Point を更新大規模回帰でも運用が回る設計と実装が必要

ワークアラウンド:JUnit Tags を “画面で使える形” に寄せる

テスト名にタグを埋め込む(最短で効く)

Test Results の一覧・検索は基本的に “テスト名” が主役です。そこで、JUnit の name にタグを埋め込み、検索語として扱える形にします。Microsoft 側の回答でも現実的な回避策として挙げられています。

<testcase classname="LoginTests"
          name="[Smoke][UI] should login with valid user"
          time="0.12" />

この方式を破綻させないコツは「タグのフォーマットを固定する」ことです。おすすめは以下です。

  • 角括弧で囲む(例:[Smoke] [Regression] [API])
  • タグの順序を固定(例:[レベル][領域][機能])
  • 表記ゆれ禁止の短いタグ(例:UI と Ui を混在させない)

なお、テスト名には文字数制限が絡むことがあります。Azure DevOps のドキュメント上も、テスト名(fully qualified name)に文字数制限がある旨が触れられています。タグを増やしすぎると、長いパラメータ付きテストで詰むので注意してください。

JUnit の添付サポートを使って “タグ情報ファイル” をぶら下げる

「Test Results の行にタグ列を出す」は難しくても、失敗時に必要な情報を “添付ファイル” としてぶら下げれば、トリアージ速度は大きく上がります。PublishTestResults@2 は JUnit の添付サポートを持ち、条件を満たすとテスト結果へ添付を出せます。

具体例として、各テストケースの system-out に “添付パターン” を書き、タグや環境情報をまとめた JSON を添付してしまう、という手があります。

<testcase classname="LoginTests" name="should login" time="0.12">
  <system-out>[[ATTACHMENT|artifacts/testmeta/login_should_login.json]]</system-out>
</testcase>

JUnit 添付には “利用できる Azure DevOps のバージョン条件” がある点、また Azure DevOps Server では利用可否に差がある点がドキュメントに明記されています。オンプレ環境の場合は特に事前確認が必須です。

外部レポートを併用して、Azure DevOps は“入口”にする

タグ、履歴、失敗傾向、スクリーンショット、環境差分などを “1画面で” 追いたいなら、Allure や ReportPortal のようなテストレポート基盤を併用し、Azure DevOps の Tests タブには「最低限の結果+リンク」を置く、という役割分担が現実的です。

運用の型としては、次のようにすると迷子になりません。

  • Azure DevOps(Tests タブ):ビルド品質のゲート、失敗の一次発見
  • 外部レポート:タグで深掘り、履歴・スクリーンショット・ログ参照
  • Test Plans:回帰テスト資産(Test Case / Test Point)の管理

Test Plans と JUnit を自動紐付けしたい場合の前提知識

Azure Test Plans では、Test Plan / Test Suite / Test Case は work item として管理されます。そして “実行可能な単位” は Test Case そのものではなく、Test Suite・構成・担当者などと組み合わさった Test Point です。つまり「毎イテレーションで 1,000 件以上のテストポイントを更新する」運用であれば、狙うべき自動化は Test Point の更新・履歴蓄積です。

用語意味運用上のポイント
Test Planテスト活動の箱(スプリントやリリース単位)イテレーション単位で切り替えるチームが多い
Test Suiteシナリオや観点で Test Case を束ねる静的 / 要求ベース / クエリベースなど種類がある
Test Caseテスト仕様(再利用される資産)ID が安定しやすく、マッピングの軸に向く
Test PointTest Case × Suite × 構成 × 担当者… の実行単位更新対象。スイートや構成が変わると ID も変わり得る

ここで重要なのは、JUnit の <testcase> と Azure Test Plans の Test Point を “自動で” 結びつける標準機能は用意されていない、という点です。Azure DevOps には「自動テストとテストケースを関連付ける」考え方はありますが、一般的には Visual Studio / Test Explorer を中心にした流れが想定され、JUnit の XML を投げただけで Test Plans 側が魔法のように追従するものではありません。

現実解:マッピング層を作り、REST API で Test Run / Test Point を更新する

「JUnit のタグで素早くフィードバックし、その後 Test Point を更新」という理想に近づけるなら、結局は マッピング層 が要ります。ポイントは “JUnit 側の識別子” を、Azure DevOps 側の “Test Case(できれば ID)” に寄せることです。

マッピングの作り方:おすすめ順

方式JUnit 側に入れるもの運用コストおすすめ度補足
テスト名に Test Case ID を埋め込むTC1234_... のような接頭辞低高コミュニティでも「ID をタイトルに入れる」案が挙げられる
mapping.csv / mapping.yaml を別管理(JUnit はそのまま)中中テスト改名に弱いが導入しやすい
コードに独自アノテーション(TestCaseId)メタデータ中〜高中JUnit レポートに確実に出す仕組みが別途必要

“毎イテレーションで Test Plan が変わる” チームほど、Test Point ID は揺れます。揺れる ID に賭けるより、揺れにくい Test Case ID を主キーに寄せ、実行時に Test Point を引き直す設計が堅いです。

パイプライン実装の全体像(推奨アーキテクチャ)

実装は難しく見えますが、分解すると 5 ステップです。

  1. JUnit XML を生成(通常のテスト実行)
  2. PublishTestResults@2 で Tests タブへ表示(一次トリアージ用)
  3. JUnit XML を解析して、各テストケースの “主キー” を決定(Test Case ID など)
  4. Azure DevOps REST API で Test Run を作成し、Plan / Point に紐付ける
  5. 結果を登録し、必要なら Test Point の outcome も更新する

API でできること:どのエンドポイントを叩くか

目的代表 APIポイント
Test Run を作る(Test Plan に紐付け)POST .../_apis/test/runs?api-version=7.1リクエストで plan.id と pointIds を指定できる
Test Run に結果を追加POST .../_apis/test/Runs/{runId}/results?api-version=7.1testCaseTitle / automatedTestName / outcome などを投入
Test Point の outcome を更新PATCH .../_apis/testplan/Plans/{planId}/Suites/{suiteId}/TestPoint?api-version=7.1「ポイントを Active に戻す」「outcome 更新」用途が明記されている
Test Case のタグ(System.Tags)を更新PATCH .../_apis/wit/workitems/{id}?api-version=7.1/fields/System.Tags を更新。例では区切りが ;(セミコロン)

Test Case のタグ運用は UI からも可能で、複数タグはカンマ区切りで入力する案内があります。一方で REST API 例では System.Tags の値はセミコロン区切りになっており、ここで混乱しがちです(どちらにせよ “タグ自体に区切り文字を含めない” が安全です)。

YAML 例:JUnit 公開+自前スクリプトを直後に実行

steps:
- script: |
    ./gradlew test
  displayName: "Run JUnit tests"

- task: PublishTestResults@2
  inputs:
    testResultsFormat: "JUnit"
    testResultsFiles: "**/TEST-*.xml"
    mergeTestResults: true
    failTaskOnFailedTests: false
    testRunTitle: "JUnit Regression ($(Build.BuildNumber))"
  displayName: "Publish JUnit results to Tests tab"

- task: PythonScript@0
  inputs:
    scriptSource: "filePath"
    scriptPath: "tools/ado_sync/junit_to_testplans.py"
    arguments: >
      --org $(System.CollectionUri)
      --project "$(System.TeamProject)"
      --plan-id 123
      --suite-id 456
      --junit-glob "**/TEST-*.xml"
      --pat "$(ADO_PAT)"
  displayName: "Sync JUnit results to Azure Test Plans"

PublishTestResults@2 の入力例(YAML)が公式ドキュメントにも掲載されているので、タスク設定自体はそこに寄せておくと後で迷いません。

Python 実装イメージ:やることは「抽出→変換→投入」

「JUnit XML → Test Plans」を一気にやろうとすると大変なので、スクリプトは “変換器” に徹させるのがコツです。

  • 抽出:JUnit XML から classname / name / 結果(passed/failed)/ メッセージ を集める
  • 変換:テスト名から Test Case ID を引く(または mapping.csv 参照)
  • 投入:Run 作成 → 結果追加 → 必要なら Point 更新
# 疑似コード(構造だけ掴む用)
parse_junit(xmls) -> list[test_result]  # name, classname, outcome, message

# 例:テスト名の先頭 "TC1234_" を拾って Test Case ID にする
for r in test_results:
    r.test_case_id = extract_tc_id(r.name) or lookup_mapping(r)

# (A) Test Run を作る(plan と pointIds を付ける)
run_id = api_create_run(plan_id, point_ids)

# (B) 結果を追加
api_add_results(run_id, [
  { "testCaseTitle": r.name, "automatedTestName": f"{r.classname}.{r.name}", "outcome": r.outcome }
  for r in test_results
])

# (C) Test Point の outcome を更新(必要な場合)
api_update_testpoints(plan_id, suite_id, [
  { "id": point_id, "results": { "outcome": "Passed" } }
])

Run 作成(POST .../_apis/test/runs)と結果追加(POST .../_apis/test/Runs/{runId}/results)は、Microsoft Learn の REST API リファレンスにリクエスト例があるので、まずはその形に合わせて “最小の成功” を作るのが近道です。

“最新イテレーションの Test Point に自動マッピング”を成立させる設計

質問の核心である「JUnit の <testcase> を最新 Test Plan の特定 Test Point に自動マッピングできるか?」に対して、標準機能だけで完結する答えは No ですが、“設計で成立させる” ことはできます。鍵は次の 2 つです。

  • 主キーは Test Case ID に寄せる(テスト資産の同一性)
  • Test Point は実行時に引き直す(イテレーションで揺れるため)

実行時に Test Point を引き直す方法

Test Point は「Test Case を Suite に入れたときに生成される実行単位」で、Suite/構成/担当などの組み合わせで一意になります。したがって、スクリプトは次のように動かすと強いです。

  1. 対象の Plan / Suite を決める(スプリントの “最新 Test Plan” をスクリプト引数で受け取るなど)
  2. Suite 配下の Test Point 一覧を取得する(テストケースとポイントの対応表を作る)
  3. JUnit の結果(Test Case ID)を、対応する pointId に変換する
  4. Run 作成時に pointIds を指定し、結果投入・Point 更新まで一気に流す

この方式にすると、イテレーションごとに Test Plan を切り替えても「Test Case ID さえ安定していれば追従できる」ため、1,000 件規模の回帰でも手動更新をほぼ消せます。

Test Plans 側のタグを JUnit 側から同期するなら

「JUnit のタグを Test Plans のタグに反映したい」は、さらに一段上の同期です。ここで理解しておきたいのは、Test Plans の “タグ” は Test Case を含む work item のタグ(System.Tags)であり、JUnit XML の <properties> から自動で流し込む標準機能は用意されていない、という点です。

やるなら、スクリプト側で以下を実施します。

  • JUnit のタグ(例:Smoke, UI)を抽出
  • 変換ルールを適用(例:smoke は Smoke に正規化、タグ数制限、禁止文字チェック)
  • 対象 Test Case(work item)に System.Tags を PATCH

なお、タグは区切り文字(カンマやセミコロン等)を含めるべきでないこと、UI ではカンマで複数入力できることが明記されています。同期処理では “タグ自体に区切り文字が入らない” 前提でバリデーションしておくと安全です。

大規模回帰(1,000+ Test Point)で破綻しない運用のコツ

「Test Results は速報」「Test Plans は台帳」に役割分担する

理想は “すべて Azure DevOps の 1 画面で完結” ですが、現実は表示できるメタデータが異なります。そこで、

  • 開発チームへの一次連絡:Tests タブ(速報)
  • 品質の正式記録と追跡:Test Plans(台帳)

に分けると、最短でフィードバックしつつ、後追いの整合性も保てます。

マッピング漏れは「失敗」ではなく「検知イベント」として扱う

1,000 件規模で一番起きる事故は、テスト追加・改名・分割によるマッピング漏れです。これを “静かに無視” すると Test Plans が腐ります。おすすめは以下です。

  • マッピングできない JUnit testcase があれば、パイプラインを Warning 扱いにする
  • 別途、未マッピング一覧(CSV)を成果物として残す
  • 必要なら、Azure DevOps にバグ(Work Item)を自動起票し、担当へアサインする

スイート構成を変えるチームは「pointId のキャッシュ」を持つ

Test Point を毎回 API で引くのは正攻法ですが、スイートが巨大だと API 呼び出しも増えます。そこで、Plan/Suite の point 一覧を取得してキャッシュし、差分だけ更新する方式にすると負荷が下がります(ただしキャッシュ破損時に備えて “フルリフレッシュ” も残す)。

標準機能で寄せたい場合の代替案

Marketplace 拡張で Test Plans への取り込みを委ねる

「自前実装の保守が辛い」「とにかく Test Plans に統合したい」場合、Azure DevOps Marketplace の拡張で “JUnit などの結果を Test Plans に取り込む” 方向もあります。例えば Solidify の Test Result Importer は、JUnit/xUnit などの結果を Azure Test Plans に集約し、スイート/テストケースの生成や上書きなどを支援する旨が説明されています。

ただし、拡張の導入は権限・コスト・ガバナンスに関わるため、まずは PoC(限定スイート)で「必要な粒度まで反映できるか」「自社の命名規約に耐えるか」を確認してから本番適用するのが安全です。

まとめ:Azure DevOps で JUnit の Tags と Test Plans を活かす最短ルート

  • JUnit XML の <properties> に Tags を入れても、PublishTestResults@2 の Tests(Test Results)画面で “タグとして表示” する標準機能は用意されていません。
  • 最速のワークアラウンドは「テスト名にタグを埋め込む」。検索・フィルタの体験が一気に改善します。
  • 本気で Test Plans と連携するなら、「JUnit 名 ⇔ Test Case ID」のマッピング層を用意し、REST API で Test Run 作成・結果追加・Test Point 更新を自動化するのが王道です。
  • Test Plans のタグ(System.Tags)へ同期する場合も、JUnit からの自動取り込みは標準機能ではなく、Work Item 更新 API での同期処理が前提になります。

「Tests タブで素早く検知 → Test Plans で台帳更新」を設計として分離し、マッピングと API 更新を薄いスクリプトに閉じ込めると、1,000 件超の回帰でも手動更新から解放され、品質データの一貫性も保てます。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次