Unit test generation
QA Skills Directory QA Skills is a curated directory of testing-specific skills for AI coding agents (Claude Code, Cursor, Copilot, etc.).
npx -y skills add PramodDutta/qaskills --skill unit-test-generationAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
What its author says it does
Copied from the file, not written here
Generate high-signal unit tests for existing code, behavior-first case selection, boundary and error paths, mocking discipline, mutation-tested quality, and framework-idiomatic output for Vitest, Jest, and pytest.
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
6.8 KB, as published. Nobody here has run it
Unit Test Generation Skill
You are an expert software engineer generating unit tests for existing code. Your tests must catch real future bugs, not inflate coverage numbers. Follow these instructions whenever asked to "write tests for" a function, module, or class.
Core Principles
- Read before you write. Understand the unit's contract (inputs, outputs, errors, side effects) and its callers before generating anything; tests encode the contract, not the current implementation.
- Behavior first, lines second. Derive cases from what the function promises; use coverage only to find untested branches afterwards.
- One behavior per test, named as a sentence.
refuses expired couponsbeatstest_coupon_2. - Assert outcomes, not implementation. Calling internals or over-specifying mock interactions makes refactors fail tests without bugs.
- A generated test you have not seen fail is unverified. Mentally (or actually) break the code to confirm each test would catch it.
Case Selection Algorithm
For each public function, enumerate in this order:
- Happy paths: one per meaningful input class (equivalence partitions)
- Boundaries: empty, one, many; zero, negative, max; exact limits +/- 1
- Error contract: every documented throw/rejection/error return, asserted by TYPE and meaningful message
- Special values: null/undefined/None, NaN, empty string vs whitespace, unicode, duplicates, unsorted input to order-sensitive code
- State and idempotency: repeated calls, call order, mutation of inputs (assert the function does NOT mutate arguments unless documented)
- Concurrency/async: rejected promises, timeout paths, out-of-order resolution where relevant
Skip: private helpers (test through the public surface), trivial getters, framework glue.
Vitest/Jest Pattern
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { calculateDiscount } from './discount';
import * as rates from './rates';
describe('calculateDiscount', () => {
it('applies percentage discount to eligible subtotal', () => {
expect(calculateDiscount({ subtotal: 200, code: 'SAVE10' })).toEqual({ total: 180, applied: true });
});
it.each([
[0, 0], // zero subtotal
[0.01, 0.01], // minimum
[49.99, 49.99], // just under threshold: no discount
[50, 45], // threshold boundary: discount applies
])('boundary: subtotal %f -> total %f', (subtotal, total) => {
expect(calculateDiscount({ subtotal, code: 'SAVE10' }).total).toBeCloseTo(total, 2);
});
it('throws CodeExpiredError with the expiry date for expired codes', () => {
expect(() => calculateDiscount({ subtotal: 100, code: 'XMAS2024' }))
.toThrowError(expect.objectContaining({ name: 'CodeExpiredError' }));
});
it('does not mutate the input order object', () => {
const input = Object.freeze({ subtotal: 100, code: 'SAVE10' });
expect(() => calculateDiscount(input)).not.toThrow();
});
it('fetches live rates only for FX orders (mock at the boundary)', async () => {
const spy = vi.spyOn(rates, 'fetchRate').mockResolvedValue(1.1);
await calculateDiscount({ subtotal: 100, code: 'SAVE10', currency: 'EUR' });
expect(spy).toHaveBeenCalledWith('EUR'); // interaction that IS the contract
});
});
pytest Pattern
import pytest
from discount import calculate_discount, CodeExpiredError
class TestCalculateDiscount:
def test_applies_percentage_to_eligible_subtotal(self):
assert calculate_discount(subtotal=200, code="SAVE10").total == 180
@pytest.mark.parametrize("subtotal,total", [
(0, 0), (0.01, 0.01), (49.99, 49.99), (50, 45),
])
def test_threshold_boundaries(self, subtotal, total):
assert calculate_discount(subtotal=subtotal, code="SAVE10").total == pytest.approx(total)
def test_expired_code_raises_with_expiry(self):
with pytest.raises(CodeExpiredError, match=r"expired on \d{4}-\d{2}-\d{2}"):
calculate_discount(subtotal=100, code="XMAS2024")
def test_unknown_code_is_no_op_not_error(self):
result = calculate_discount(subtotal=100, code="NOPE")
assert result.total == 100 and result.applied is False
Mocking Discipline
Mock ONLY at architectural boundaries: network, filesystem, clock, randomness, databases, third-party SDKs. Never mock the module under test's own collaborators just to isolate lines.
- Clock: inject or
vi.useFakeTimers()/ freezegun; no test should depend on real now() - Randomness: seed or inject
- Network: MSW or respx at the HTTP layer beats stubbing your own client methods
- Verify interactions only when the interaction IS the contract (sent the email, charged once); otherwise assert return values
Quality Gate: Prove the Tests Bite
- Break-the-code pass: flip one operator in the unit; at least one test must fail. Repeat for each core branch.
- Coverage as gap-finder: run
vitest run --coverageorpytest --cov; inspect UNCOVERED branches and either add a behavior case or document why it is unreachable. Do not chase 100%. - Mutation testing when it matters: Stryker (JS/TS) or mutmut (Python) on critical modules; surviving mutants = weak assertions.
- Determinism: run the new suite 5x; any flake is a bug in the tests (shared state, time, ordering).
Common Mistakes
- Snapshot tests as a substitute for assertions on computed values; snapshots freeze bugs
- Asserting
toHaveBeenCalledTimeson internals so refactors fail without behavior changes - Generated tests that re-implement the function's logic in the expectation (tautological tests)
- One giant test per function covering everything; failures become unreadable
- Testing error MESSAGES exactly when only the error TYPE is the contract (brittle) or vice versa (too loose)
Checklist
- Contract read (signature, docs, callers) before generation
- Happy paths, boundaries, error contract, special values, mutation/idempotency covered
- Mocks only at boundaries; clock and randomness controlled
- Each test named as a behavior sentence; one behavior per test
- Break-the-code pass done; coverage gaps triaged; suite deterministic across 5 runs