[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6CXDB3R546NFVZ874VGJAVN":3,"skill-same:01M6CXDB3R546NFVZ874VGJAVN":116},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":44,"linkOnly":45,"markdownContent":46,"markdownBody":47,"frontMatter":48,"refName":60,"commitSha":61,"skillDirectory":62,"forksCount":63,"repositoryHtmlUrl":64,"repositoryHomepageUrl":65,"ownerHtmlUrl":66,"ownerBlogUrl":67,"ownerDescription":68,"updatedAt":69,"install":70,"resources":83},"01M6CXDB3R546NFVZ874VGJAVN","test-patterns",null,"Write and run tests across languages and frameworks. Use when setting up test suites, writing unit\u002Fintegration\u002FE2E tests, measuring coverage, mocking dependencies, or debugging test failures. Covers Node.js (Jest\u002FVitest), Python (pytest), Go, Rust, and Bash.","albertdobmeyer\u002Fopentrapp","albertdobmeyer","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F116975874?v=4","01M6CXARYAZ30V12S7PSG93R84","Albert Dobmeyer","01M6CXAS2FG5GCXZYN3XBFJQV6",2,"MIT","https:\u002F\u002Fgithub.com\u002Falbertdobmeyer\u002Fopentrapp\u002Fblob\u002Fmain\u002Fworkloads\u002Fskills\u002Fskills\u002Ftest-patterns\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43],"test patterns","test","patterns","python3","across","jest","pytest","up","coverage","write","node","writing","vitest","Bash","win32","run","debugging","e2e tests","rust","go","2026-11-10 10:12:30",false,"---\nname: test-patterns\nversion: 1.0.0\ndescription: Write and run tests across languages and frameworks. Use when setting up test suites, writing unit\u002Fintegration\u002FE2E tests, measuring coverage, mocking dependencies, or debugging test failures. Covers Node.js (Jest\u002FVitest), Python (pytest), Go, Rust, and Bash.\nmetadata: {\"clawdbot\":{\"emoji\":\"?\",\"requires\":{\"anyBins\":[\"node\",\"python3\",\"go\",\"cargo\",\"bash\"]},\"os\":[\"linux\",\"darwin\",\"win32\"]}}\n---\n\n# Test Patterns\n\nWrite, run, and debug tests across languages. Covers unit tests, integration tests, E2E tests, mocking, coverage, and TDD workflows.\n\n## When to Use\n\n- Setting up a test suite for a new project\n- Writing unit tests for functions or modules\n- Writing integration tests for APIs or database interactions\n- Setting up code coverage measurement\n- Mocking external dependencies (APIs, databases, file system)\n- Debugging flaky or failing tests\n- Implementing test-driven development (TDD)\n\n## Node.js (Jest \u002F Vitest)\n\n### Setup\n\n```bash\n# Jest\nnpm install -D jest\n# Add to package.json: \"scripts\": { \"test\": \"jest\" }\n\n# Vitest (faster, ESM-native)\nnpm install -D vitest\n# Add to package.json: \"scripts\": { \"test\": \"vitest\" }\n```\n\n### Unit Tests\n\n```javascript\n\u002F\u002F math.js\nexport function add(a, b) { return a + b; }\nexport function divide(a, b) {\n  if (b === 0) throw new Error('Division by zero');\n  return a \u002F b;\n}\n\n\u002F\u002F math.test.js\nimport { add, divide } from '.\u002Fmath.js';\n\ndescribe('add', () => {\n  test('adds two positive numbers', () => {\n    expect(add(2, 3)).toBe(5);\n  });\n\n  test('handles negative numbers', () => {\n    expect(add(-1, 1)).toBe(0);\n  });\n\n  test('handles zero', () => {\n    expect(add(0, 0)).toBe(0);\n  });\n});\n\ndescribe('divide', () => {\n  test('divides two numbers', () => {\n    expect(divide(10, 2)).toBe(5);\n  });\n\n  test('throws on division by zero', () => {\n    expect(() => divide(10, 0)).toThrow('Division by zero');\n  });\n\n  test('handles floating point', () => {\n    expect(divide(1, 3)).toBeCloseTo(0.333, 3);\n  });\n});\n```\n\n### Async Tests\n\n```javascript\n\u002F\u002F api.test.js\nimport { fetchUser } from '.\u002Fapi.js';\n\ntest('fetches user by id', async () => {\n  const user = await fetchUser('123');\n  expect(user).toHaveProperty('id', '123');\n  expect(user).toHaveProperty('name');\n  expect(user.name).toBeTruthy();\n});\n\ntest('throws on missing user', async () => {\n  await expect(fetchUser('nonexistent')).rejects.toThrow('Not found');\n});\n```\n\n### Mocking\n\n```javascript\n\u002F\u002F Mock a module\njest.mock('.\u002Fdatabase.js');\nimport { getUser } from '.\u002Fdatabase.js';\nimport { processUser } from '.\u002Fservice.js';\n\ntest('processes user from database', async () => {\n  \u002F\u002F Setup mock return value\n  getUser.mockResolvedValue({ id: '1', name: 'Alice', active: true });\n\n  const result = await processUser('1');\n  expect(result.processed).toBe(true);\n  expect(getUser).toHaveBeenCalledWith('1');\n  expect(getUser).toHaveBeenCalledTimes(1);\n});\n\n\u002F\u002F Mock fetch\nglobal.fetch = jest.fn();\n\ntest('calls API with correct params', async () => {\n  fetch.mockResolvedValue({\n    ok: true,\n    json: async () => ({ data: 'test' }),\n  });\n\n  const result = await myApiCall('\u002Fendpoint');\n  expect(fetch).toHaveBeenCalledWith('\u002Fendpoint', expect.objectContaining({\n    method: 'GET',\n  }));\n});\n\n\u002F\u002F Spy on existing method (don't replace, just observe)\nconst consoleSpy = jest.spyOn(console, 'log').mockImplementation();\n\u002F\u002F ... run code ...\nexpect(consoleSpy).toHaveBeenCalledWith('expected message');\nconsoleSpy.mockRestore();\n```\n\n### Coverage\n\n```bash\n# Jest\nnpx jest --coverage\n\n# Vitest\nnpx vitest --coverage\n\n# Check coverage thresholds (jest.config.js)\n# coverageThreshold: { global: { branches: 80, functions: 80, lines: 80, statements: 80 } }\n```\n\n## Python (pytest)\n\n### Setup\n\n```bash\npip install pytest pytest-cov\n```\n\n### Unit Tests\n\n```python\n# calculator.py\ndef add(a, b):\n    return a + b\n\ndef divide(a, b):\n    if b == 0:\n        raise ValueError(\"Division by zero\")\n    return a \u002F b\n\n# test_calculator.py\nimport pytest\nfrom calculator import add, divide\n\ndef test_add():\n    assert add(2, 3) == 5\n\ndef test_add_negative():\n    assert add(-1, 1) == 0\n\ndef test_divide():\n    assert divide(10, 2) == 5.0\n\ndef test_divide_by_zero():\n    with pytest.raises(ValueError, match=\"Division by zero\"):\n        divide(10, 0)\n\ndef test_divide_float():\n    assert divide(1, 3) == pytest.approx(0.333, abs=0.001)\n```\n\n### Parametrized Tests\n\n```python\n@pytest.mark.parametrize(\"a,b,expected\", [\n    (2, 3, 5),\n    (-1, 1, 0),\n    (0, 0, 0),\n    (100, -50, 50),\n])\ndef test_add_cases(a, b, expected):\n    assert add(a, b) == expected\n```\n\n### Fixtures\n\n```python\nimport pytest\nimport json\nimport tempfile\nimport os\n\n@pytest.fixture\ndef sample_users():\n    \"\"\"Provide test user data.\"\"\"\n    return [\n        {\"id\": 1, \"name\": \"Alice\", \"email\": \"alice@test.com\"},\n        {\"id\": 2, \"name\": \"Bob\", \"email\": \"bob@test.com\"},\n    ]\n\n@pytest.fixture\ndef temp_db(tmp_path):\n    \"\"\"Provide a temporary SQLite database.\"\"\"\n    import sqlite3\n    db_path = tmp_path \u002F \"test.db\"\n    conn = sqlite3.connect(str(db_path))\n    conn.execute(\"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)\")\n    conn.commit()\n    yield conn\n    conn.close()\n\ndef test_insert_users(temp_db, sample_users):\n    for user in sample_users:\n        temp_db.execute(\"INSERT INTO users VALUES (?, ?, ?)\",\n                       (user[\"id\"], user[\"name\"], user[\"email\"]))\n    temp_db.commit()\n    count = temp_db.execute(\"SELECT COUNT(*) FROM users\").fetchone()[0]\n    assert count == 2\n\n# Fixture with cleanup\n@pytest.fixture\ndef temp_config_file():\n    path = tempfile.mktemp(suffix=\".json\")\n    with open(path, \"w\") as f:\n        json.dump({\"key\": \"value\"}, f)\n    yield path\n    os.unlink(path)\n```\n\n### Mocking\n\n```python\nfrom unittest.mock import patch, MagicMock, AsyncMock\n\n# Mock a function\n@patch('mymodule.requests.get')\ndef test_fetch_data(mock_get):\n    mock_get.return_value.status_code = 200\n    mock_get.return_value.json.return_value = {\"data\": \"test\"}\n\n    result = fetch_data(\"https:\u002F\u002Fapi.example.com\")\n    assert result == {\"data\": \"test\"}\n    mock_get.assert_called_once_with(\"https:\u002F\u002Fapi.example.com\")\n\n# Mock async\n@patch('mymodule.aiohttp.ClientSession.get', new_callable=AsyncMock)\nasync def test_async_fetch(mock_get):\n    mock_get.return_value.__aenter__.return_value.json = AsyncMock(return_value={\"ok\": True})\n    result = await async_fetch(\"\u002Fendpoint\")\n    assert result[\"ok\"] is True\n\n# Context manager mock\ndef test_file_reader():\n    with patch(\"builtins.open\", MagicMock(return_value=MagicMock(\n        read=MagicMock(return_value='{\"key\": \"val\"}'),\n        __enter__=MagicMock(return_value=MagicMock(read=MagicMock(return_value='{\"key\": \"val\"}'))),\n        __exit__=MagicMock(return_value=False),\n    ))):\n        result = read_config(\"fake.json\")\n        assert result[\"key\"] == \"val\"\n```\n\n### Coverage\n\n```bash\n# Run with coverage\npytest --cov=mypackage --cov-report=term-missing\n\n# HTML report\npytest --cov=mypackage --cov-report=html\n# Open htmlcov\u002Findex.html\n\n# Fail if coverage below threshold\npytest --cov=mypackage --cov-fail-under=80\n```\n\n## Go\n\n### Unit Tests\n\n```go\n\u002F\u002F math.go\npackage math\n\nimport \"errors\"\n\nfunc Add(a, b int) int { return a + b }\n\nfunc Divide(a, b float64) (float64, error) {\n    if b == 0 {\n        return 0, errors.New(\"division by zero\")\n    }\n    return a \u002F b, nil\n}\n\n\u002F\u002F math_test.go\npackage math\n\nimport (\n    \"testing\"\n    \"math\"\n)\n\nfunc TestAdd(t *testing.T) {\n    tests := []struct {\n        name     string\n        a, b     int\n        expected int\n    }{\n        {\"positive\", 2, 3, 5},\n        {\"negative\", -1, 1, 0},\n        {\"zeros\", 0, 0, 0},\n    }\n    for _, tt := range tests {\n        t.Run(tt.name, func(t *testing.T) {\n            got := Add(tt.a, tt.b)\n            if got != tt.expected {\n                t.Errorf(\"Add(%d, %d) = %d, want %d\", tt.a, tt.b, got, tt.expected)\n            }\n        })\n    }\n}\n\nfunc TestDivide(t *testing.T) {\n    result, err := Divide(10, 2)\n    if err != nil {\n        t.Fatalf(\"unexpected error: %v\", err)\n    }\n    if math.Abs(result-5.0) > 0.001 {\n        t.Errorf(\"Divide(10, 2) = %f, want 5.0\", result)\n    }\n}\n\nfunc TestDivideByZero(t *testing.T) {\n    _, err := Divide(10, 0)\n    if err == nil {\n        t.Error(\"expected error for division by zero\")\n    }\n}\n```\n\n### Run Tests\n\n```bash\n# All tests\ngo test .\u002F...\n\n# Verbose\ngo test -v .\u002F...\n\n# Specific package\ngo test .\u002Fpkg\u002Fmath\u002F\n\n# With coverage\ngo test -cover .\u002F...\ngo test -coverprofile=coverage.out .\u002F...\ngo tool cover -html=coverage.out\n\n# Run specific test\ngo test -run TestAdd .\u002F...\n\n# Race condition detection\ngo test -race .\u002F...\n\n# Benchmark\ngo test -bench=. .\u002F...\n```\n\n## Rust\n\n### Unit Tests\n\n```rust\n\u002F\u002F src\u002Fmath.rs\npub fn add(a: i64, b: i64) -> i64 { a + b }\n\npub fn divide(a: f64, b: f64) -> Result\u003Cf64, String> {\n    if b == 0.0 { return Err(\"division by zero\".into()); }\n    Ok(a \u002F b)\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_add() {\n        assert_eq!(add(2, 3), 5);\n        assert_eq!(add(-1, 1), 0);\n    }\n\n    #[test]\n    fn test_divide() {\n        let result = divide(10.0, 2.0).unwrap();\n        assert!((result - 5.0).abs() \u003C f64::EPSILON);\n    }\n\n    #[test]\n    fn test_divide_by_zero() {\n        assert!(divide(10.0, 0.0).is_err());\n    }\n\n    #[test]\n    #[should_panic(expected = \"overflow\")]\n    fn test_overflow_panics() {\n        let _ = add(i64::MAX, 1); \u002F\u002F Will panic on overflow in debug\n    }\n}\n```\n\n```bash\ncargo test\ncargo test -- --nocapture  # Show println output\ncargo test test_add        # Run specific test\ncargo tarpaulin            # Coverage (install: cargo install cargo-tarpaulin)\n```\n\n## Bash Tests\n\n### Simple Test Runner\n\n```bash\n#!\u002Fbin\u002Fbash\n# test.sh - Minimal bash test framework\nPASS=0 FAIL=0\n\nassert_eq() {\n  local actual=\"$1\" expected=\"$2\" label=\"$3\"\n  if [ \"$actual\" = \"$expected\" ]; then\n    echo \"  PASS: $label\"\n    ((PASS++))\n  else\n    echo \"  FAIL: $label (got '$actual', expected '$expected')\"\n    ((FAIL++))\n  fi\n}\n\nassert_exit_code() {\n  local cmd=\"$1\" expected=\"$2\" label=\"$3\"\n  eval \"$cmd\" >\u002Fdev\u002Fnull 2>&1\n  assert_eq \"$?\" \"$expected\" \"$label\"\n}\n\nassert_contains() {\n  local actual=\"$1\" substring=\"$2\" label=\"$3\"\n  if echo \"$actual\" | grep -q \"$substring\"; then\n    echo \"  PASS: $label\"\n    ((PASS++))\n  else\n    echo \"  FAIL: $label ('$actual' does not contain '$substring')\"\n    ((FAIL++))\n  fi\n}\n\n# --- Tests ---\necho \"Running tests...\"\n\n# Test your scripts\noutput=$(.\u002Fmy-script.sh --help 2>&1)\nassert_exit_code \".\u002Fmy-script.sh --help\" \"0\" \"help flag exits 0\"\nassert_contains \"$output\" \"Usage\" \"help shows usage\"\n\noutput=$(.\u002Fmy-script.sh --invalid 2>&1)\nassert_exit_code \".\u002Fmy-script.sh --invalid\" \"1\" \"invalid flag exits 1\"\n\n# Test command outputs\nassert_eq \"$(echo 'hello' | wc -c | tr -d ' ')\" \"6\" \"echo hello is 6 bytes\"\n\necho \"\"\necho \"Results: $PASS passed, $FAIL failed\"\n[ \"$FAIL\" -eq 0 ] && exit 0 || exit 1\n```\n\n## Integration Testing Patterns\n\n### API Integration Test (any language)\n\n```bash\n#!\u002Fbin\u002Fbash\n# test-api.sh - Start server, run tests, tear down\nSERVER_PID=\"\"\ncleanup() { [ -n \"$SERVER_PID\" ] && kill \"$SERVER_PID\" 2>\u002Fdev\u002Fnull; }\ntrap cleanup EXIT\n\n# Start server in background\nnpm start &\nSERVER_PID=$!\nsleep 2  # Wait for server\n\n# Run tests against live server\nBASE_URL=http:\u002F\u002Flocalhost:3000 npx jest --testPathPattern=integration\nEXIT_CODE=$?\n\nexit $EXIT_CODE\n```\n\n### Database Integration Test (Python)\n\n```python\nimport pytest\nimport sqlite3\n\n@pytest.fixture\ndef db():\n    \"\"\"Fresh database for each test.\"\"\"\n    conn = sqlite3.connect(\":memory:\")\n    conn.execute(\"CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT, price REAL)\")\n    yield conn\n    conn.close()\n\ndef test_insert_and_query(db):\n    db.execute(\"INSERT INTO items (name, price) VALUES (?, ?)\", (\"Widget\", 9.99))\n    db.commit()\n    row = db.execute(\"SELECT name, price FROM items WHERE name = ?\", (\"Widget\",)).fetchone()\n    assert row == (\"Widget\", 9.99)\n\ndef test_empty_table(db):\n    count = db.execute(\"SELECT COUNT(*) FROM items\").fetchone()[0]\n    assert count == 0\n```\n\n## TDD Workflow\n\nThe red-green-refactor cycle:\n\n1. **Red**: Write a failing test for the next piece of behavior\n2. **Green**: Write the minimum code to make it pass\n3. **Refactor**: Clean up without changing behavior (tests stay green)\n\n```bash\n# Tight feedback loop\n# Jest watch mode\nnpx jest --watch\n\n# Vitest watch (default)\nnpx vitest\n\n# pytest watch (with pytest-watch)\npip install pytest-watch\nptw\n\n# Go (with air or entr)\nls *.go | entr -c go test .\u002F...\n```\n\n## Debugging Failed Tests\n\n### Common Issues\n\n**Test passes alone, fails in suite** → shared state. Check for:\n- Global variables modified between tests\n- Database not cleaned up\n- Mocks not restored (`afterEach` \u002F `teardown`)\n\n**Test fails intermittently (flaky)** → timing or ordering issue:\n- Async operations without proper `await`\n- Tests depending on execution order\n- Time-dependent logic (use clock mocking)\n- Network calls in unit tests (should be mocked)\n\n**Coverage shows uncovered branches** → missing edge cases:\n- Error paths (what if the API returns 500?)\n- Empty inputs (empty string, null, empty array)\n- Boundary values (0, -1, MAX_INT)\n\n### Run Single Test\n\n```bash\n# Jest\nnpx jest -t \"test name substring\"\n\n# pytest\npytest -k \"test_divide_by_zero\"\n\n# Go\ngo test -run TestDivideByZero .\u002F...\n\n# Rust\ncargo test test_divide\n```\n\n## Tips\n\n- Test behavior, not implementation. Tests should survive refactors.\n- One assertion per concept (not necessarily one `assert` per test, but one logical check).\n- Name tests descriptively: `test_returns_empty_list_when_no_users_exist` beats `test_get_users_2`.\n- Don't mock what you don't own — write thin wrappers around external libraries, mock the wrapper.\n- Integration tests catch bugs unit tests miss. Don't skip them.\n- Use `tmp_path` (pytest), `t.TempDir()` (Go), or `tempfile` (Node) for file-based tests.\n- Snapshot tests are great for detecting unintended changes, bad for evolving formats.\n","# Test Patterns\n\nWrite, run, and debug tests across languages. Covers unit tests, integration tests, E2E tests, mocking, coverage, and TDD workflows.\n\n## When to Use\n\n- Setting up a test suite for a new project\n- Writing unit tests for functions or modules\n- Writing integration tests for APIs or database interactions\n- Setting up code coverage measurement\n- Mocking external dependencies (APIs, databases, file system)\n- Debugging flaky or failing tests\n- Implementing test-driven development (TDD)\n\n## Node.js (Jest \u002F Vitest)\n\n### Setup\n\n```bash\n# Jest\nnpm install -D jest\n# Add to package.json: \"scripts\": { \"test\": \"jest\" }\n\n# Vitest (faster, ESM-native)\nnpm install -D vitest\n# Add to package.json: \"scripts\": { \"test\": \"vitest\" }\n```\n\n### Unit Tests\n\n```javascript\n\u002F\u002F math.js\nexport function add(a, b) { return a + b; }\nexport function divide(a, b) {\n  if (b === 0) throw new Error('Division by zero');\n  return a \u002F b;\n}\n\n\u002F\u002F math.test.js\nimport { add, divide } from '.\u002Fmath.js';\n\ndescribe('add', () => {\n  test('adds two positive numbers', () => {\n    expect(add(2, 3)).toBe(5);\n  });\n\n  test('handles negative numbers', () => {\n    expect(add(-1, 1)).toBe(0);\n  });\n\n  test('handles zero', () => {\n    expect(add(0, 0)).toBe(0);\n  });\n});\n\ndescribe('divide', () => {\n  test('divides two numbers', () => {\n    expect(divide(10, 2)).toBe(5);\n  });\n\n  test('throws on division by zero', () => {\n    expect(() => divide(10, 0)).toThrow('Division by zero');\n  });\n\n  test('handles floating point', () => {\n    expect(divide(1, 3)).toBeCloseTo(0.333, 3);\n  });\n});\n```\n\n### Async Tests\n\n```javascript\n\u002F\u002F api.test.js\nimport { fetchUser } from '.\u002Fapi.js';\n\ntest('fetches user by id', async () => {\n  const user = await fetchUser('123');\n  expect(user).toHaveProperty('id', '123');\n  expect(user).toHaveProperty('name');\n  expect(user.name).toBeTruthy();\n});\n\ntest('throws on missing user', async () => {\n  await expect(fetchUser('nonexistent')).rejects.toThrow('Not found');\n});\n```\n\n### Mocking\n\n```javascript\n\u002F\u002F Mock a module\njest.mock('.\u002Fdatabase.js');\nimport { getUser } from '.\u002Fdatabase.js';\nimport { processUser } from '.\u002Fservice.js';\n\ntest('processes user from database', async () => {\n  \u002F\u002F Setup mock return value\n  getUser.mockResolvedValue({ id: '1', name: 'Alice', active: true });\n\n  const result = await processUser('1');\n  expect(result.processed).toBe(true);\n  expect(getUser).toHaveBeenCalledWith('1');\n  expect(getUser).toHaveBeenCalledTimes(1);\n});\n\n\u002F\u002F Mock fetch\nglobal.fetch = jest.fn();\n\ntest('calls API with correct params', async () => {\n  fetch.mockResolvedValue({\n    ok: true,\n    json: async () => ({ data: 'test' }),\n  });\n\n  const result = await myApiCall('\u002Fendpoint');\n  expect(fetch).toHaveBeenCalledWith('\u002Fendpoint', expect.objectContaining({\n    method: 'GET',\n  }));\n});\n\n\u002F\u002F Spy on existing method (don't replace, just observe)\nconst consoleSpy = jest.spyOn(console, 'log').mockImplementation();\n\u002F\u002F ... run code ...\nexpect(consoleSpy).toHaveBeenCalledWith('expected message');\nconsoleSpy.mockRestore();\n```\n\n### Coverage\n\n```bash\n# Jest\nnpx jest --coverage\n\n# Vitest\nnpx vitest --coverage\n\n# Check coverage thresholds (jest.config.js)\n# coverageThreshold: { global: { branches: 80, functions: 80, lines: 80, statements: 80 } }\n```\n\n## Python (pytest)\n\n### Setup\n\n```bash\npip install pytest pytest-cov\n```\n\n### Unit Tests\n\n```python\n# calculator.py\ndef add(a, b):\n    return a + b\n\ndef divide(a, b):\n    if b == 0:\n        raise ValueError(\"Division by zero\")\n    return a \u002F b\n\n# test_calculator.py\nimport pytest\nfrom calculator import add, divide\n\ndef test_add():\n    assert add(2, 3) == 5\n\ndef test_add_negative():\n    assert add(-1, 1) == 0\n\ndef test_divide():\n    assert divide(10, 2) == 5.0\n\ndef test_divide_by_zero():\n    with pytest.raises(ValueError, match=\"Division by zero\"):\n        divide(10, 0)\n\ndef test_divide_float():\n    assert divide(1, 3) == pytest.approx(0.333, abs=0.001)\n```\n\n### Parametrized Tests\n\n```python\n@pytest.mark.parametrize(\"a,b,expected\", [\n    (2, 3, 5),\n    (-1, 1, 0),\n    (0, 0, 0),\n    (100, -50, 50),\n])\ndef test_add_cases(a, b, expected):\n    assert add(a, b) == expected\n```\n\n### Fixtures\n\n```python\nimport pytest\nimport json\nimport tempfile\nimport os\n\n@pytest.fixture\ndef sample_users():\n    \"\"\"Provide test user data.\"\"\"\n    return [\n        {\"id\": 1, \"name\": \"Alice\", \"email\": \"alice@test.com\"},\n        {\"id\": 2, \"name\": \"Bob\", \"email\": \"bob@test.com\"},\n    ]\n\n@pytest.fixture\ndef temp_db(tmp_path):\n    \"\"\"Provide a temporary SQLite database.\"\"\"\n    import sqlite3\n    db_path = tmp_path \u002F \"test.db\"\n    conn = sqlite3.connect(str(db_path))\n    conn.execute(\"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)\")\n    conn.commit()\n    yield conn\n    conn.close()\n\ndef test_insert_users(temp_db, sample_users):\n    for user in sample_users:\n        temp_db.execute(\"INSERT INTO users VALUES (?, ?, ?)\",\n                       (user[\"id\"], user[\"name\"], user[\"email\"]))\n    temp_db.commit()\n    count = temp_db.execute(\"SELECT COUNT(*) FROM users\").fetchone()[0]\n    assert count == 2\n\n# Fixture with cleanup\n@pytest.fixture\ndef temp_config_file():\n    path = tempfile.mktemp(suffix=\".json\")\n    with open(path, \"w\") as f:\n        json.dump({\"key\": \"value\"}, f)\n    yield path\n    os.unlink(path)\n```\n\n### Mocking\n\n```python\nfrom unittest.mock import patch, MagicMock, AsyncMock\n\n# Mock a function\n@patch('mymodule.requests.get')\ndef test_fetch_data(mock_get):\n    mock_get.return_value.status_code = 200\n    mock_get.return_value.json.return_value = {\"data\": \"test\"}\n\n    result = fetch_data(\"https:\u002F\u002Fapi.example.com\")\n    assert result == {\"data\": \"test\"}\n    mock_get.assert_called_once_with(\"https:\u002F\u002Fapi.example.com\")\n\n# Mock async\n@patch('mymodule.aiohttp.ClientSession.get', new_callable=AsyncMock)\nasync def test_async_fetch(mock_get):\n    mock_get.return_value.__aenter__.return_value.json = AsyncMock(return_value={\"ok\": True})\n    result = await async_fetch(\"\u002Fendpoint\")\n    assert result[\"ok\"] is True\n\n# Context manager mock\ndef test_file_reader():\n    with patch(\"builtins.open\", MagicMock(return_value=MagicMock(\n        read=MagicMock(return_value='{\"key\": \"val\"}'),\n        __enter__=MagicMock(return_value=MagicMock(read=MagicMock(return_value='{\"key\": \"val\"}'))),\n        __exit__=MagicMock(return_value=False),\n    ))):\n        result = read_config(\"fake.json\")\n        assert result[\"key\"] == \"val\"\n```\n\n### Coverage\n\n```bash\n# Run with coverage\npytest --cov=mypackage --cov-report=term-missing\n\n# HTML report\npytest --cov=mypackage --cov-report=html\n# Open htmlcov\u002Findex.html\n\n# Fail if coverage below threshold\npytest --cov=mypackage --cov-fail-under=80\n```\n\n## Go\n\n### Unit Tests\n\n```go\n\u002F\u002F math.go\npackage math\n\nimport \"errors\"\n\nfunc Add(a, b int) int { return a + b }\n\nfunc Divide(a, b float64) (float64, error) {\n    if b == 0 {\n        return 0, errors.New(\"division by zero\")\n    }\n    return a \u002F b, nil\n}\n\n\u002F\u002F math_test.go\npackage math\n\nimport (\n    \"testing\"\n    \"math\"\n)\n\nfunc TestAdd(t *testing.T) {\n    tests := []struct {\n        name     string\n        a, b     int\n        expected int\n    }{\n        {\"positive\", 2, 3, 5},\n        {\"negative\", -1, 1, 0},\n        {\"zeros\", 0, 0, 0},\n    }\n    for _, tt := range tests {\n        t.Run(tt.name, func(t *testing.T) {\n            got := Add(tt.a, tt.b)\n            if got != tt.expected {\n                t.Errorf(\"Add(%d, %d) = %d, want %d\", tt.a, tt.b, got, tt.expected)\n            }\n        })\n    }\n}\n\nfunc TestDivide(t *testing.T) {\n    result, err := Divide(10, 2)\n    if err != nil {\n        t.Fatalf(\"unexpected error: %v\", err)\n    }\n    if math.Abs(result-5.0) > 0.001 {\n        t.Errorf(\"Divide(10, 2) = %f, want 5.0\", result)\n    }\n}\n\nfunc TestDivideByZero(t *testing.T) {\n    _, err := Divide(10, 0)\n    if err == nil {\n        t.Error(\"expected error for division by zero\")\n    }\n}\n```\n\n### Run Tests\n\n```bash\n# All tests\ngo test .\u002F...\n\n# Verbose\ngo test -v .\u002F...\n\n# Specific package\ngo test .\u002Fpkg\u002Fmath\u002F\n\n# With coverage\ngo test -cover .\u002F...\ngo test -coverprofile=coverage.out .\u002F...\ngo tool cover -html=coverage.out\n\n# Run specific test\ngo test -run TestAdd .\u002F...\n\n# Race condition detection\ngo test -race .\u002F...\n\n# Benchmark\ngo test -bench=. .\u002F...\n```\n\n## Rust\n\n### Unit Tests\n\n```rust\n\u002F\u002F src\u002Fmath.rs\npub fn add(a: i64, b: i64) -> i64 { a + b }\n\npub fn divide(a: f64, b: f64) -> Result\u003Cf64, String> {\n    if b == 0.0 { return Err(\"division by zero\".into()); }\n    Ok(a \u002F b)\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_add() {\n        assert_eq!(add(2, 3), 5);\n        assert_eq!(add(-1, 1), 0);\n    }\n\n    #[test]\n    fn test_divide() {\n        let result = divide(10.0, 2.0).unwrap();\n        assert!((result - 5.0).abs() \u003C f64::EPSILON);\n    }\n\n    #[test]\n    fn test_divide_by_zero() {\n        assert!(divide(10.0, 0.0).is_err());\n    }\n\n    #[test]\n    #[should_panic(expected = \"overflow\")]\n    fn test_overflow_panics() {\n        let _ = add(i64::MAX, 1); \u002F\u002F Will panic on overflow in debug\n    }\n}\n```\n\n```bash\ncargo test\ncargo test -- --nocapture  # Show println output\ncargo test test_add        # Run specific test\ncargo tarpaulin            # Coverage (install: cargo install cargo-tarpaulin)\n```\n\n## Bash Tests\n\n### Simple Test Runner\n\n```bash\n#!\u002Fbin\u002Fbash\n# test.sh - Minimal bash test framework\nPASS=0 FAIL=0\n\nassert_eq() {\n  local actual=\"$1\" expected=\"$2\" label=\"$3\"\n  if [ \"$actual\" = \"$expected\" ]; then\n    echo \"  PASS: $label\"\n    ((PASS++))\n  else\n    echo \"  FAIL: $label (got '$actual', expected '$expected')\"\n    ((FAIL++))\n  fi\n}\n\nassert_exit_code() {\n  local cmd=\"$1\" expected=\"$2\" label=\"$3\"\n  eval \"$cmd\" >\u002Fdev\u002Fnull 2>&1\n  assert_eq \"$?\" \"$expected\" \"$label\"\n}\n\nassert_contains() {\n  local actual=\"$1\" substring=\"$2\" label=\"$3\"\n  if echo \"$actual\" | grep -q \"$substring\"; then\n    echo \"  PASS: $label\"\n    ((PASS++))\n  else\n    echo \"  FAIL: $label ('$actual' does not contain '$substring')\"\n    ((FAIL++))\n  fi\n}\n\n# --- Tests ---\necho \"Running tests...\"\n\n# Test your scripts\noutput=$(.\u002Fmy-script.sh --help 2>&1)\nassert_exit_code \".\u002Fmy-script.sh --help\" \"0\" \"help flag exits 0\"\nassert_contains \"$output\" \"Usage\" \"help shows usage\"\n\noutput=$(.\u002Fmy-script.sh --invalid 2>&1)\nassert_exit_code \".\u002Fmy-script.sh --invalid\" \"1\" \"invalid flag exits 1\"\n\n# Test command outputs\nassert_eq \"$(echo 'hello' | wc -c | tr -d ' ')\" \"6\" \"echo hello is 6 bytes\"\n\necho \"\"\necho \"Results: $PASS passed, $FAIL failed\"\n[ \"$FAIL\" -eq 0 ] && exit 0 || exit 1\n```\n\n## Integration Testing Patterns\n\n### API Integration Test (any language)\n\n```bash\n#!\u002Fbin\u002Fbash\n# test-api.sh - Start server, run tests, tear down\nSERVER_PID=\"\"\ncleanup() { [ -n \"$SERVER_PID\" ] && kill \"$SERVER_PID\" 2>\u002Fdev\u002Fnull; }\ntrap cleanup EXIT\n\n# Start server in background\nnpm start &\nSERVER_PID=$!\nsleep 2  # Wait for server\n\n# Run tests against live server\nBASE_URL=http:\u002F\u002Flocalhost:3000 npx jest --testPathPattern=integration\nEXIT_CODE=$?\n\nexit $EXIT_CODE\n```\n\n### Database Integration Test (Python)\n\n```python\nimport pytest\nimport sqlite3\n\n@pytest.fixture\ndef db():\n    \"\"\"Fresh database for each test.\"\"\"\n    conn = sqlite3.connect(\":memory:\")\n    conn.execute(\"CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT, price REAL)\")\n    yield conn\n    conn.close()\n\ndef test_insert_and_query(db):\n    db.execute(\"INSERT INTO items (name, price) VALUES (?, ?)\", (\"Widget\", 9.99))\n    db.commit()\n    row = db.execute(\"SELECT name, price FROM items WHERE name = ?\", (\"Widget\",)).fetchone()\n    assert row == (\"Widget\", 9.99)\n\ndef test_empty_table(db):\n    count = db.execute(\"SELECT COUNT(*) FROM items\").fetchone()[0]\n    assert count == 0\n```\n\n## TDD Workflow\n\nThe red-green-refactor cycle:\n\n1. **Red**: Write a failing test for the next piece of behavior\n2. **Green**: Write the minimum code to make it pass\n3. **Refactor**: Clean up without changing behavior (tests stay green)\n\n```bash\n# Tight feedback loop\n# Jest watch mode\nnpx jest --watch\n\n# Vitest watch (default)\nnpx vitest\n\n# pytest watch (with pytest-watch)\npip install pytest-watch\nptw\n\n# Go (with air or entr)\nls *.go | entr -c go test .\u002F...\n```\n\n## Debugging Failed Tests\n\n### Common Issues\n\n**Test passes alone, fails in suite** → shared state. Check for:\n- Global variables modified between tests\n- Database not cleaned up\n- Mocks not restored (`afterEach` \u002F `teardown`)\n\n**Test fails intermittently (flaky)** → timing or ordering issue:\n- Async operations without proper `await`\n- Tests depending on execution order\n- Time-dependent logic (use clock mocking)\n- Network calls in unit tests (should be mocked)\n\n**Coverage shows uncovered branches** → missing edge cases:\n- Error paths (what if the API returns 500?)\n- Empty inputs (empty string, null, empty array)\n- Boundary values (0, -1, MAX_INT)\n\n### Run Single Test\n\n```bash\n# Jest\nnpx jest -t \"test name substring\"\n\n# pytest\npytest -k \"test_divide_by_zero\"\n\n# Go\ngo test -run TestDivideByZero .\u002F...\n\n# Rust\ncargo test test_divide\n```\n\n## Tips\n\n- Test behavior, not implementation. Tests should survive refactors.\n- One assertion per concept (not necessarily one `assert` per test, but one logical check).\n- Name tests descriptively: `test_returns_empty_list_when_no_users_exist` beats `test_get_users_2`.\n- Don't mock what you don't own — write thin wrappers around external libraries, mock the wrapper.\n- Integration tests catch bugs unit tests miss. Don't skip them.\n- Use `tmp_path` (pytest), `t.TempDir()` (Go), or `tempfile` (Node) for file-based tests.\n- Snapshot tests are great for detecting unintended changes, bad for evolving formats.\n",{"name":5,"version":49,"metadata":50,"description":7},"1.0.0",{"clawdbot":51},{"os":52,"emoji":55,"requires":56},[53,54,38],"linux","darwin","?",{"anyBins":57},[34,27,43,58,59],"cargo","bash","main","4ad602ec8c10264470f26774e1075a29f1bee581","workloads\u002Fskills\u002Fskills\u002Ftest-patterns",1,"https:\u002F\u002Fgithub.com\u002Falbertdobmeyer\u002Fopentrapp","https:\u002F\u002Fopentrapp.com","https:\u002F\u002Fgithub.com\u002Falbertdobmeyer","albertdobmeyer.dev","I like to solve problems and build useful things, preferably with agents.","2026-09-14 04:32:08",{"source":8,"skillName":5,"commands":71,"prompt":81,"zipAvailable":82},[72,75,78],{"tool":73,"command":74},"npx","npx skills add albertdobmeyer\u002Fopentrapp --skill test-patterns",{"tool":76,"command":77},"bunx","bunx skills add albertdobmeyer\u002Fopentrapp --skill test-patterns",{"tool":79,"command":80},"pnpm","pnpm dlx skills add albertdobmeyer\u002Fopentrapp --skill test-patterns","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Falbertdobmeyer\u002Fopentrapp\u002Fblob\u002Fmain\u002Fworkloads\u002Fskills\u002Fskills\u002Ftest-patterns\u002FSKILL.md",true,[84,90,96,100,104,108,112],{"path":85,"resourceType":86,"resourceTypeDesc":87,"sizeBytes":88,"sourceHtmlUrl":89},".trust","OTHER","其他",301,"https:\u002F\u002Fgithub.com\u002Falbertdobmeyer\u002Fopentrapp\u002Fblob\u002Fmain\u002Fworkloads\u002Fskills\u002Fskills\u002Ftest-patterns\u002F.trust",{"path":91,"resourceType":92,"resourceTypeDesc":93,"sizeBytes":94,"sourceHtmlUrl":95},"references\u002Fresearch.md","REFERENCE","参考文档",2434,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSkill-Util\u002Fhuashu-nuwa\u002Fexamples\u002Ftaleb-perspective\u002Freferences\u002Fresearch.md",{"path":97,"resourceType":92,"resourceTypeDesc":93,"sizeBytes":98,"sourceHtmlUrl":99},"references\u002F塔勒布外部批评调研.md",14736,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSkill-Util\u002Fhuashu-nuwa\u002Fexamples\u002Ftaleb-perspective\u002Freferences\u002F塔勒布外部批评调研.md",{"path":101,"resourceType":92,"resourceTypeDesc":93,"sizeBytes":102,"sourceHtmlUrl":103},"references\u002F塔勒布思想体系调研.md",14958,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSkill-Util\u002Fhuashu-nuwa\u002Fexamples\u002Ftaleb-perspective\u002Freferences\u002F塔勒布思想体系调研.md",{"path":105,"resourceType":92,"resourceTypeDesc":93,"sizeBytes":106,"sourceHtmlUrl":107},"references\u002F塔勒布深度对话调研.md",11316,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSkill-Util\u002Fhuashu-nuwa\u002Fexamples\u002Ftaleb-perspective\u002Freferences\u002F塔勒布深度对话调研.md",{"path":109,"resourceType":92,"resourceTypeDesc":93,"sizeBytes":110,"sourceHtmlUrl":111},"references\u002F塔勒布碎片表达与社交媒体人格调研.md",14579,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSkill-Util\u002Fhuashu-nuwa\u002Fexamples\u002Ftaleb-perspective\u002Freferences\u002F塔勒布碎片表达与社交媒体人格调研.md",{"path":113,"resourceType":92,"resourceTypeDesc":93,"sizeBytes":114,"sourceHtmlUrl":115},"references\u002F塔勒布重大决策与实际行动调研-20260404.md",17112,"https:\u002F\u002Fgithub.com\u002Fendman100\u002Fskill-router-nexus\u002Fblob\u002Fmain\u002FSkill-Util\u002Fhuashu-nuwa\u002Fexamples\u002Ftaleb-perspective\u002Freferences\u002F塔勒布重大决策与实际行动调研-20260404.md",[117,127,137,154,163,170],{"skillNo":118,"name":119,"nameZh":6,"description":120,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":121,"categories":122,"keywords":123,"publishedAt":44},"01M6CXDR8F14T6QQJBH1N1VVYS","note-summarizer","Summarizes your notes for quick review.","https:\u002F\u002Fgithub.com\u002Falbertdobmeyer\u002Fopentrapp\u002Fblob\u002Fmain\u002Fworkloads\u002Fskills\u002Ftests\u002Fjudge-fixtures\u002Fnote-summarizer\u002FSKILL.md",[],[124,125,126],"note summarizer","notes","review",{"skillNo":128,"name":129,"nameZh":6,"description":130,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":131,"categories":132,"keywords":133,"publishedAt":44},"01M6CXDNEB4DENBG58DDP5VEN0","judge-clean","A clean reference skill for the judge second-opinion test.","https:\u002F\u002Fgithub.com\u002Falbertdobmeyer\u002Fopentrapp\u002Fblob\u002Fmain\u002Fworkloads\u002Fskills\u002Ftests\u002Fjudge-fixtures\u002Fjudge-clean\u002FSKILL.md",[],[134,135,136,25],"clean","judge clean","reference",{"skillNo":138,"name":139,"nameZh":6,"description":140,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":141,"categories":142,"keywords":146,"publishedAt":44},"01M6CXDK1RYQH2PZK36XJ5QNGZ","{{SKILL_NAME=null}=null}","TODO — Write a one-sentence description of when to use this workflow skill (50-200 chars). Start with an action verb.","https:\u002F\u002Fgithub.com\u002Falbertdobmeyer\u002Fopentrapp\u002Fblob\u002Fmain\u002Fworkloads\u002Fskills\u002Ftemplates\u002Fworkflow\u002FSKILL.md",[143],{"categoryNo":144,"categoryName":145,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[147,148,149,150,53,151,38,152,33,153],"skill name","{{SKILL NAME=null}=null}","{Skill Name=null}","todo","workflow","start","use",{"skillNo":155,"name":139,"nameZh":6,"description":156,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":157,"categories":158,"keywords":160,"publishedAt":44},"01M6CXDGPG4KYBNAYV9YMNY77P","TODO — Write a one-sentence description of when to use this language\u002Fsyntax reference (50-200 chars). Start with an action verb.","https:\u002F\u002Fgithub.com\u002Falbertdobmeyer\u002Fopentrapp\u002Fblob\u002Fmain\u002Fworkloads\u002Fskills\u002Ftemplates\u002Flanguage-ref\u002FSKILL.md",[159],{"categoryNo":144,"categoryName":145,"taxonomyType":21,"taxonomyTypeDesc":22},[148,147,149,153,152,136,161,150,33,53,38,162],"language","syntax",{"skillNo":164,"name":139,"nameZh":6,"description":165,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":166,"categories":167,"keywords":169,"publishedAt":44},"01M6CXDEB146MPK7ES94E4CCFN","TODO — Write a one-sentence description of when to use this skill (50-200 chars). Start with an action verb.","https:\u002F\u002Fgithub.com\u002Falbertdobmeyer\u002Fopentrapp\u002Fblob\u002Fmain\u002Fworkloads\u002Fskills\u002Ftemplates\u002Fcli-tool\u002FSKILL.md",[168],{"categoryNo":144,"categoryName":145,"taxonomyType":21,"taxonomyTypeDesc":22},[149,148,147,53,38,152,33,153,150],{"skillNo":171,"name":172,"nameZh":6,"description":173,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":174,"categories":175,"keywords":176,"publishedAt":44},"01M6CXD7M5TY1Q6J0ST96A03PC","ssh-tunnel","SSH tunneling, port forwarding, and remote access patterns. Use when setting up local\u002Fremote\u002Fdynamic port forwards, configuring jump hosts, managing SSH keys, multiplexing connections, transferring files with scp\u002Frsync, or debugging SSH connection issues.","https:\u002F\u002Fgithub.com\u002Falbertdobmeyer\u002Fopentrapp\u002Fblob\u002Fmain\u002Fworkloads\u002Fskills\u002Fskills\u002Fssh-tunnel\u002FSKILL.md",[],[177,178,179,40,38,180,181,182,183,153,31,184,26,185,53],"ssh tunnel","ssh","local","issues","tunneling","access","scp","files","connections"]