skills/coding/test-readability/SKILL.md
Master writing readable tests: how to structure tests for clarity, name tests descriptively, and make tests self-documenting. Use when writing tests, reviewing test quality, or when tests are hard to understand. Triggers especially when user says "readable tests", "test structure", "test naming", "test organization", "self-documenting tests" or "clear tests".
npx skillsauth add ImaginerLabs/skill-manager test-readabilityInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Test-readability is a behavioral specification skill that encapsulates the methodology for writing tests that are self-documenting, well-structured, and communicate intent clearly.
This skill embodies the experience of developers who've inherited tests they couldn't understand and spent hours deciphering what they did — and knows that readable tests are as important as working code.
"Tests are documentation. When you write a test, you're documenting how the code should behave. If someone can read your test and understand the system, you've done it right."
┌─────────────────────────────────────────────────────────────┐
│ READABLE vs UNREADABLE │
│ │
│ UNREADABLE: READABLE: │
│ ─────────── ───────── │
│ test('1', () => {...}) test('returns user when │
│ test('2', () => {...}) valid id provided') │
│ │
│ expect(u).toBe(e) expect(gotUser).toEqual( │
│ expectedUser) │
│ │
│ No context Clear intent │
│ Cryptic Self-documenting │
└─────────────────────────────────────────────────────────────┘
| Aspect | Poor | Good | |--------|------|------| | Name | "test 1" | "should return user when valid id provided" | | Structure | Everything in one block | Arrange-Act-Assert | | Assertions | Cryptic | Self-documenting | | Context | Hidden | Explicit | | Intent | Hidden | Clear |
┌─────────────────────────────────────────────────────────────┐
│ TEST NAME PATTERNS │
│ │
│ PATTERN 1: "should [expected behavior]" │
│ "should return user when valid id provided" │
│ │
│ PATTERN 2: "[action] [target] [expected result]" │
│ "returns user for valid id" │
│ │
│ PATTERN 3: "[when/scenario] [expected]" │
│ "when user not found throws error" │
│ │
│ AVOID: "test 1", "test 2", "does thing" │
└─────────────────────────────────────────────────────────────┘
// CLASSIC: Arrange-Act-Assert
describe('UserService', () => {
describe('getUserById', () => {
it('should return user when id exists', async () => {
// ARRANGE: Set up test data
const existingUser = await createTestUser({ id: '123' });
// ACT: Perform the action
const result = await userService.getUserById('123');
// ASSERT: Verify the outcome
expect(result).toEqual(existingUser);
});
});
});
// BDD: Given-When-Then
describe('OrderProcessor', () => {
describe('when order is valid', () => {
given('a valid order with in-stock items', () => {
const order = createOrder({ items: [{ inStock: true }] });
when('processOrder is called', () => {
const result = orderProcessor.process(order);
then('should mark order as processed', () => {
expect(result.status).toBe('processed');
});
});
});
});
});
// ORGANIZED: Nested describe blocks
describe('PaymentService', () => {
describe('processPayment', () => {
describe('when payment method is credit card', () => {
describe('and card is valid', () => {
it('should process payment successfully', () => {
// Test for valid card
});
it('should charge correct amount', () => {
// Test amount calculation
});
});
describe('and card is expired', () => {
it('should reject payment', () => {
// Test expired card handling
});
});
});
describe('when payment method is PayPal', () => {
// PayPal-specific tests
});
});
});
// EXCELLENT: Self-documenting
it('should return null when user does not exist', () => { ... });
it('should throw ValidationError when email is invalid', () => { ... });
it('should retry three times before failing on network error', () => { ... });
it('should format currency correctly for USD locale', () => { ... });
// Each name tells you:
// 1. What triggers it (when)
// 2. What expected behavior (should)
// 3. No need to read the test body to understand
// TEMPLATE: should [expected result] when [condition]
it('should return {value} when {condition}', () => { ... });
it('should throw {error} when {condition}', () => { ... });
it('should {action} when {condition}', () => { ... });
// Examples:
it('should return empty array when user has no orders', () => { ... });
it('should throw UnauthorizedError when token is expired', () => { ... });
it('should redirect to dashboard when login succeeds', () => { ... });
// BAD: Cryptic names
it('test 1', () => { ... });
it('process', () => { ... });
it('works', () => { ... });
it('edge case', () => { ... });
// BAD: Implementation details in name
it('calls repository.findById', () => { ... });
it('uses cache.get', () => { ... });
// BAD: Too vague
it('handles error', () => { ... });
it('works correctly', () => { ... });
// BAD: Cryptic assertion
expect(res).toBe(true);
// GOOD: Descriptive assertion
expect(isValid).toBe(true);
expect(paymentProcessed).toBe(true);
// GREAT: Clear value in assertion
expect(result).toEqual({
status: 'success',
userId: 'user-123',
amount: 99.99,
});
// EVEN BETTER: Named expected value
const expectedOrder = {
status: 'pending',
items: [expectedItem],
};
expect(order).toEqual(expectedOrder);
// GOOD: Descriptive helpers
function createValidUser(overrides?: Partial<User>): User {
return {
id: 'user-123',
email: '[email protected]',
name: 'Test User',
isActive: true,
...overrides,
};
}
// Usage in test
it('should reject inactive user', async () => {
const inactiveUser = createValidUser({ isActive: false });
// Clear what 'inactive' means in context
});
// GOOD: Why, not what
it('should handle race condition in concurrent updates', () => {
// Race condition occurs when two requests try to update
// the same resource simultaneously. The database should
// use optimistic locking to handle this gracefully.
});
// BAD: What, not why (code already says this)
// Increment counter
counter++;
// GOOD: Add context when non-obvious
// Use <= not < to match the API spec's inclusive bounds
const isInRange = value <= MAX_VALUE;
describe('UserService', () => {
// GROUP: Creation
describe('createUser', () => { ... });
describe('createAdmin', () => { ... });
// GROUP: Retrieval
describe('getUserById', () => { ... });
describe('getUserByEmail', () => { ... });
// GROUP: Updates
describe('updateUser', () => { ... });
describe('deleteUser', () => { ... });
});
describe('OrderService', () => {
let orderService: OrderService;
let mockOrderRepo: MockOrderRepository;
// SHARED SETUP: Runs before each test
beforeEach(() => {
mockOrderRepo = createMockOrderRepository();
orderService = new OrderService(mockOrderRepo);
});
// CLEANUP: Runs after each test
afterEach(() => {
vi.clearAllMocks();
});
it('should create order', () => { ... });
it('should cancel order', () => { ... });
});
// BAD: 100 lines in one test
it('should process full order flow', async () => {
// Create user...
// Create product...
// Add to cart...
// Apply discount...
// Calculate total...
// Create payment...
// Verify email...
// Update inventory...
// 100 lines later...
});
// GOOD: Split into focused tests
it('should create order from cart', () => {
const cart = createCartWithItems();
const order = orderService.createOrder(cart);
expect(order.items).toEqual(cart.items);
});
it('should calculate total with tax', () => {
const order = createOrder({ subtotal: 100 });
const total = orderService.calculateTotal(order);
expect(total).toBe(110); // 10% tax
});
it('should send confirmation email', () => {
const order = createCompletedOrder();
await orderService.sendConfirmation(order);
expect(emailService.send).toHaveBeenCalledWith(order.email);
});
// BAD: Unclear context
it('should handle 400 error', () => {
mockApi.post.mockRejectedValue({ status: 400 });
// What endpoint? What causes 400?
});
// GOOD: Clear context
it('should return ValidationError when POST /users has invalid email', async () => {
mockApi.post.mockRejectedValue({
status: 400,
message: 'Invalid email format',
});
await expect(createUser({ email: 'invalid' }))
.rejects.toThrow(ValidationError);
});
// BAD: Magic numbers
it('should calculate', () => {
const result = calculate(100, 20, 5);
expect(result).toBe(25);
// What do 100, 20, 5 mean?
});
// GOOD: Named values
it('should apply discount correctly', () => {
const price = 100;
const discountPercent = 20;
const taxRate = 5;
const result = calculate(price, discountPercent, taxRate);
const expected = (price * (1 - discountPercent / 100)) * (1 + taxRate / 100);
expect(result).toBeCloseTo(expected);
});
// BAD
test('1', () => { ... });
test('a', () => { ... });
test('t', () => { ... });
// GOOD
test('should return user when id is valid', () => { ... });
// BAD: Everything in one flat describe
describe('Tests', () => {
it('test 1', () => { ... });
it('test 2', () => { ... });
it('test 3', () => { ... });
});
// GOOD: Organized structure
describe('UserService', () => {
describe('getUserById', () => {
it('should return user when exists', () => { ... });
it('should throw when not found', () => { ... });
});
});
// BAD: What is being checked?
expect(result).toBe(1);
// GOOD: Clear expectation
expect(userRole).toBe(Role.ADMIN);
expect(hasPermission).toBe(true);
expect(errorCode).toBe('INVALID_EMAIL');
// BAD: Assumes global state
beforeEach(() => {
db.clear(); // What db?
});
// GOOD: Clear setup
beforeEach(() => {
testDatabase.clear(); // What is being cleared?
});
TEST READABILITY CHECKLIST
==========================
□ 1. Does the test name describe WHAT it tests?
□ 2. Is the scenario/condition clear?
□ 3. Is the expected behavior obvious?
□ 4. Is there Arrange-Act-Assert structure?
□ 5. Are assertions self-documenting?
□ 6. Are there clear setup/teardown?
□ 7. Can someone understand test without reading code?
□ 8. Are helper functions used to reduce noise?
□ 9. Are magic values named?
□ 10. Is the test single-purpose?
FOR TEST NAMES:
□ "should [expected] when [condition]"
□ Describes behavior, not implementation
□ No cryptic abbreviations
□ Clear scenario
Good tests explain how the system should behave.
The test name should summarize the test completely.
Anyone should be able to scan and find tests they need.
Read the assertion, understand what was checked.
Readers shouldn't have to hold context in their head.
test-think — Determines what to testintelligent-test-creation — Creates readable testssetup-test-data — Data setup affects readabilitygenerate-mock — Mock setup affects readabilitydebug-test — Unclear tests cause debugging difficultydevelopment
Use this skill whenever the user wants to create, read, edit, or manipulate Word documents (.docx files). Triggers include: any mention of 'Word doc', 'word document', '.docx', or requests to produce professional documents with formatting like tables of contents, headings, page numbers, or letterheads. Also use when extracting or reorganizing content from .docx files, inserting or replacing images in documents, performing find-and-replace in Word files, working with tracked changes or comments, or converting content into a polished Word document. If the user asks for a 'report', 'memo', 'letter', 'template', or similar deliverable as a Word or .docx file, use this skill. Do NOT use for PDFs, spreadsheets, Google Docs, or general coding tasks unrelated to document generation.
devops
Create a new implementation plan file for new features, refactoring existing code or upgrading packages, design, architecture or infrastructure.
tools
Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).
documentation
Generates standardized porting documentation from completed feature changes. Analyzes commit diffs or file contents, extracts change intent, and outputs Markdown documentation for cross-team understanding. Should be used when the user needs to document a change for cross-team or cross-project consumption. Distinguished from cross-branch-fix-porter which actively re-implements fixes, this skill documents changes.