modernize-test-starter について
UI5 開発者向けに、ユニットテストと OPA5 テストを Test Starter 構成へ移行するスキルです。旧構成の検出、ファイルと設定の変更、移行後の検証を扱います。
主な機能
- 01ユニットテストと OPA のパターン検出
- 02Test Starter のスイート設定作成
- 03ユニットテストと OPA ジャーニーの変換
- 04条件に応じた iframe 移行
- 05構成と残存ファイルの確認
活用シーン
- 01UI5 プロジェクトの旧テスト構成の刷新
- 02OPA5 ランチャーの Test Starter 移行
- 03変換後に残ったファイルの特定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ ui5/ plugins-coding-agents --skill modernize-test-starterインストール後、Claude Code で/modernize-test-starterを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/linter-invisible-patterns.mdpattern-a-modernization.mdpattern-b-modernization.mdpattern-u-iframe-migration.mdtestsuite-configuration.mdscripts/check-dangling-entries.jscheck-dangling-entries.test.jsdetect-cross-window-imports.jsdetect-cross-window-imports.test.jsparse-testsuite.jsparse-testsuite.test.js詳細
Test Starter へのモダナイズ
このスキルでは、UI5 プロジェクトのテストインフラ全体(ユニットテストと OPA5 統合テストの両方)を、Test Starter の概念に合わせてモダナイズします。
モダナイズする理由
Test Starter は、テストごとの HTML ブートストラップを、単一の汎用テストページと宣言型の testsuite.qunit.js 設定に置き換えます。利点:定型的な HTML/JS をなくし、QUnit/Sinon/カバレッジのセットアップを自動的に処理し、CSP への準拠を確保して、一貫性のある設定を提供します。また、テストごとの分離と並列実行を可能にします。
Test Starter へのモダナイズを絶対に省略しないでください
OPA5 のテスト HTML ファイルは必ず変換できます。機械的なパターンに従っているためです。無効な言い訳:「複雑なモックサーバー」(ホストにあり、エントリーポイントにはありません)、「sap.ui.define のモンキーパッチ」(同様です)、「FLP サンドボックスのブートストラップ」(同様です)、「ファイルが多すぎる」(一括処理してください)、「複雑すぎる」(無効です)。
| 種類 | 例 | 対応 |
|---|---|---|
| テストドライバー | opa/<Area>/<TestName>.qunit.html | Test Starter に変換 |
| テストホスト | test<ServiceName>.html | そのまま維持(テスト対象アプリを iframe で実行) |
| テストスイート | testsuite.qunit.html | testsuite.qunit.js に置き換え |
| 開発用サンドボックス | flpSandbox.html | 無視 |
唯一の実質的な技術的課題は、OPA5 のエントリーポイントで QUnit ブートストラップライブラリの読み込みに jQuery.sap.require() を使用している場合です。sap.ui.require に置き換えるか、Test Starter が自動的に読み込むことを記載してください。
前提条件
manifest.json → sap.app/id を読み取ります。これが名前空間です。
<NAMESPACE>— ドットをスラッシュに置換します(例:my.app→my/app)<NAMESPACE-WITH-DOTS>— ドットを含む元の値です
フェーズ 0:検出
0.1 ユニットテストの確認
unitTests.qunit.html、unitTests.qunit.js、allTests.js、AllTests.js、または allTests.qunit.js を含む webapp/test/unit/ を探します。すべてのレガシー集約ファイルを特定します。これは、実際のテストロジック(QUnit.module、QUnit.test、assert.*)を含まず、sap.ui.require/sap.ui.define で他のテストモジュールを読み込むことだけを目的とする JS ファイルです。その内容は unitTests.qunit.js にインライン化し、ファイルは削除します。
0.2 OPA ランチャーと FLP サンドボックスの有無を分類
フェーズ 5b は、needsIframeMigration = (launcher === "in-window" AND flpSandbox === true) の場合に実行します。
node <skill-dir>/scripts/parse-testsuite.js --detect-launcher webapp/test > /tmp/launcher.json
{ "launcher": "iframe"|"in-window"|"mixed"|"none", "flpSandbox": true|false, "needsIframeMigration": true|false } を返します。
launcher | flpSandbox | 対応 |
|---|---|---|
iframe | 任意 | パターン I — フェーズ 5 のみ。5b はスキップ |
in-window | true | パターン U — フェーズ 5 とフェーズ 5b |
in-window | false | 通常のインウィンドウ — 5b はスキップし、iStartMyUIComponent はそのままにする |
mixed | 任意 | 停止してください。 MODERNIZATION_ISSUES.md に記録し、開発者に整合を依頼 |
none | 任意 | OPA テストなし — OPA フェーズをスキップ |
パターン U とパターン B の組み合わせはサポートされていません。 needsIframeMigration === true で、フェーズ 0.3 がパターン B を報告した場合は停止し、開発者に伝えてください。
0.3 OPA テストを確認 — パターンを特定
パターン A —「単一 HTML + AllJourneys」:opaTests.qunit.html + AllJourneys.js オーケストレーター。
パターン B —「複数の個別 HTML ファイル」:webapp/test/opa/ 内の複数の *.qunit.html。
検出:
find webapp/test -name "AllJourneys.js" -o -name "AllJourneys.json"
find webapp/test/opa -name "*.qunit.html" -type f 2>/dev/null | wc -l
0.4 解析スクリプトを実行
node <skill-dir>/scripts/parse-testsuite.js <testsuite.qunit.html> <test-base-dir> <namespace>
出力:pattern(A/B)、summary(件数)、entries(モジュールから設定へのマッピング)、opaConfig(パターン A 用)。これを保存してください。以降の作業はこの内容に基づいて進めます。
0.5 ブートストラップのオーバーライドを報告
node <skill-dir>/scripts/parse-testsuite.js --scan-bootstrap-overrides webapp/test > /tmp/bootstrap-overrides.json
検出対象:sap.ui.define = ...、sap.ui.require = ...、defineModuleSync(...)。検出した各項目を MODERNIZATION_ISSUES.md に追加します。オーバーライドは書き換えないでください。報告することが成果物です。
フェーズ 1:testsuite.qunit.js を作成する(メイン)
すべてのテストを列挙します。ユニットテストは "unit/unitTests" に委譲し、OPA ジャーニーは個別に列挙します。パーススクリプトの出力から OPA エントリーを作成します。キーには、webapp/test/ からの相対パスから .qunit.js を除いたものを使用します。
sap.ui.define(function() {
"use strict";
return {
name: "QUnit test suite for <NAMESPACE-WITH-DOTS>",
defaults: {
page: "ui5://test-resources/<NAMESPACE>/Test.qunit.html?testsuite={suite}&test={name}",
qunit: { version: 2 },
sinon: { version: 4 },
ui5: { theme: "sap_horizon" },
loader: {
map: { "*": {
"sap/ui/thirdparty/sinon": "sap/ui/thirdparty/sinon-4",
"sap/ui/thirdparty/sinon-qunit": "sap/ui/qunit/sinon-qunit-bridge"
}},
paths: { "<NAMESPACE>": "../" }
},
coverage: { only: ["<NAMESPACE>"], never: ["<NAMESPACE>/test"] }
},
tests: {
// ----- Unit Tests -----
"unit/unitTests": { title: "Unit Tests" },
// ----- OPA Integration Tests -----
"integration/NavigationJourney": { title: "Navigation Journey" }
}
};
});
オプションの詳細な説明については、references/testsuite-configuration.md を参照してください。
重要な点:
pageには必ずui5://プロトコルのプレフィックスを使用してください。moduleプロパティは不要です。Test Starter がエントリーキーに.qunitを追加します。{suite}と{name}のプレースホルダーは必須です。
追加のローダーパス
必須です。 すべてのテスト HTML ファイルから、リソースルートのマッピングをすべて抽出します。
grep -rh "data-sap-ui-resourceroots" webapp/test/ --include="*.html"
ドット区切りのキーをスラッシュ区切りに変換し、loader.paths に追加します。値はすべて Test.qunit.html(webapp/test/ 内)を基準として解決されるため、サブディレクトリ内の HTML ファイルについてはパスを計算し直してください。
2 つの HTML ファイルで同じキーに異なる値が定義されている場合は、メインの testsuite.qunit.html/opaTests.qunit.html の値を優先してください。少数派のテストには、テストごとの loader.paths オーバーライドを設定します。
フェーズ 2:Test.qunit.html を作成する
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="../resources/sap/ui/test/starter/runTest.js"
data-sap-ui-resource-roots='{ "test-resources.<NAMESPACE-WITH-DOTS>": "./" }'
></script>
</head>
<body class="sapUiBody">
<div id="qunit"></div>
<div id="qunit-fixture"></div>
</body>
</html>
フェーズ 3:testsuite.qunit.html を更新する
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>QUnit test suite for <NAMESPACE-WITH-DOTS></title>
<script src="../resources/sap/ui/test/starter/createSuite.js"
data-sap-ui-testsuite="test-resources/<NAMESPACE>/testsuite.qunit"
data-sap-ui-resource-roots='{ "test-resources.<NAMESPACE-WITH-DOTS>": "./" }'
></script>
</head>
<body></body>
</html>
フェーズ 4:ユニットテストの JS ファイルを最新化する
4.0 最初に — 冗長なアグリゲーターを削除する
冗長なアグリゲーターとは、実際のテストロジックがなく、sap.ui.require([deps], function() { QUnit.start(); }) または読み込みのみの sap.ui.define だけを含むものです。QUnit.config.autostart = false と QUnit.start() は起動用のコードであり、テストロジックではありません。
対応: 読み込むテストモジュールを記録してから(unitTests.qunit.js 用)、.js ファイルと対応する .html ファイルの両方を直ちに削除します。
4.1 実際のテストファイルを変換して名前を変更する
実際のテストロジック(QUnit.module、QUnit.test、assert.*)を含むファイルでは、次の作業が必要です。
.qunit.jsのサフィックスに名前を変更します(例:App.controller.js→App.controller.qunit.js)。sap.ui.defineに変換します(QUnit.config.autostartとCore.readyのラッパーを削除します)。
// Before (App.controller.js)
QUnit.config.autostart = false;
sap.ui.getCore().attachInit(function() {
sap.ui.require(["my/app/model/formatter"], function(formatter) {
QUnit.module("formatter");
QUnit.test("formatValue", function(assert) { ... });
});
});
// After (App.controller.qunit.js)
sap.ui.define(["my/app/model/formatter"], function(formatter) {
"use strict";
QUnit.module("formatter");
QUnit.test("formatValue", function(assert) { ... });
});
4.2 unitTests.qunit.js アグリゲーターを作成する
削除したアグリゲーターに含まれていたものと、追加の .qunit.js ファイルを合わせて、実際のユニットテストモジュールをすべて直接列挙します。
sap.ui.define([
"./controller/Main.qunit",
"./model/formatter.qunit"
]);
ルール:相対パスには ./ を使用し、.js を付けずに .qunit サフィックスを含めます。
jsUnitTestSuite の変換
以前の testsuite.qunit.js が jsUnitTestSuite を使用していた場合は、フェーズ 1 ですでに置き換えられています。古い内容を削除してください。
フェーズ 5:OPA テストを最新化する
パターン A — 単一の HTML + AllJourneys
詳しい手順については、references/pattern-a-modernization.md を参照してください。
- AllJourneys.js から OpaSetup.js を作成します。
Opa5.extendConfigとページオブジェクトのインポートを抽出します。OpaSetup ではsap/ui/test/opaQunitをインポートしないでください。 - ジャーニーファイルの名前を
.qunit.jsに変更します。 - ジャーニーを更新し、副作用依存関係として
"./OpaSetup"を追加します(相対パス)。 autoWaitのオーバーライドを処理します。ジャーニーごとにOpa5.extendConfigを設定してください。- OpaSetup.js の
testLibs設定を維持します。
// OpaSetup.js (no opaQunit!)
sap.ui.define(["sap/ui/test/Opa5", "test-resources/<NAMESPACE>/integration/pages/App"
], function(Opa5) {
"use strict";
Opa5.extendConfig({ viewNamespace: "<APP-NAMESPACE>.view.", autoWait: true });
});
// Journey file (opaQunit here, OpaSetup relative)
sap.ui.define(["sap/ui/test/opaQunit", "sap/ui/test/Opa5", "./OpaSetup"
], function(opaTest, Opa5) {
"use strict";
// opaTest(...) calls
});
パターン B — 個別の HTML ファイルが複数ある場合
詳しい手順については、references/pattern-b-modernization.md を参照してください。
Opa5.createPageObjectsを呼び出すユーティリティモジュールを一覧にします。- OpaSetup.js を作成し、ユーティリティのインポートと
Opa5.extendConfigをまとめます。 - ジャーニーの名前を
.qunit.jsに変更し、OpaSetup の依存関係を追加します。 - パーススクリプトの
autoWaitFalseFilesに基づいてautoWaitのオーバーライドを処理します。 - 複数モジュールを含む HTML ファイルでは、モジュールごとに testsuite エントリーを 1 つ出力します(
*Combinedという名前を決して作らないでください)。references/pattern-b-modernization.mdのステップ 6 を参照してください。
フェーズ 5b:ウィンドウ内の OPA を bare-Component iframe に移行する
フェーズ 0.2 で needsIframeMigration: true と報告された場合のみ実行します。
手順の詳細は references/pattern-u-iframe-migration.md を参照してください。概要:
opaIframe.qunit.htmlとopaIframeBoot.jsを作成する — bare-Component iframe です。HTML ではスタブ用にsap/ushellの sandbox.js を読み込み、sap-ushell-configは使用しません。Boot モジュールではmockserver.init()→new ComponentContainer(...).placeAt("content")を実行します。CSP に準拠するため、data-sap-ui-oninit="module:..."を使い、インラインスクリプトは使用しません。arrangements/Common.jsを書き換える —iStartMyAppをiStartMyAppInAFrame(...)に変更します。mockserver のインポートと_clearSharedDataを削除します。- journey を書き換える —
iStartMyUIComponent({...})をiStartMyApp()に変更します(hash/autoWait を引き継ぎます)。 OpaSetup.jsから親フレームの mockserver 初期化を削除する — iframe 内で起動するようになります。- ウィンドウをまたぐコントロールのインスタンス化 —
waitForコールバック内の UI5 コントロールは、Opa5.getWindow().sap.ui.require(...)を使って解決する必要があります。OPA で安全な依存関係は親側に保持します:sap/ui/test/*、sap/ui/core/routing/History。確認コマンド:node <skill-dir>/scripts/detect-cross-window-imports.js <project-root>。 - ウィンドウをまたぐ jQuery/DOM — 裸の
$(...)、document.*、window.*をOpa5.getJQuery()(...)、Opa5.getWindow().*に置き換えます。 - ルーティングヘルパー — プレーンな Component ルーターの hash を使用し、
#app-tile&/のプレフィックスは付けません。 - mockserver の
sap-messageエンベロープ — メッセージコレクターには POST ハンドラーのsap-messageヘッダーが必要です。 - ErrorHandler の null ガード —
getElementsByTagName("message")[0].firstChild.dataが null にならないようガードします。 <NAMESPACE>/test/integration/opaIframeをloader.pathsに登録しないでください。
項目 5~9 はプロジェクト固有です。参照ファイルに記載された検出用 grep に従って機械的に適用してください。
フェーズ 6:古いファイルを削除する
ユニットテストファイル
unitTests.qunit.html、legacyTests.qunit.htmlを削除します(古いブートストラップ)。- フェーズ 4.0 で特定された重複するアグリゲーターがすべて削除されていることを確認します。
OPA ファイル — パターン A
opaTests.qunit.html、AllJourneys.js、AllJourneys.jsonを削除します。
OPA ファイル — パターン U(needsIframeMigration の場合のみ)
- 以前の試行で作成されている場合は
flpSandbox.qunit.htmlを削除します。 iStartMyUIComponentを呼び出す journey が残っていないことを確認します。
OPA ファイル — パターン B
webapp/test/opa/以下の*.qunit.htmlをすべて削除します(件数はsummary.totalActiveと一致する必要があります)。
削除しないファイル
testsuite.qunit.html(更新済み)、Test.qunit.html(作成済み)
フェーズ 7:検証する
- 件数チェック:
testsuite.qunit.jsの OPA エントリー数が、解析スクリプトの OPA 合計数と一致すること。 - 参照切れエントリーチェック:
node <skill-dir>/scripts/check-dangling-entries.js webapp/test— 終了コード 0 = OK。 - リンター:
npx @ui5/linter—prefer-test-starterの警告が残っていないこと。 - 構造レビュー:
Test.qunit.html→runTest.js、testsuite.qunit.html→createSuite.js、testsuite.qunit.js→ unit delegate + OPA journey であること。すべてのユニットファイルでsap.ui.defineを使用していること。OpaSetup.jsが存在し、すべての journey がこれをインポートしていること。古い HTML が残っていないこと。 - パターン U の検証(
needsIframeMigrationの場合):opaIframe.qunit.htmlとopaIframeBoot.jsが存在し、インラインの<script>本文もwindow["sap-ushell-config"]もないことgrep -rn "iStartMyUIComponent\b" webapp/test→ ヒット数が 0grep -rn "#app-tile&/" webapp/test→ ヒット数が 0mockserver.init()がopaIframeBoot.js内だけにあること- opaIframe/flpSandbox 用の
loader.pathsエイリアスがないこと detect-cross-window-imports.jsの終了コードが 0- ErrorHandler の XML 解析がガードされているか、問題として報告されていること
実例
例 A — パターン A(単一 HTML + AllJourneys)
名前空間:com.mycompany.myapp、OPA journey 4 件 + ユニットテスト 2 件。
変更後:testsuite.qunit.js → 5 エントリー(unit 1 件 + OPA 4 件)。AllJourneys.js → OpaSetup.js + 個別エントリー。削除対象:AllJourneys.json、opaTests.qunit.html、unitTests.qunit.html。
例 B — パターン B(個別の HTML ファイルが多数ある場合)
名前空間:com.mycompany.myapp、OPA ジャーニー 45 件+ユニットテスト 3 件。
変換後:testsuite.qunit.js → 46 エントリー(ユニットテスト 1 件+OPA 45 件)。OpaSetup.js = ユーティリティのインポートを統合したもの。3 件のジャーニーで autoWait: false を上書き設定。削除対象:HTML ファイル 46 件+unitTests.qunit.html。
重要な注意事項
runTest.jsとcreateSuite.js:createSuite.js= テストスイートの概要。runTest.js= 個別のテスト用のTest.qunit.html。.qunit.jsのサフィックス規則:.qunit.jsが必要なのはテストスイートのエントリーキーのみです。sap.ui.defineで読み込む依存関係(ページオブジェクト、OpaSetup.js)には通常の.jsを使用します。- 依存関係パス内の
.qunit:"./FilterBar.qunit"はFilterBar.qunit.jsに解決されます。サフィックスがない場合は、ファイルが見つかりません。 test-resources/プレフィックス:webapp/test/以下の依存関係には、<NAMESPACE>/test/...ではなくtest-resources/<NAMESPACE>/...を使用します。- 既存の
<NAMESPACE>/test/依存関係を変換する:フェーズ 5 の後、すべてのテスト.jsファイルをスキャンし、test-resources/<NAMESPACE>/に変換します。 - 相対パスと
test-resources/:同じディレクトリー内の場合は./を使用します。別のディレクトリーの場合はtest-resources/<NAMESPACE>/...を使用します。 - OpaSetup.js では
sap/ui/test/opaQunitをインポートしないでください — 各ジャーニーファイルに記述します。 - 副作用のあるインポートは依存関係配列の末尾に置きます — 先頭に追加すると、パラメーターの位置がずれます。
完了チェックリスト
| # | 確認項目 |
|---|---|
| 1 | runTest.js を含む Test.qunit.html が存在する |
| 2 | testsuite.qunit.html が createSuite.js を使用している |
| 3 | testsuite.qunit.js に "unit/unitTests" とすべての OPA ジャーニーが含まれている |
| 4 | 不要な集約ファイルが残っていない |
| 5 | 古いテスト用 HTML ブートストラップが残っていない |
| 6 | unitTests.qunit.js が実在するテストファイルのみを参照している |
| 7 | OPA エントリー数が解析スクリプトの合計と一致している |
| 8 | 孤立エントリーのチェックに合格している(終了コード 0) |
| 9 | ブートストラップの上書き設定が MODERNIZATION_ISSUES.md に報告されている(該当する場合) |
| 10 | ランチャーと FLP が分類されている。mixed の場合は停止している |
| 11 | needsIframeMigration の場合:iframe ファイルが存在し、インラインスクリプトと iStartMyUIComponent がない |
| 12 | needsIframeMigration の場合:FLP ハッシュプレフィックスとローダーエイリアスがない |
| 13 | needsIframeMigration の場合:ErrorHandler が保護されているか、問題として報告されている |
| 14 | needsIframeMigration の場合:detect-cross-window-imports.js の終了コードが 0 |
関連スキル
- fix-csp-compliance — 古い HTML のインラインスクリプトは CSP に違反します。Test Starter がこれらを削除します。
- fix-linter-blind-spots — リンターが検出できない、実行時に問題を起こすパターンを検出します。
- fix-js-globals (cases 1b/1c) — リンターが報告する
sap.*グローバルを処理します。