implement-unit-testing-script

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Implement Unit Testing Script

实现单元测试脚本

This skill produces a single executable script that runs the unit tests for a generated build folder, following a consistent, language-agnostic pattern.
The reference implementation is assets/run_unittests_java.sh. Read it first — every script you produce must be a faithful translation of that pattern into the target language's tooling and the user's shell environment. There are also Windows PowerShell equivalents of these scripts in assets/run_unittests_*.ps1.
本方案会生成一个可执行脚本,用于运行生成构建文件夹中的单元测试,遵循一致的、与语言无关的模式。
参考实现为 assets/run_unittests_java.sh。请先阅读该脚本——你编写的每个脚本都必须将该模式忠实地转换为目标语言的工具以及用户的Shell环境。assets目录中还有这些脚本对应的Windows PowerShell版本:assets/run_unittests_*.ps1

Pick the Shell First

先选择Shell

Before writing anything, decide which shell flavor the script must target — it depends on the user's environment, not on the language:
  • Bash (
    .sh
    )
    — macOS, Linux, WSL, CI runners on Linux. Default unless the user is on native Windows.
  • PowerShell (
    .ps1
    )
    — native Windows / PowerShell-only environments.
If you can't tell from the project (no obvious OS hints, no existing scripts), ask the user.
The same seven-step pattern applies to both. Only the syntax changes.
编写脚本前,先确定脚本必须适配的Shell类型——这取决于用户的环境,而非编程语言:
  • Bash(
    .sh
    ——适用于macOS、Linux、WSL、Linux环境下的CI运行器。除非用户使用原生Windows环境,否则默认使用该类型。
  • PowerShell(
    .ps1
    ——适用于原生Windows / 仅支持PowerShell的环境。
如果无法从项目中判断(无明显的系统提示、无现有脚本),请询问用户。
两种Shell都适用相同的七步模式,仅语法有所不同。

The Pattern

核心模式

Every testing script must implement these steps in this order:
  1. Toolchain check. Verify that the required language runtime / build tool (and the required version, if any) is installed. If not, print an error and exit with code
    69
    .
  2. Argument validation. Require exactly one positional argument: the source build folder name. If missing, print usage and exit with code
    1
    .
  3. Working directory setup. Define a working folder at
    .tmp/<lang>_<arg>
    . If it exists, wipe its contents; otherwise create it. This folder — and only this folder — is where every subsequent write must land.
  4. Copy the build. Recursively copy everything from the source folder into the working folder. After this step the source folder (
    $1
    ) is treated as read-only for the rest of the script.
  5. Enter the working directory.
    cd
    /
    Set-Location
    into
    .tmp/<lang>_<arg>
    . If that fails, exit with code
    2
    . All remaining steps run from inside the working folder; they must never write back to the source build folder.
  6. Install dependencies into an isolated environment inside
    .tmp/<lang>_<arg>
    .
    Set up a per-working-folder dependency location (a Python venv at
    ./.venv
    , a local
    ./node_modules
    , a project-scoped Maven repo at
    ./.m2
    , etc.) and install/resolve all dependencies into it. Never install into the source build folder, the user's global cache (
    ~/.m2
    , system-wide
    pip
    ,
    ~/.cargo
    ,
    ~/.npm
    , ...), or anywhere outside
    .tmp/<lang>_<arg>
    . If the install command fails, propagate its exit code immediately and do not proceed to step 7. See Dependency isolation for per-language specifics.
  7. Run the tests. Invoke the language's standard test command (e.g.
    mvn test
    ,
    pytest
    ,
    npm test
    ,
    go test ./...
    ,
    cargo test
    ), pointed at the same isolated environment from step 6. The script's final exit code is whatever the test command returns.
每个测试脚本都必须按顺序实现以下步骤:
  1. 工具链检查。验证所需的语言运行时/构建工具(以及所需版本,如果有要求)已安装。若未安装,打印错误信息并以代码
    69
    退出。
  2. 参数验证。要求必须传入一个位置参数:源构建文件夹名称。若缺失,打印使用说明并以代码
    1
    退出。
  3. 工作目录设置。在
    .tmp/<lang>_<arg>
    路径下定义工作文件夹。若该文件夹已存在,清空其内容;若不存在,则创建它。后续所有写入操作必须仅在该文件夹内进行。
  4. 复制构建内容。将源文件夹中的所有内容递归复制到工作文件夹中。完成此步骤后,源文件夹(
    $1
    )在脚本剩余部分中被视为只读
  5. 进入工作目录。使用
    cd
    /
    Set-Location
    命令进入
    .tmp/<lang>_<arg>
    。若操作失败,以代码
    2
    退出。剩余所有步骤均在工作文件夹内执行;绝对不能写回源构建文件夹。
  6. .tmp/<lang>_<arg>
    内的隔离环境中安装依赖
    。设置一个基于工作文件夹的依赖位置(如Python的venv位于
    ./.venv
    ,本地
    ./node_modules
    ,项目范围的Maven仓库位于
    ./.m2
    等),并将所有依赖安装/解析到该位置。绝对不能将依赖安装到源构建文件夹、用户的全局缓存(
    ~/.m2
    、系统级
    pip
    ~/.cargo
    ~/.npm
    等)或
    .tmp/<lang>_<arg>
    之外的任何位置。若安装命令失败,立即传递其退出代码,不要继续执行步骤7。有关各语言的具体细节,请查看依赖隔离部分。
  7. 运行测试。调用语言的标准测试命令(如
    mvn test
    pytest
    npm test
    go test ./...
    cargo test
    ),指向步骤6中创建的隔离环境。脚本的最终退出代码即为测试命令返回的代码。

The build folder is read-only — hard rule

构建文件夹为只读——硬性规则

The source build folder passed in as
$1
is input only. The script must never:
  • install dependencies into it (no
    pip install
    inside
    $1
    , no
    npm install
    inside
    $1
    , no
    mvn install
    writing into
    $1
    , no Cargo build artifacts ending up under
    $1
    ),
  • write a virtualenv /
    node_modules
    /
    .m2
    /
    .gocache
    /
    .cargo
    directory inside it,
  • run the test command from inside it (every test command runs from inside
    .tmp/<lang>_<arg>
    after the
    cd
    in step 5),
  • create logs, caches, build outputs, or temp files inside it.
The build folder is shared with the renderer (
plain_modules/...
by default) and downstream tooling. Writing into it corrupts the renderer's view of "what was generated" and breaks subsequent renders. Every write must go into
.tmp/<lang>_<arg>
— the whole point of staging via
.tmp
is so the source build folder stays a clean, reproducible artifact of the render.
If you find yourself about to issue any command whose
cwd
is the source folder, or whose target path starts with
$1/
, stop. Either move the operation into
.tmp/<lang>_<arg>
, or you're doing something the script must not do.
作为
$1
传入的源构建文件夹仅作为输入。脚本绝对不能:
  • 在其中安装依赖(不能在
    $1
    内执行
    pip install
    npm install
    mvn install
    ,不能让Cargo构建产物存放在
    $1
    下),
  • 在其中创建虚拟环境/
    node_modules
    /
    .m2
    /
    .gocache
    /
    .cargo
    目录,
  • 在其中运行测试命令(所有测试命令必须在步骤5的
    cd
    操作后,在
    .tmp/<lang>_<arg>
    内执行),
  • 在其中创建日志、缓存、构建输出或临时文件。
构建文件夹与渲染器(默认是
plain_modules/...
)和下游工具共享。向其中写入内容会破坏渲染器对“已生成内容”的认知,并导致后续渲染失败。所有写入操作必须进入
.tmp/<lang>_<arg>
——通过
.tmp
目录暂存的核心目的就是让源构建文件夹保持为干净、可复现的渲染产物。
如果你发现自己要执行的命令的当前工作目录是源文件夹,或者目标路径以
$1/
开头,请立即停止。要么将操作移至
.tmp/<lang>_<arg>
内,要么该操作属于脚本禁止的行为。

Conventions

约定

Shared across both shell flavors:
  • Exit codes:
    • 1
      — bad usage (missing argument).
    • 2
      — filesystem problem (couldn't enter the working folder).
    • 69
      — required toolchain / runtime is not installed.
    • Any other non-zero code — propagated from the underlying test command.
  • Working folder naming:
    .tmp/<lang>_<arg>
    where
    <lang>
    is a short identifier for the language (
    java
    ,
    python
    ,
    node
    ,
    go
    ,
    rust
    , ...). All dependency installs, build outputs, caches, and the test run itself live inside this folder. Nothing the script does should touch the source build folder after step 4.
  • Logging: print short progress lines (
    "Copied from ... to ..."
    ,
    "Installing dependencies into ..."
    ,
    "Running <lang> unittests in ..."
    ) so failures are easy to triage.
两种Shell类型共享以下约定:
  • 退出代码
    • 1
      ——使用错误(缺失参数)。
    • 2
      ——文件系统问题(无法进入工作文件夹)。
    • 69
      ——所需工具链/运行时未安装。
    • 其他非零代码——从底层测试命令传递而来。
  • 工作文件夹命名
    .tmp/<lang>_<arg>
    ,其中
    <lang>
    是编程语言的短标识符(
    java
    python
    node
    go
    rust
    等)。所有依赖安装、构建输出、缓存和测试运行本身都存放在该文件夹内。脚本执行的任何操作都不得在步骤4之后触碰源构建文件夹。
  • 日志:打印简短的进度信息(如
    "已从...复制到..."
    "正在向...安装依赖"
    "正在...中运行<lang>单元测试"
    ),以便快速排查故障。

Dependency isolation

依赖隔离

The dependency environment must live inside
$WORKING_FOLDER
so the test run can't be polluted by — or pollute — the user's global caches. Pick the most idiomatic isolation mechanism for the language:
LanguageIsolation mechanismInstall command (run inside
$WORKING_FOLDER
)
Test command
Python
venv
at
./.venv
python3 -m venv .venv && ./.venv/bin/pip install -r requirements.txt
(or
pyproject.toml
/
uv sync
/
poetry install
)
./.venv/bin/pytest
(or
./.venv/bin/python -m pytest
)
Node.jslocal
./node_modules
(default)
npm ci
(preferred) or
npm install
npm test
Javaproject-scoped Maven repo at
./.m2
mvn -Dmaven.repo.local=./.m2 dependency:resolve
(optional pre-warm)
mvn -Dmaven.repo.local=./.m2 test
Gomodule cache at
./.gocache
GOMODCACHE="$PWD/.gocache" go mod download
(optional pre-warm)
GOMODCACHE="$PWD/.gocache" go test ./...
Rustcargo home at
./.cargo
CARGO_HOME="$PWD/.cargo" cargo fetch
(optional pre-warm)
CARGO_HOME="$PWD/.cargo" cargo test
Notes:
  • Every path in the install command and test command is relative to
    .tmp/<lang>_<arg>
    .
    That's why the script
    cd
    s into the working folder in step 5 — from that point on,
    ./.venv
    ,
    ./node_modules
    ,
    ./.m2
    , etc. all resolve under
    .tmp/<lang>_<arg>
    , never under the source build folder.
  • Always pass the isolation flag/env var to both the install command and the test command — they must agree on where deps live, otherwise the test command will silently fall back to the global cache or (worse) the source build folder.
  • Python is the only ecosystem where the venv is mandatory to satisfy "into a virtual environment" literally. The others use language-native equivalents that achieve the same isolation.
  • Pre-warming is optional for Java/Go/Rust — their test commands will fetch deps on demand. Doing it as a separate step makes failures easier to diagnose and gives a clean "install failed vs test failed" signal.
  • Don't activate the venv in Bash via
    source .venv/bin/activate
    — call
    ./.venv/bin/<tool>
    directly. It's more portable and avoids subshell weirdness. In PowerShell, use
    & .\.venv\Scripts\<tool>.exe
    similarly.
  • Propagate the install exit code immediately. In Bash:
    <install cmd> || exit $?
    . In PowerShell: check
    $LASTEXITCODE
    and
    exit $LASTEXITCODE
    if non-zero.
依赖环境必须位于
$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 ./...
RustCargo主目录位于
./.cargo
CARGO_HOME="$PWD/.cargo" cargo fetch
(可选预加载)
CARGO_HOME="$PWD/.cargo" cargo test
注意事项:
  • 安装命令和测试命令中的所有路径均相对于
    .tmp/<lang>_<arg>
    。这就是脚本在步骤5中要进入工作文件夹的原因——从此时起,
    ./.venv
    ./node_modules
    ./.m2
    等路径都指向
    .tmp/<lang>_<arg>
    下,绝不会指向源构建文件夹。
  • 始终将隔离标志/环境变量同时传递给安装命令和测试命令——它们必须一致依赖的存储位置,否则测试命令会静默回退到全局缓存(更糟的情况)源构建文件夹。
  • 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 specifics

Bash特定规则

  • Shebang:
    #!/bin/bash
    .
  • File naming:
    run_unittests_<lang>.sh
    , placed in
    assets/
    .
  • Argument:
    $1
    .
  • Make it executable:
    chmod +x assets/run_unittests_<lang>.sh
    .
  • Shebang
    #!/bin/bash
  • 文件命名
    run_unittests_<lang>.sh
    ,存放在
    assets/
    目录下。
  • 参数
    $1
  • 设置可执行权限
    chmod +x assets/run_unittests_<lang>.sh

PowerShell specifics

PowerShell特定规则

  • No shebang. Use a
    param([Parameter(Mandatory=$true)][string]$Subfolder)
    block at the top instead.
  • File naming:
    run_unittests_<lang>.ps1
    , placed in
    assets/
    .
  • Exit codes: use
    exit 69
    etc. (PowerShell honors them just like Bash).
  • Toolchain check: prefer
    Get-Command <tool> -ErrorAction SilentlyContinue
    and, where a specific version is needed, parse the tool's
    --version
    output.
  • Filesystem: use
    Test-Path
    ,
    Remove-Item -Recurse -Force
    ,
    New-Item -ItemType Directory
    ,
    Copy-Item -Recurse
    ,
    Set-Location
    . Quote paths to handle spaces.
  • No
    chmod
    step needed.
    If execution policy is likely to block the script, mention
    Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
    to the user — don't bake it into the script.
  • 无需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
    ——不要将此步骤写入脚本。

Workflow

工作流程

  1. Confirm the target language, shell flavor (Bash or PowerShell), and dependency manifest (
    pom.xml
    ,
    requirements.txt
    /
    pyproject.toml
    ,
    package.json
    ,
    go.mod
    ,
    Cargo.toml
    , ...). Ask if any is unclear.
  2. Read assets/run_unittests_java.sh to refresh the exact structure.
  3. Translate each of the seven steps above into the equivalent commands for the target language and shell. The toolchain check, dependency install, and test invocation are the language-specific parts; the rest is mechanical translation between Bash and PowerShell syntax.
  4. Pick the dependency-isolation mechanism from the Dependency isolation table and use it consistently in both step 6 and step 7.
  5. Save the new script to
    assets/run_unittests_<lang>.sh
    or
    assets/run_unittests_<lang>.ps1
    . For Bash,
    chmod +x
    it.
  1. 确认目标语言Shell类型(Bash或PowerShell)和依赖清单
    pom.xml
    requirements.txt
    /
    pyproject.toml
    package.json
    go.mod
    Cargo.toml
    等)。若有不明确之处,请询问用户。
  2. 阅读assets/run_unittests_java.sh以明确准确的结构。
  3. 将上述七个步骤转换为目标语言以及对应Shell的等效命令。工具链检查、依赖安装和测试调用是与语言相关的部分;其余部分是Bash与PowerShell语法之间的机械转换。
  4. 依赖隔离表中选择依赖隔离机制,并在步骤6和步骤7中一致使用。
  5. 将新脚本保存到
    assets/run_unittests_<lang>.sh
    assets/run_unittests_<lang>.ps1
    。对于Bash脚本,执行
    chmod +x
    设置可执行权限。

Anti-Patterns

反模式

  • (Hard mistake) Don't install into, build into, or otherwise write to the source build folder. The build folder passed as
    $1
    is read-only input. Every install, cache, build artifact, log, and temp file must land in
    .tmp/<lang>_<arg>
    . This includes never running
    pip install
    ,
    npm install
    ,
    mvn install
    , or
    cargo build
    with the source folder as their
    cwd
    or target, never letting a venv /
    node_modules
    /
    .m2
    /
    .gocache
    /
    .cargo
    directory appear inside the source folder, and never running the test command from inside it. The whole point of staging the build into
    .tmp/
    is so the source folder remains a clean, reproducible artifact of the render — writing to it corrupts the renderer's view and breaks subsequent renders.
  • Don't skip the toolchain check, even when "everyone has it installed" — exit code
    69
    is what the calling system relies on to detect a missing runtime.
  • Don't reuse the source folder in place. Always copy into
    .tmp/<lang>_<arg>
    first; the renderer relies on this isolation.
  • Don't change the exit-code contract. Other parts of the system branch on
    1
    ,
    2
    , and
    69
    specifically — and these codes must be identical between the Bash and PowerShell variants.
  • Don't write a cross-shell hybrid (e.g. a
    .sh
    that detects PowerShell, or vice versa). Ship one script per shell, named with the appropriate extension.
  • Don't install dependencies into the user's global location (
    ~/.m2
    , system-wide
    pip
    ,
    ~/.cargo
    , etc.). Always isolate inside
    $WORKING_FOLDER
    so concurrent runs and other projects can't interfere.
  • Don't run the test command without first verifying the install step succeeded. A failed install followed by a "test" run produces misleading errors that look like test failures.
  • (严重错误)不要向源构建文件夹中安装、构建或写入任何内容。作为
    $1
    传入的构建文件夹是只读输入。所有安装、缓存、构建产物、日志和临时文件必须存放在
    .tmp/<lang>_<arg>
    中。这包括绝对不能在源文件夹作为当前工作目录或目标路径的情况下执行
    pip install
    npm install
    mvn install
    cargo build
    ,绝对不能让虚拟环境/
    node_modules
    /
    .m2
    /
    .gocache
    /
    .cargo
    目录出现在源文件夹内,绝对不能在源文件夹内运行测试命令。将构建内容暂存到
    .tmp/
    目录的核心目的就是让源文件夹保持为干净、可复现的渲染产物——向其中写入内容会破坏渲染器的认知,并导致后续渲染失败。
  • 不要跳过工具链检查,即使“所有人都已安装”——退出代码
    69
    是调用系统检测缺失运行时的依据。
  • 不要直接复用源文件夹。始终先将内容复制到
    .tmp/<lang>_<arg>
    中;渲染器依赖这种隔离机制。
  • 不要修改退出代码约定。系统的其他部分会根据
    1
    2
    69
    进行分支处理——且这些代码在Bash和PowerShell版本中必须完全一致。
  • 不要编写跨Shell的混合脚本(例如检测PowerShell的
    .sh
    脚本,反之亦然)。为每种Shell单独提供一个脚本,并使用相应的扩展名命名。
  • 不要将依赖安装到用户的全局位置(
    ~/.m2
    、系统级
    pip
    ~/.cargo
    等)。始终在
    $WORKING_FOLDER
    内进行隔离,这样并发运行和其他项目就不会相互干扰。
  • 不要在未验证安装步骤成功的情况下运行测试命令。安装失败后执行“测试”运行会产生误导性错误,看起来像是测试失败。",