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 Point | Test 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 ステップです。
- JUnit XML を生成(通常のテスト実行)
PublishTestResults@2で Tests タブへ表示(一次トリアージ用)- JUnit XML を解析して、各テストケースの “主キー” を決定(Test Case ID など)
- Azure DevOps REST API で Test Run を作成し、Plan / Point に紐付ける
- 結果を登録し、必要なら 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.1 | testCaseTitle / 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/構成/担当などの組み合わせで一意になります。したがって、スクリプトは次のように動かすと強いです。
- 対象の Plan / Suite を決める(スプリントの “最新 Test Plan” をスクリプト引数で受け取るなど)
- Suite 配下の Test Point 一覧を取得する(テストケースとポイントの対応表を作る)
- JUnit の結果(Test Case ID)を、対応する pointId に変換する
- 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 件超の回帰でも手動更新から解放され、品質データの一貫性も保てます。

コメント