一键导入
software-testing
Use when adding or revising automated tests in this project, especially when choosing test scope, fixtures, or application startup boundaries.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Use when adding or revising automated tests in this project, especially when choosing test scope, fixtures, or application startup boundaries.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Use when working with repository workstreams stored under `.workstreams/` and you need to understand their structure, files, lifecycle, and execution loop. Trigger phrases: "workstream about".
Use when defining or refining a repository workstream in `.workstreams/<name>/design.md` before planning ordered execution waves. Trigger phrases: "ws design", "ws-design", "workstream design".
Use when executing a workstream from `.workstreams/<name>/tasks.json`, completing all waves serially and tasks within a wave in parallel. Trigger phrases: "ws execute", "workstream execute".
Use when creating or updating a workstream plan in `.workstreams/<name>/plan.md` that must break the workstream into ordered execution waves, parallel tasks within a wave, per-task behavioral specs, and meaningful review gates between waves. Trigger phrases: "ws plan", "workstream plan".
Use when reviewing executed work for a repository workstream against `design.md`, `plan.md`, `tasks.json`, and recent execution history after `workstream-execute` completes. Trigger phrases: "ws review", "workstream review".
Use when building or refreshing `.workstreams/<name>/tasks.json` from `plan.md` and optional `review.md` while preserving execution state across the review loop. Trigger phrases: "ws tasks", "workstream tasks".
| name | software-testing |
| description | Use when adding or revising automated tests in this project, especially when choosing test scope, fixtures, or application startup boundaries. |
Prefer integration tests over unit tests. The default test should exercise the application the way a real user or client would: through its public interface, with the real configuration and dependencies wired together.
Unit tests are acceptable for pure logic such as parsers, transformers, and algorithms. If a behavior depends on routing, middleware, serialization, startup wiring, filesystem layout, or database integration, it should usually be an integration test.
For languages that require a main function, keep main minimal. Move startup and configuration into an importable entrypoint so tests can boot the real application without duplicating startup logic.
The most valuable tests avoid reaching into application internals. Instead, choose the external tool that exercises the application the way a real user would:
The pattern is always the same: start the real application through its real startup path, interact with it through the same interface a user would, and assert on observable behavior. The specific tool changes by platform; the principle does not.
Integration testing becomes much easier when the application is configurable at startup: ports, database URLs, storage paths, feature flags. If the app can be pointed at a test database, a temp directory, or a random port via arguments or a config file, each test can spin up an isolated instance without conflicts.
Avoid hardcoding infrastructure details. An application that accepts --port 0 or --db-url sqlite::memory: is trivially testable. One that assumes port 8080 and a production database is not.
Tests should execute the real startup path, not a mock, fake service layer, or hand-rolled harness. Depending on the platform, that may mean spawning the compiled binary or calling a shared entrypoint function from the test harness. What matters is that the test exercises the same configuration, middleware, and integrations that production uses.
This catches an entire class of bugs that unit tests miss: configuration wiring, middleware ordering, serialization mismatches, startup failures, and dependency injection mistakes.
test runner ──HTTP/browser/emulator──▶ application entrypoint
├── real database
├── real middleware
└── real configuration
A fixture encapsulates setup and teardown for a test. It prepares the test environment and exposes a clean interface for the test to use. Starting the application is a separate step.
pub struct AppFixture {
port: u16,
db_url: String,
temp_dir: TempDir,
database: DatabaseHandle,
}
impl AppFixture {
pub async fn new(port: u16) -> Result<Self, String> {
// 1. Start dependencies
// 2. Create isolated filesystem state
// 3. Collect addresses and config the app will need
Ok(Self {
port,
db_url,
temp_dir,
database,
})
}
pub async fn run(&self) -> Result<AppHandle, String> {
// 1. Start the application with this fixture's config
// 2. Wait for readiness
start_app(self.port, &self.db_url, self.temp_dir.path()).await
}
pub fn base_url(&self) -> String {
format!("http://localhost:{port}", port = self.port)
}
}
AppFixture::new() to set it up, then run() to start the app.A well-structured fixture does four things:
new(), then start the application explicitly with run().base_url(), db_connection(), logs()).Drop or equivalent), not manual.Tests often need preconditions: a database with seed data, files on disk, a git repository, or a running dependency. Prepare this state inside the test environment, whether that environment is the host or a container.
Prefer the host when you can isolate state with temp directories, ephemeral ports, and fake dependencies in a parallel-safe way. Use containers when you need stronger environmental fidelity or process isolation.
For reusable setup, extract helper functions:
async fn seed_database(fixture: &AppFixture) {
fixture.exec(&["psql", "-c", "INSERT INTO users (name) VALUES ('alice')"]).await;
}
These helpers live in the test module, not in production code. They are specific to testing and shouldn't leak into the main codebase.
Each test follows the same pattern:
#[tokio::test]
async fn test_feature_x() {
// 1. Arrange — create fixture, prepare state
let port = pick_unused_port();
let fixture = AppFixture::new(port).await.expect("setup failed");
seed_database(&fixture).await;
let _app = fixture.run().await.expect("app start failed");
// 2. Act — interact with the application through its public interface
let client = reqwest::Client::new();
let response = client
.post(format!("{}/api/resource", fixture.base_url()))
.json(&json!({ "name": "test" }))
.send()
.await
.expect("request failed");
// 3. Assert — verify the response
assert_eq!(response.status().as_u16(), 201);
let body: serde_json::Value = response.json().await.expect("parse failed");
assert_eq!(body["name"], "test");
}
finally or manual cleanup step.