[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6FP68JRP2ZEE2GENHJMT9VT":3,"skill-same:01M6FP68JRP2ZEE2GENHJMT9VT":85},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":22,"publishedAt":43,"linkOnly":44,"markdownContent":45,"markdownBody":46,"frontMatter":47,"refName":58,"commitSha":59,"skillDirectory":60,"forksCount":61,"repositoryHtmlUrl":62,"repositoryHomepageUrl":6,"ownerHtmlUrl":63,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":64,"install":65,"resources":78},"01M6FP68JRP2ZEE2GENHJMT9VT","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.","kbarbel640-del\u002Fskills","kbarbel640-del","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F255315490?v=4","01M6CJPPQGCCESXMG3K7BBGQ5J","01M6CJPPX2PB2BCZERFVM2C2B3",1,"MIT","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fgitgoodordietrying\u002Ftest-patterns\u002FSKILL.md",[17],{"categoryNo":18,"categoryName":19,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",[23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42],"test patterns","test","patterns","node","unit","across","jest","pytest","up","coverage","write","writing","python3","Bash","win32","run","debugging","e2e tests","rust","go","2026-11-10 10:12:30",false,"---\nname: test-patterns\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,"metadata":48,"description":7},{"clawdbot":49},{"os":50,"emoji":53,"requires":54},[51,52,37],"linux","darwin","?",{"anyBins":55},[26,35,42,56,57],"cargo","bash","main","e7e336ac628c3e3bd771aff1aaa469c93778f94a","skills\u002Fgitgoodordietrying\u002Ftest-patterns",0,"https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del","2026-08-23 22:39:37",{"source":8,"skillName":5,"commands":66,"prompt":76,"zipAvailable":77},[67,70,73],{"tool":68,"command":69},"npx","npx skills add kbarbel640-del\u002Fskills --skill test-patterns",{"tool":71,"command":72},"bunx","bunx skills add kbarbel640-del\u002Fskills --skill test-patterns",{"tool":74,"command":75},"pnpm","pnpm dlx skills add kbarbel640-del\u002Fskills --skill test-patterns","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fgitgoodordietrying\u002Ftest-patterns\u002FSKILL.md",true,[79],{"path":80,"resourceType":81,"resourceTypeDesc":82,"sizeBytes":83,"sourceHtmlUrl":84},"_meta.json","REFERENCE","参考文档",290,"https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fgitgoodordietrying\u002Ftest-patterns\u002F_meta.json",[86,117,144,165,171,189],{"skillNo":87,"name":88,"nameZh":6,"description":89,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":90,"categories":91,"keywords":97,"publishedAt":43},"01M6G8J55967D6281ACMKAKTEQ","crowd-prompting","A marketplace where AI agents improve prompts, system instructions, tool descriptions, and other text-based content with domain expertise from real-world operations — and earn tokens for valuable contributions.","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fzzadrian\u002Fcrowd-prompting\u002FSKILL.md",[92],{"categoryNo":93,"categoryName":94,"taxonomyType":95,"taxonomyTypeDesc":96},"01JBSEEDCAT000000000000036","内容创作者","PROFESSION","适用职业",[98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116],"crowd prompting","prompting","api","with","tokens","domain","world","prompts","operations","instructions","text","tool","system","improve","productivity","marketplace","https","content","ai agents",{"skillNo":118,"name":119,"nameZh":6,"description":120,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":121,"categories":122,"keywords":126,"publishedAt":43},"01M6G8J0PKPAYVET6BWE8W4C26","dilemm-ai","Compete in DilemmAI, the prisoner's dilemma AI arena at dilemm.ai. Use when an OpenClaw agent wants to create an account, design and submit strategy prompts for their bot, enter matchmaking, analyze historical matches and leaderboards, and iterate on bot strategy. Requires headless browser automation (no API available yet).","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fzyori\u002Fdilemm-ai\u002FSKILL.md",[123],{"categoryNo":124,"categoryName":125,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000023","Skill与智能体开发",[127,100,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,105,143],"dilemm ai","arena","analyze","browser","openclaw agent","create","automation","browser automation","bot","openclaw","design","strategy","headless","compete","submit","matchmaking","use",{"skillNo":145,"name":146,"nameZh":6,"description":147,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":148,"categories":149,"keywords":153,"publishedAt":43},"01M6G8HWAF3K9SW7NP3Z83CPJ6","arxiv-paper-reviews","Interact with arXiv Crawler API to fetch papers, read reviews, and submit comments. Use when working with arXiv papers, fetching paper lists by date\u002Fcategory\u002Finterest, viewing paper details with comments, or submitting paper reviews via API at http:\u002F\u002F150.158.152.82:8000.","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fzxrys\u002Fweak-accept\u002FSKILL.md",[150],{"categoryNo":151,"categoryName":152,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000021","资料检索与研究",[154,155,156,157,158,159,160,101,161,141,143,162,100,163,164],"arxiv paper reviews","reviews","arxiv","paper","crawler","papers","comments","fetch","read","http","via",{"skillNo":166,"name":146,"nameZh":6,"description":147,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":167,"categories":168,"keywords":170,"publishedAt":43},"01M6G8HQJ7N7WMY6YV7D7SSJ16","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fzxrys\u002Farxiv-paper-reviews\u002FSKILL.md",[169],{"categoryNo":151,"categoryName":152,"taxonomyType":20,"taxonomyTypeDesc":21},[157,156,155,154,164,159,160,101,158,141,143,161,162,100,163],{"skillNo":172,"name":173,"nameZh":6,"description":174,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":175,"categories":176,"keywords":177,"publishedAt":43},"01M6G8HM95Y64JNVFC2HN4E2BG","git-summary","Get a quick summary of the current Git repository including status, recent commits, branches, and contributors.","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fzweack\u002Fgit-summary\u002FSKILL.md",[],[178,179,180,181,182,37,183,51,184,185,186,187,188],"git","summary","git summary","commits","repository","status","contributors","true","quick","get","branches",{"skillNo":190,"name":191,"nameZh":6,"description":192,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":193,"categories":194,"keywords":199,"publishedAt":43},"01M6G8HGEGF05CNEJCGVYFSC4R","agent-constitution","Interact with AgentConstitution governance contracts on Base Sepolia. Check compliance, read rules, log actions, query governance state.","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fztsalexey\u002Fagent-constitution\u002FSKILL.md",[195,196],{"categoryNo":124,"categoryName":125,"taxonomyType":20,"taxonomyTypeDesc":21},{"categoryNo":197,"categoryName":198,"taxonomyType":95,"taxonomyTypeDesc":96},"01JBSEEDCAT000000000000037","律师与法务",[200,201,202,203,101,204,205,58,206,162,207,208,209,210,211,114,212,213,214],"agent constitution","constitution","contracts","tree","query","base","log","check","Rules","projects","compliance","actions","state","governance","github"]