Test Writer Skill
Overview
Generate comprehensive, maintainable test suites. Focuses on correctness, isolation, and readability - tests that catch real bugs and survive refactoring.
Principles
- One assertion concept per test - each test validates one specific behavior
- Descriptive names -
test_<unit>_<scenario>_<expected> format
- Isolation - no shared mutable state between tests; mock external dependencies
- Determinism - no flakiness from time, randomness, or network
- Coverage - happy path + edge cases + error paths
Step-by-Step Process
Step 1: Analyse the Code Under Test
Identify:
- Inputs: parameters, types, valid ranges, optional vs required
- Outputs: return values, side effects (file writes, DB calls, HTTP requests)
- Dependencies: external systems to mock (DB, HTTP, clock, filesystem)
- Behaviors: branching logic, loops, error handling paths
Step 2: Define Test Cases
For each function/method, write test cases for:
| Category |
Examples |
| Happy path |
Valid inputs → expected output |
| Boundary values |
0, -1, max int, empty string, empty list |
| None / null |
Missing optional fields, None arguments |
| Type errors |
Wrong types where applicable |
| Domain errors |
Negative price, future birth date, invalid email |
| External failure |
DB down, HTTP 500, file not found |
Step 3: Python / pytest
import pytest
from myapp.billing import calculate_discount
class TestCalculateDiscount:
def test_gold_tier_applies_20_percent(self):
assert calculate_discount(price=100.0, tier="gold") == 80.0
def test_standard_tier_applies_no_discount(self):
assert calculate_discount(price=100.0, tier="standard") == 100.0
def test_zero_price_returns_zero(self):
assert calculate_discount(price=0.0, tier="gold") == 0.0
def test_negative_price_raises_value_error(self):
with pytest.raises(ValueError, match="Price must be non-negative"):
calculate_discount(price=-10.0, tier="gold")
def test_unknown_tier_raises_value_error(self):
with pytest.raises(ValueError, match="Unknown tier"):
calculate_discount(price=100.0, tier="diamond")
@pytest.mark.parametrize("tier,expected", [
("gold", 80.0),
("silver", 90.0),
("bronze", 95.0),
])
def test_tier_discounts_parametrized(self, tier, expected):
assert calculate_discount(price=100.0, tier=tier) == expected
Mocking external dependencies
from unittest.mock import patch, MagicMock
import pytest
def test_send_invoice_calls_email_service():
with patch("myapp.billing.email_client") as mock_email:
mock_email.send.return_value = {"status": "sent"}
result = send_invoice(user_id="u1", amount=50.0)
mock_email.send.assert_called_once_with(
to="user@example.com", subject="Your invoice", amount=50.0
)
assert result["status"] == "sent"
Fixtures
@pytest.fixture
def db_session():
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
session = Session(engine)
yield session
session.close()
def test_create_user_persists_to_db(db_session):
user = create_user(db_session, name="Alice", email="alice@example.com")
fetched = db_session.get(User, user.id)
assert fetched.name == "Alice"
Step 4: JavaScript / Jest
// billing.test.js
import { calculateDiscount } from './billing';
describe('calculateDiscount', () => {
it('applies 20% for gold tier', () => {
expect(calculateDiscount(100, 'gold')).toBe(80);
});
it('returns 0 for zero price', () => {
expect(calculateDiscount(0, 'gold')).toBe(0);
});
it('throws for negative price', () => {
expect(() => calculateDiscount(-10, 'gold')).toThrow('Price must be non-negative');
});
it.each([
['gold', 80],
['silver', 90],
['bronze', 95],
])('tier %s gets expected discount', (tier, expected) => {
expect(calculateDiscount(100, tier)).toBe(expected);
});
});
Mocking in Jest
jest.mock('./emailClient');
import { emailClient } from './emailClient';
test('sendInvoice calls email client with correct args', async () => {
emailClient.send.mockResolvedValue({ status: 'sent' });
await sendInvoice('u1', 50);
expect(emailClient.send).toHaveBeenCalledWith(
expect.objectContaining({ amount: 50 })
);
});
Step 5: API / Endpoint Tests (pytest + httpx)
import pytest
from httpx import AsyncClient
from myapp.server import app
@pytest.mark.asyncio
async def test_get_user_returns_200():
async with AsyncClient(app=app, base_url="http://test") as client:
response = await client.get("/users/1")
assert response.status_code == 200
assert response.json()["id"] == 1
@pytest.mark.asyncio
async def test_get_unknown_user_returns_404():
async with AsyncClient(app=app, base_url="http://test") as client:
response = await client.get("/users/99999")
assert response.status_code == 404
Coverage Checklist
1---2name: test-writer3description: Write unit tests, integration tests, and end-to-end tests following best practices. Covers pytest, Jest, Go testing, and JUnit. Generates tests for functions, classes, REST endpoints, and database operations. Use when the user wants to write tests, increase test coverage, practice TDD, or add a test suite to existing code.4license: Apache-2.05---67# Test Writer Skill89## Overview10Generate comprehensive, maintainable test suites. Focuses on correctness, isolation, and readability - tests that catch real bugs and survive refactoring.1112## Principles131. **One assertion concept per test** - each test validates one specific behavior142. **Descriptive names** - `test_<unit>_<scenario>_<expected>` format153. **Isolation** - no shared mutable state between tests; mock external dependencies164. **Determinism** - no flakiness from time, randomness, or network175. **Coverage** - happy path + edge cases + error paths1819## Step-by-Step Process2021### Step 1: Analyse the Code Under Test22Identify:23- **Inputs**: parameters, types, valid ranges, optional vs required24- **Outputs**: return values, side effects (file writes, DB calls, HTTP requests)25- **Dependencies**: external systems to mock (DB, HTTP, clock, filesystem)26- **Behaviors**: branching logic, loops, error handling paths2728### Step 2: Define Test Cases2930For each function/method, write test cases for:31| Category | Examples |32|----------|----------|33| Happy path | Valid inputs → expected output |34| Boundary values | 0, -1, max int, empty string, empty list |35| None / null | Missing optional fields, None arguments |36| Type errors | Wrong types where applicable |37| Domain errors | Negative price, future birth date, invalid email |38| External failure | DB down, HTTP 500, file not found |3940### Step 3: Python / pytest4142```python43import pytest44from myapp.billing import calculate_discount4546class TestCalculateDiscount:47 def test_gold_tier_applies_20_percent(self):48 assert calculate_discount(price=100.0, tier="gold") == 80.04950 def test_standard_tier_applies_no_discount(self):51 assert calculate_discount(price=100.0, tier="standard") == 100.05253 def test_zero_price_returns_zero(self):54 assert calculate_discount(price=0.0, tier="gold") == 0.05556 def test_negative_price_raises_value_error(self):57 with pytest.raises(ValueError, match="Price must be non-negative"):58 calculate_discount(price=-10.0, tier="gold")5960 def test_unknown_tier_raises_value_error(self):61 with pytest.raises(ValueError, match="Unknown tier"):62 calculate_discount(price=100.0, tier="diamond")6364 @pytest.mark.parametrize("tier,expected", [65 ("gold", 80.0),66 ("silver", 90.0),67 ("bronze", 95.0),68 ])69 def test_tier_discounts_parametrized(self, tier, expected):70 assert calculate_discount(price=100.0, tier=tier) == expected71```7273**Mocking external dependencies**74```python75from unittest.mock import patch, MagicMock76import pytest7778def test_send_invoice_calls_email_service():79 with patch("myapp.billing.email_client") as mock_email:80 mock_email.send.return_value = {"status": "sent"}81 result = send_invoice(user_id="u1", amount=50.0)82 mock_email.send.assert_called_once_with(83 to="user@example.com", subject="Your invoice", amount=50.084 )85 assert result["status"] == "sent"86```8788**Fixtures**89```python90@pytest.fixture91def db_session():92 engine = create_engine("sqlite:///:memory:")93 Base.metadata.create_all(engine)94 session = Session(engine)95 yield session96 session.close()9798def test_create_user_persists_to_db(db_session):99 user = create_user(db_session, name="Alice", email="alice@example.com")100 fetched = db_session.get(User, user.id)101 assert fetched.name == "Alice"102```103104### Step 4: JavaScript / Jest105106```javascript107// billing.test.js108import { calculateDiscount } from './billing';109110describe('calculateDiscount', () => {111 it('applies 20% for gold tier', () => {112 expect(calculateDiscount(100, 'gold')).toBe(80);113 });114115 it('returns 0 for zero price', () => {116 expect(calculateDiscount(0, 'gold')).toBe(0);117 });118119 it('throws for negative price', () => {120 expect(() => calculateDiscount(-10, 'gold')).toThrow('Price must be non-negative');121 });122123 it.each([124 ['gold', 80],125 ['silver', 90],126 ['bronze', 95],127 ])('tier %s gets expected discount', (tier, expected) => {128 expect(calculateDiscount(100, tier)).toBe(expected);129 });130});131```132133**Mocking in Jest**134```javascript135jest.mock('./emailClient');136import { emailClient } from './emailClient';137138test('sendInvoice calls email client with correct args', async () => {139 emailClient.send.mockResolvedValue({ status: 'sent' });140 await sendInvoice('u1', 50);141 expect(emailClient.send).toHaveBeenCalledWith(142 expect.objectContaining({ amount: 50 })143 );144});145```146147### Step 5: API / Endpoint Tests (pytest + httpx)148149```python150import pytest151from httpx import AsyncClient152from myapp.server import app153154@pytest.mark.asyncio155async def test_get_user_returns_200():156 async with AsyncClient(app=app, base_url="http://test") as client:157 response = await client.get("/users/1")158 assert response.status_code == 200159 assert response.json()["id"] == 1160161@pytest.mark.asyncio162async def test_get_unknown_user_returns_404():163 async with AsyncClient(app=app, base_url="http://test") as client:164 response = await client.get("/users/99999")165 assert response.status_code == 404166```167168## Coverage Checklist169- [ ] Happy path test exists for every public function170- [ ] `None` / empty inputs tested for functions that accept optional args171- [ ] All `if` branches exercised (aim for >80% branch coverage)172- [ ] Every exception type the code raises has a test173- [ ] All external dependencies (DB, HTTP, clock) are mocked174- [ ] No test depends on execution order or global state