关于 implement-unit-testing-script
用于生成可执行单元测试脚本的 Skill:将生成的构建内容暂存到临时目录,在隔离环境中安装依赖并运行测试。它为 Bash 和 PowerShell 规定了统一流程。
主要功能
- 01将构建内容暂存到临时工作目录
- 02在隔离环境中安装依赖
- 03运行语言对应的单元测试命令
- 04规定日志和退出码约定
- 05支持 Bash 和 PowerShell
适用场景
- 01为项目构建生成单元测试脚本
- 02运行测试且不修改源构建目录
- 03排查测试或依赖安装失败
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ plainlang/ plain-forge --skill implement-unit-testing-script安装后在 Claude Code 中输入/implement-unit-testing-script确认已加载。
详细介绍
实现单元测试脚本
此技能会生成一个可执行脚本,用于运行生成的构建文件夹中的单元测试,并遵循一致、与语言无关的模式。
参考实现是 assets/run_unittests_java.sh。请先阅读它——你编写的每个脚本都必须忠实地将此模式转换为目标语言的工具链,并且适配用户的 Shell 环境。这些脚本也提供对应的 Windows PowerShell 版本,见 assets/run_unittests_*.ps1。
先选择 Shell
编写任何内容之前,先确定脚本要面向哪种 Shell——这取决于用户的环境,而不是语言:
- Bash(
.sh)——macOS、Linux、WSL、Linux 上的 CI 运行器。除非用户使用原生 Windows,否则默认选择此项。 - PowerShell(
.ps1)——原生 Windows/仅支持 PowerShell 的环境。
如果无法从项目中判断(没有明显的操作系统线索,也没有现有脚本),请询问用户。
以下七个步骤适用于这两种 Shell,只有语法不同。
模式
每个测试脚本都必须按以下顺序实现这些步骤:
- 检查工具链。 验证所需的语言运行时/构建工具(以及任何必需的版本)是否已安装。如果没有,打印错误并以代码
69退出。 - 验证参数。 必须且只能有一个位置参数:源构建文件夹。如果缺少参数,打印用法并以代码
1退出。渲染器会将此参数作为绝对路径传入(较旧版本的渲染器会传入相对路径)——脚本必须同时支持两者,因此第 3 步的工作文件夹名称要使用参数的基本名称构造,不能直接使用原始参数。 - 设置工作目录。 在系统临时目录中定义工作文件夹:Bash 中为
/tmp/<lang>_$(basename "$1"),PowerShell 中为Join-Path ([System.IO.Path]::GetTempPath()) "<lang>_$(Split-Path $BuildFolder -Leaf)"(下文写作<temp>/<lang>_<basename>)。如果文件夹已存在,则清空其内容;否则创建该文件夹。注册清理操作,以便脚本退出时删除该文件夹——Bash 中使用trap 'rm -rf "$WORKING_FOLDER"' EXIT,PowerShell 中在finally块中使用Remove-Item -Recurse -Force。此文件夹——并且只有此文件夹——是后续所有写入操作的目标位置。 - 复制构建内容。 将源文件夹中的所有内容递归复制到工作文件夹。此步骤之后,源文件夹(
$1)在脚本余下部分中都视为只读。 - 进入工作目录。 使用
cd/Set-Location进入<temp>/<lang>_<basename>。如果失败,以代码2退出。所有后续步骤都在工作文件夹中执行;绝不能写回源构建文件夹。 - 在
<temp>/<lang>_<basename>内的隔离环境中安装依赖项。 为每个工作文件夹设置专属的依赖项存放位置(例如 Python 虚拟环境./.venv、本地./node_modules、项目专属的 Maven 仓库./.m2等),并在其中安装/解析所有依赖项。绝不能将依赖项安装到源构建文件夹、用户的全局缓存(~/.m2、系统范围的pip、~/.cargo、~/.npm等)或<temp>/<lang>_<basename>以外的任何位置。如果安装命令失败,立即传递其退出代码,不要继续第 7 步。各语言的具体要求见依赖隔离。 - 运行测试。 调用语言的标准测试命令(例如
mvn test、pytest、npm test、go test ./...、cargo test),并指定第 6 步中的同一隔离环境。脚本的最终退出代码应与测试命令返回的退出代码一致。
构建文件夹为只读——硬性规则
作为 $1 传入的源构建文件夹仅供输入。脚本绝不能:
- 在其中安装依赖(不得在
$1内运行pip install,不得在$1内运行npm install,不得运行会向$1写入内容的mvn install,不得让 Cargo 构建产物落在$1下), - 在其中写入 virtualenv /
node_modules/.m2/.gocache/.cargo目录, - 从其中运行测试命令(每条测试命令都必须在第 5 步执行
cd后,从<temp>/<lang>_<basename>内运行), - 在其中创建日志、缓存、构建输出或临时文件。
构建文件夹与渲染器(默认位于 plain_modules/<module>/code)及下游工具共享。向其中写入内容会破坏渲染器对“已生成内容”的视图,并导致后续渲染失败。所有写入都必须进入 <temp>/<lang>_<basename>——将内容暂存到系统临时目录的全部目的,就是让源构建文件夹保持干净,成为渲染结果的可复现产物,并确保用户项目中不遗留任何构建杂项。
如果你发现自己即将发出任何 cwd 为源文件夹,或目标路径以 $1/ 开头的命令,请停下。要么将操作移到 <temp>/<lang>_<basename> 中,要么说明你正在执行脚本绝不能执行的操作。
约定
两种 shell 风格通用:
- 退出码:
1— 用法错误(缺少参数)。2— 文件系统问题(无法进入工作文件夹)。69— 未安装所需的工具链/运行时。- 其他任何非零代码 — 由底层测试命令传递而来。
- 工作文件夹命名:
<temp>/<lang>_<basename>— 系统临时目录(Bash 中为/tmp,PowerShell 中为[System.IO.Path]::GetTempPath()),加上语言的简短标识(java、python、node、go、rust等),以及构建文件夹参数的基名($(basename "$1")/Split-Path $BuildFolder -Leaf)。绝不要将原始参数嵌入路径中——它可能是绝对路径,拼接后会生成错误的重复路径。所有依赖安装、构建输出、缓存和测试运行都必须位于此文件夹内。第 4 步之后,脚本不得再对源构建文件夹进行任何操作。 - **退出时清理:**工作文件夹是临时的——脚本退出时,无论成功还是失败,都要将其删除(Bash 中使用
trap 'rm -rf "$WORKING_FOLDER"' EXIT;PowerShell 中在finally块里的Pop-Location之后使用Remove-Item -Recurse -Force)。临时目录或用户项目中不得留下不断累积的构建文件夹。 - **日志记录——尽可能详细。**在渲染完成到测试结果显示绿色/红色之间,操作人员能看到的只有脚本;发生故障时,stdout/stderr 是所有人唯一的取证依据。应将脚本视为生产运行手册,而不是安静的辅助程序。具体来说:
- 每个步骤都要在执行前宣布,并显示涉及的每个变量的确切值——解析后的源文件夹、工作文件夹、语言、检测到的工具链版本、隔离根目录、依赖清单路径,以及即将运行的测试命令。仅仅写“正在安装依赖”毫无用处;“正在从
./requirements.txt安装依赖到虚拟环境./.venv(Python 3.11.6,位于/usr/local/bin/python3)”才便于排查问题。 - **执行前回显每条重要命令。**在 Bash 中,为脚本主体使用
set -x(或在每次调用前立即执行echo "+ <cmd>");在 PowerShell 中,设置$VerbosePreference = 'Continue',并使用Write-Verbose/Write-Host打印每行命令及其参数。 - **打印清晰的分节标题。**七个步骤都要有类似
===== [3/7] Working directory setup =====的标题,便于浏览较长的日志。 - 打印解析后的绝对路径,而不只是相对路径。使用其他机器查看日志的操作人员需要知道实际生成文件的位置。
- 在第 1 步中打印工具链自身的
--version输出(以及解析出的工具链路径),即使成功也要打印。这是测试在本地通过、却在 CI 中失败时最有用的取证信息。 - 原样显示安装命令的输出——不要将其管道传输到
/dev/null,也不要重定向到工作文件夹中的日志文件。操作人员必须能在脚本自身的输出流中看到依赖解析器的每一行输出。 - 失败时,在退出前打印即将运行的内容——准确的命令、工作目录以及相关环境变量(
PYTHONPATH、NODE_PATH、GOMODCACHE、CARGO_HOME、MAVEN_OPTS、PATH)。 - 打印一行最终摘要,注明测试命令、退出码和工作文件夹,让操作人员在需要时确切知道该手动重新运行什么。 在这里,详细不等于冗余——会在自身输出中记录过程的详尽脚本,可以将故障排查时间从 30 秒缩短到 30 分钟。绝不要为了简洁而牺牲详细程度。
- 每个步骤都要在执行前宣布,并显示涉及的每个变量的确切值——解析后的源文件夹、工作文件夹、语言、检测到的工具链版本、隔离根目录、依赖清单路径,以及即将运行的测试命令。仅仅写“正在安装依赖”毫无用处;“正在从
依赖隔离
依赖环境必须位于 $WORKING_FOLDER 内部,这样测试运行就不会受到用户全局缓存的影响,也不会污染这些缓存。请选择最符合该语言惯例的隔离机制:
| 语言 | 隔离机制 | 安装命令(在 $WORKING_FOLDER 内运行) | 测试命令 |
|---|---|---|---|
| Python | 位于 ./.venv 的 venv | python3 -m venv .venv && ./.venv/bin/pip install -r requirements.txt(或 pyproject.toml / uv sync / poetry install) | ./.venv/bin/pytest(或 ./.venv/bin/python -m pytest) |
| Node.js | 本地 ./node_modules(默认) | npm ci(优先)或 npm install | npm test |
| Java | 项目专属 Maven 仓库,位于 ./.m2 | mvn -Dmaven.repo.local=./.m2 dependency:resolve(可选的预热步骤) | mvn -Dmaven.repo.local=./.m2 test |
| Go | 位于 ./.gocache 的模块缓存 | GOMODCACHE="$PWD/.gocache" go mod download(可选的预热步骤) | GOMODCACHE="$PWD/.gocache" go test ./... |
| Rust | 位于 ./.cargo 的 cargo home | CARGO_HOME="$PWD/.cargo" cargo fetch(可选的预热步骤) | CARGO_HOME="$PWD/.cargo" cargo test |
注意:
- 安装命令和测试命令中的所有路径都相对于
<temp>/<lang>_<basename>。 因此,脚本会在第 5 步切换到工作文件夹——从此以后,./.venv、./node_modules、./.m2等路径都会指向<temp>/<lang>_<basename>内部,而不会指向源代码构建文件夹。 - 安装命令和测试命令都必须传入隔离标志或环境变量——两者必须使用相同的依赖存放位置,否则测试命令会悄悄退回全局缓存,或者(更糟的是)使用源代码构建文件夹。
- 只有 Python 生态系统必须使用 venv,才能从字面上满足“放入虚拟环境”的要求。其他语言使用原生等效机制,实现相同的隔离效果。
- Java/Go/Rust 的预热步骤是可选的——它们的测试命令会按需获取依赖。将其作为单独步骤执行,有助于更轻松地诊断故障,并清楚地区分“安装失败”和“测试失败”。
- 不要在 Bash 中通过
source .venv/bin/activate激活 venv——应直接调用./.venv/bin/<tool>。这种方式可移植性更好,也能避免子 shell 的各种问题。在 PowerShell 中,同样使用& .\.venv\Scripts\<tool>.exe。 - 立即传递安装命令的退出代码。 在 Bash 中:
<install cmd> || exit $?。在 PowerShell 中:检查$LASTEXITCODE,若非零则执行exit $LASTEXITCODE。
Bash 具体要求
- Shebang:
#!/bin/bash。 - 文件命名:
run_unittests_<lang>.sh,放在assets/中。 - 参数:
$1。 - 设置可执行权限:
chmod +x assets/run_unittests_<lang>.sh。
PowerShell 具体要求
- 不要使用 shebang。 应在文件顶部使用
param([Parameter(Mandatory=$true)][string]$Subfolder)块。 - 文件命名:
run_unittests_<lang>.ps1,放在assets/中。 - 退出代码: 使用
exit 69等(PowerShell 和 Bash 一样会遵循这些代码)。 - 工具链检查: 优先使用
Get-Command <tool> -ErrorAction SilentlyContinue;如需特定版本,则解析工具的--version输出。 - 文件系统: 使用
Test-Path、Remove-Item -Recurse -Force、New-Item -ItemType Directory、Copy-Item -Recurse、Set-Location。为处理包含空格的路径,请将其加引号。 - 无需执行
chmod。 如果执行策略可能会阻止脚本运行,请向用户说明Set-ExecutionPolicy -Scope CurrentUser RemoteSigned——不要将其写入脚本。
工作流
- 确认目标语言、Shell 类型(Bash 或 PowerShell)和依赖清单文件(
pom.xml、requirements.txt/pyproject.toml、package.json、go.mod、Cargo.toml等)。如有不明确之处,请先询问。 - 阅读 assets/run_unittests_java.sh,熟悉其确切结构。
- 将上述七个步骤分别转换为目标语言和 Shell 对应的命令。工具链检查、依赖安装和测试调用因语言而异;其余部分是在 Bash 与 PowerShell 语法之间进行机械转换。
- 从 依赖隔离 表格中选择依赖隔离机制,并在第 6 步和第 7 步中保持一致。
- 将新脚本保存为
assets/run_unittests_<lang>.sh或assets/run_unittests_<lang>.ps1。对于 Bash,请运行chmod +x。 - 更新
config.yaml,使其引用新脚本。 添加或更新unit-tests-script:键,并填写新建脚本的路径(例如:unit-tests-script: test_scripts/run_unittests_<lang>.sh)。此项为必需操作——渲染器需要此引用,才能在开发工作流中调用单元测试脚本。 - 将脚本作为关联资源链接到基础
.plain文件中。 脚本在磁盘上创建后,必须使用add-resource技能,在项目的基础.plain模块的 implementation reqs 部分中引用该脚本。完成链接后,重新读取规范,确认链接路径指向刚刚编写的脚本。
反模式
- (严重错误)不要安装到、构建到或以其他方式写入源构建文件夹。 作为
$1传入的构建文件夹是只读输入。所有安装内容、缓存、构建产物、日志和临时文件都必须放在<temp>/<lang>_<basename>中。这包括:绝不能将源文件夹设为pip install、npm install、mvn install或cargo build的cwd或目标目录;绝不能让 venv /node_modules/.m2/.gocache/.cargo目录出现在源文件夹中;也绝不能从源文件夹内部运行测试命令。将构建暂存到系统临时目录的目的,就是让源文件夹保持为渲染结果的干净、可复现产物;写入源文件夹会破坏渲染器的视图,并导致后续渲染失败。 - (严重错误)不要通过拼接原始构建文件夹参数来构建路径。 当前的渲染器传入的
$1是绝对路径。诸如WORKING_FOLDER=.tmp/$1、WORKING_FOLDER=<lang>_$1或"$current_dir/$1"这样的写法会悄悄生成重复路径(/project//abs/path/...)——典型症状是出现ImportError: Start directory is not importable,或“文件夹不存在”错误,其指向的路径包含了两次项目根目录。务必使用$(basename "$1")/Split-Path -Leaf。 - 不要跳过工具链检查,即使“所有人都已安装”也是如此——调用系统依赖退出代码
69来检测缺失的运行时。 - 不要就地复用源文件夹。务必先复制到
<temp>/<lang>_<basename>;渲染器依赖这种隔离。 - 不要遗留工作文件夹。在首次写入前注册清理操作(
trap … EXIT/finally),这样即使提前失败也会将其删除。 - 不要更改退出代码约定。系统的其他部分会专门针对
1、2和69进行分支处理——Bash 和 PowerShell 版本必须使用完全相同的这些代码。 - 不要编写跨 Shell 混合脚本(例如检测 PowerShell 的
.sh,或反过来)。每种 Shell 都要提供一个脚本,并使用相应的扩展名命名。 - 不要将依赖项安装到用户的全局位置(
~/.m2、系统级pip、~/.cargo等)。务必隔离在$WORKING_FOLDER中,以免并发运行和其他项目相互干扰。 - 未先确认安装步骤成功时,不要运行测试命令。安装失败后接着运行“测试”,会产生误导性错误,看起来像是测试失败。
- 不要忘记更新
config.yaml。 创建单元测试脚本后,务必在config.yaml中添加或更新unit-tests-script:键,以引用新脚本。没有此条目,渲染器就不知道从哪里找到单元测试脚本。 - 不要忘记在基础
.plain文件中将脚本链接为资源。 仅在config.yaml中引用的脚本对规范约定不可见——渲染器会将unit-tests-script:视为构建系统指针,而非模型读取的 test-req 约定的一部分。使用add-resource技能,在负责单元测试 test-req 的.plain模块中添加指向脚本的 Markdown 链接(参见工作流步骤 7)。省略链接资源意味着模型作者和评审人员在完全看不到实际运行器的情况下编写和审查测试相关代码,而代码必须满足该运行器的要求。 - 不要编写过于简略的脚本。 步骤无提示、将安装输出重定向到
>/dev/null、缺少--version输出以及没有失败提示横幅,都会让脚本比被测代码更难调试。请严格遵循约定中的 日志记录——尽可能详细 规则:每个步骤都要说明用途,每条命令都要回显,每次失败都要打印即将运行的命令及其运行位置。