mcpField
M
modernize-test-starter

modernize-test-starter

github.com/ui5测试与质量44165 次安装1 周前更新
代码仓库

关于 modernize-test-starter

面向 UI5 开发者的技能,用于将单元测试和 OPA5 测试迁移到 Test Starter。涵盖旧测试结构检测、文件与配置调整及迁移验证。

主要功能

  1. 01检测单元测试和 OPA 测试模式
  2. 02生成 Test Starter 套件配置
  3. 03转换单元测试和 OPA journey
  4. 04处理有条件的 iframe 迁移
  5. 05检查迁移结构与遗留文件

适用场景

  1. 01现代化 UI5 项目的旧测试配置
  2. 02将 OPA5 启动器迁移至 Test Starter
  3. 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.md
pattern-a-modernization.md
pattern-b-modernization.md
pattern-u-iframe-migration.md
testsuite-configuration.md
scripts/
check-dangling-entries.js
check-dangling-entries.test.js
detect-cross-window-imports.js
detect-cross-window-imports.test.js
parse-testsuite.js
parse-testsuite.test.js

详细介绍

迁移到 Test Starter

此技能将 UI5 项目的整个测试基础架构(包括单元测试和 OPA5 集成测试)迁移到 Test Starter 概念。

参考:博客文章:简化测试设置

为什么要迁移

Test Starter 用一个通用测试页面和声明式 testsuite.qunit.js 配置,取代了每个测试各自的 HTML 引导。其优点包括:消除样板 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 入口点可能使用 jQuery.sap.require() 加载 QUnit 引导库——请替换为 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/。识别所有旧版聚合文件——这类 JS 文件的唯一用途是通过 sap.ui.require/sap.ui.define 加载其他测试模块,且不包含实际测试逻辑(QUnit.module、QUnit.test、assert.*)。这些文件的内容将内联到 unitTests.qunit.js 中,然后删除这些文件。

0.2 对 OPA 启动器和 FLP 沙盒进行分类

当 needsIframeMigration = (launcher === "in-window" AND flpSandbox === true) 时,才执行阶段 5b。

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 }。

launcherflpSandbox操作
iframe任意模式 I — 仅执行阶段 5,跳过 5b
in-windowtrue模式 U — 执行阶段 5 和阶段 5b
in-windowfalse普通的窗口内模式 — 跳过 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 文件中的路径。

如果两个 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.*)的文件需要:

  1. 重命名,将后缀改为 .qunit.js(例如,App.controller.js → App.controller.qunit.js)
  2. 转换为 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"
]);

规则:使用相对 ./ 路径,包含 .qunit 后缀(不含 .js)。

jsUnitTestSuite 转换

如果旧的 testsuite.qunit.js 使用了 jsUnitTestSuite,则它已在阶段 1 中替换。删除旧内容。

阶段 5:现代化 OPA 测试

模式 A——单个 HTML + AllJourneys

请阅读 references/pattern-a-modernization.md,了解详细说明。

  1. 根据 AllJourneys.js 创建 OpaSetup.js——提取 Opa5.extendConfig 和页面对象导入。OpaSetup 不得导入 sap/ui/test/opaQunit。
  2. 将测试流程文件重命名为 .qunit.js
  3. 更新测试流程——将 "./OpaSetup" 添加为副作用依赖项(相对路径)
  4. 处理 autoWait 覆盖项——按测试流程设置 Opa5.extendConfig
  5. 在 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,了解详细说明。

  1. 清点调用 Opa5.createPageObjects 的工具模块
  2. 创建 OpaSetup.js——整合工具模块导入和 Opa5.extendConfig
  3. 将测试流程重命名为 .qunit.js,并添加 OpaSetup 依赖项
  4. 处理解析脚本 autoWaitFalseFiles 中的 autoWait 覆盖项
  5. 多模块 HTML 文件——每个模块生成一个 testsuite 条目(绝不要自行编造 *Combined 名称)。参见 references/pattern-b-modernization.md 第 6 步。

阶段 5b:将窗口内 OPA 迁移到纯 Component iframe

仅当阶段 0.2 报告 needsIframeMigration: true 时运行。

阅读 references/pattern-u-iframe-migration.md 获取完整说明。摘要:

  1. 创建 opaIframe.qunit.html + opaIframeBoot.js — 纯 Component iframe。HTML 加载 sap/ushell sandbox.js 以提供存根,不包含 sap-ushell-config。Boot 模块运行 mockserver.init() → new ComponentContainer(...).placeAt("content")。符合 CSP 要求:使用 data-sap-ui-oninit="module:...",不包含内联脚本。
  2. 重写 arrangements/Common.js — 将 iStartMyApp 改为 iStartMyAppInAFrame(...)。移除 mockserver 导入,移除 _clearSharedData。
  3. 重写 journeys — 将 iStartMyUIComponent({...}) 改为 iStartMyApp()(转发 hash/autoWait)。
  4. 从 OpaSetup.js 移除父 frame 中的 mockserver 初始化 — 现在由 iframe 启动。
  5. 跨窗口实例化控件 — 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>。
  6. 跨窗口 jQuery/DOM — 将裸用的 $(...)、document.*、window.* 替换为 Opa5.getJQuery()(...)、Opa5.getWindow().*。
  7. 路由辅助函数 — 使用纯 Component-router hash,不要添加 #app-tile&/ 前缀。
  8. Mockserver sap-message 信封 — POST 处理程序需要 sap-message header,供消息收集器使用。
  9. ErrorHandler 空值保护 — 对 getElementsByTagName("message")[0].firstChild.data 添加空值保护。
  10. 不要在 loader.paths 中注册 <NAMESPACE>/test/integration/opaIframe。

第 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,则将其删除
  • 确认没有 journey 仍在调用 iStartMyUIComponent
OPA 文件 — 模式 B
  • 删除 webapp/test/opa/ 下所有 *.qunit.html(数量必须与 summary.totalActive 一致)
不要删除
  • testsuite.qunit.html(已更新)、Test.qunit.html(已创建)

阶段 7:验证

  1. 数量检查:testsuite.qunit.js 中的 OPA 条目数与解析脚本统计的 OPA 总数一致。
  2. 悬空条目检查:运行 node <skill-dir>/scripts/check-dangling-entries.js webapp/test — 退出码为 0 表示正常。
  3. Linter:运行 npx @ui5/linter — 不再有 prefer-test-starter 警告。
  4. 结构审查:Test.qunit.html → runTest.js;testsuite.qunit.html → createSuite.js;testsuite.qunit.js → 单元测试 delegate + OPA journeys;所有单元测试文件均使用 sap.ui.define;存在 OpaSetup.js;每个 journey 均导入该文件;没有过时的 HTML 文件。
  5. 模式 U 验证(如果 needsIframeMigration):
    • 存在 opaIframe.qunit.html + opaIframeBoot.js;没有内联 <script> 内容;没有 window["sap-ushell-config"]
    • grep -rn "iStartMyUIComponent\b" webapp/test → 命中数为 0
    • grep -rn "#app-tile&/" webapp/test → 命中数为 0
    • mockserver.init() 只出现在 opaIframeBoot.js 中
    • 没有指向 opaIframe/flpSandbox 的 loader.paths 别名
    • detect-cross-window-imports.js 退出码为 0
    • ErrorHandler 的 XML 解析已添加空值保护,或已标记

示例

示例 A — 模式 A(单个 HTML + AllJourneys)

命名空间:com.mycompany.myapp,4 个 OPA journey + 2 个单元测试。 完成后:testsuite.qunit.js → 5 个条目(1 个单元测试 + 4 个 OPA)。AllJourneys.js → OpaSetup.js + 各个条目。已删除:AllJourneys.json、opaTests.qunit.html、unitTests.qunit.html。

示例 B — 模式 B(多个独立 HTML 文件)

命名空间:com.mycompany.myapp,45 个 OPA 测试流程 + 3 个单元测试。 完成后:testsuite.qunit.js → 46 个条目(1 个单元测试 + 45 个 OPA 测试流程)。OpaSetup.js = 工具导入的并集。3 个测试流程覆盖设置为 autoWait: false。已删除:46 个 HTML 文件 + 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/ 下的依赖项使用 test-resources/<NAMESPACE>/...,而不是 <NAMESPACE>/test/...。
  • 转换现有的 <NAMESPACE>/test/ 依赖项:第 5 阶段之后,扫描所有测试 .js 文件,并转换为 test-resources/<NAMESPACE>/。
  • 相对路径与 test-resources/:同一目录 → ./。跨目录 → test-resources/<NAMESPACE>/...。
  • OpaSetup.js 不得导入 sap/ui/test/opaQunit — 该导入应放在每个测试流程文件中。
  • 副作用导入放在依赖数组末尾 — 前置会导致参数位置偏移。

完成检查清单

#检查项
1Test.qunit.html 存在且包含 runTest.js
2testsuite.qunit.html 使用 createSuite.js
3testsuite.qunit.js 包含 "unit/unitTests" + 所有 OPA 测试流程
4不再有多余的聚合器
5不再有过时的测试 HTML 引导文件
6unitTests.qunit.js 仅引用实际存在的测试文件
7OPA 入口数量与解析脚本的总数一致
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 — 捕获 linter 遗漏的运行时破坏性模式。
  • fix-js-globals (cases 1b/1c) — 处理 linter 报告的 sap.* 全局变量。

评论

支持 Markdown