Test pyramid strategy, test design patterns, coverage analysis, and quality gate configuration. Use when designing test strategies, improving coverage, setting up automation, or defining quality gates. Covers unit, integration, and E2E testing across frameworks.
/plugin marketplace add rsmdt/the-startup/plugin install team@the-startupThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Systematic patterns for designing comprehensive test strategies that balance coverage, speed, and maintainability.
The test pyramid guides the distribution of testing effort:
┌─────────┐
│ E2E │ ~10% - Critical user journeys
│ Tests │ Slow, expensive, high confidence
├─────────┤
│ Integr- │ ~20% - Component interactions
│ ation │ Medium speed, real dependencies
├─────────┤
│ Unit │ ~70% - Business logic
│ Tests │ Fast, isolated, cheap
└─────────┘
| Layer | Speed | Isolation | Confidence | Maintenance |
|---|---|---|---|---|
| Unit | < 10ms | Complete | Low per test | Low |
| Integration | < 1s | Partial | Medium | Medium |
| E2E | < 30s | None | High | High |
Unit Tests - Test here when:
Integration Tests - Test here when:
E2E Tests - Test here when:
// Arrange: Set up test data and dependencies
const user = createUser({ email: 'test@example.com' });
const validator = new EmailValidator();
// Act: Execute the behavior under test
const result = validator.validate(user.email);
// Assert: Verify the expected outcome
expect(result.isValid).toBe(true);
Focus each test on a single behavior:
// Bad: Multiple unrelated assertions
test('user validation', () => {
expect(user.isValid()).toBe(true);
expect(user.email).toContain('@');
expect(user.createdAt).toBeDefined();
});
// Good: Separate tests for each behavior
test('validates user with proper email', () => {
expect(user.isValid()).toBe(true);
});
test('email contains @ symbol', () => {
expect(user.email).toContain('@');
});
Test names should describe the expected behavior:
// Bad
test('email test', () => { ... });
// Good
test('rejects email without domain', () => { ... });
test('accepts email with subdomain', () => { ... });
test('trims whitespace from email input', () => { ... });
Create readable test data:
// Builder pattern for test data
const user = UserBuilder.create()
.withEmail('test@example.com')
.withRole('admin')
.withActiveSubscription()
.build();
// Bad: Testing internal implementation
test('repository uses correct SQL', () => {
const sql = userRepo.buildQuery();
expect(sql).toContain('SELECT * FROM users');
});
// Good: Testing behavior through public API
test('finds users by email domain', async () => {
await userRepo.save(createUser({ email: 'a@corp.com' }));
await userRepo.save(createUser({ email: 'b@corp.com' }));
const users = await userRepo.findByDomain('corp.com');
expect(users).toHaveLength(2);
});
// Prefer real database for repository tests
beforeAll(async () => {
testDb = await createTestDatabase();
});
afterEach(async () => {
await testDb.truncateAll();
});
// Mock only external services
jest.mock('./paymentGateway', () => ({
charge: jest.fn().mockResolvedValue({ success: true })
}));
Verify API contracts between services:
// Consumer contract
describe('User API Contract', () => {
test('GET /users/:id returns user shape', async () => {
const response = await api.get('/users/123');
expect(response).toMatchSchema({
id: expect.any(String),
email: expect.any(String),
createdAt: expect.any(String),
});
});
});
Only test the most important user journeys:
// Priority 1: Revenue-critical paths
test('complete purchase flow', async () => {
await page.addToCart(product);
await page.checkout();
await page.enterPayment(validCard);
await page.confirmOrder();
await expect(page.orderConfirmation).toBeVisible();
});
// Priority 2: Core functionality
test('user registration and login', async () => {
await page.register(newUser);
await page.login(newUser);
await expect(page.dashboard).toBeVisible();
});
Encapsulate page interactions:
class CheckoutPage {
constructor(page) {
this.page = page;
}
async enterShippingAddress(address) {
await this.page.fill('#street', address.street);
await this.page.fill('#city', address.city);
await this.page.fill('#zip', address.zip);
}
async submitOrder() {
await this.page.click('[data-testid="submit-order"]');
await this.page.waitForURL('**/confirmation');
}
}
Use stable selectors that survive UI changes:
// Fragile: Based on CSS/position
await page.click('.btn-primary');
await page.click('div > div:nth-child(2) > button');
// Stable: Based on test IDs or roles
await page.click('[data-testid="submit-button"]');
await page.getByRole('button', { name: 'Submit' });
| Type | Measures | Target |
|---|---|---|
| Line | Lines executed | 80% |
| Branch | Decision paths taken | 75% |
| Function | Functions called | 90% |
| Statement | Statements executed | 80% |
Focus coverage on high-risk areas:
High Priority (100% coverage):
├── Payment processing
├── Authentication/Authorization
├── Data validation
├── Business rule enforcement
└── Security-sensitive code
Medium Priority (80% coverage):
├── Core business logic
├── API endpoints
├── Data transformations
└── Error handling
Lower Priority (60% coverage):
├── UI components
├── Configuration
├── Logging/Monitoring
└── Admin features
Coverage quantity vs quality:
// 100% coverage, low value
test('getter returns value', () => {
user.name = 'John';
expect(user.name).toBe('John');
});
// 80% coverage, high value
test('rejects order when inventory insufficient', () => {
const inventory = createInventory({ quantity: 5 });
const order = createOrder({ quantity: 10 });
expect(() => processOrder(order, inventory))
.toThrow('Insufficient inventory');
});
| Gate | Threshold | Enforcement |
|---|---|---|
| Unit Tests | 100% pass | Block merge |
| Integration Tests | 100% pass | Block merge |
| E2E Tests | 100% pass | Block deploy |
| Line Coverage | ≥ 80% | Block merge |
| Branch Coverage | ≥ 75% | Block merge |
| New Code Coverage | ≥ 90% | Warn |
| Test Duration | < 10 min | Warn |
Flaky Test Protocol:
1. DETECT: Track test stability over time
2. QUARANTINE: Move flaky tests to separate suite
3. FIX: Prioritize fixing within 1 week
4. REINTEGRATE: Return to main suite after 10 consecutive passes
| Test Type | Target Duration | Action if Exceeded |
|---|---|---|
| Single Unit Test | < 10ms | Investigate |
| Unit Suite | < 30s | Parallelize |
| Single Integration | < 1s | Review setup/teardown |
| Integration Suite | < 2 min | Parallelize |
| Single E2E | < 30s | Optimize waits |
| E2E Suite | < 10 min | Reduce scope |
src/
├── user/
│ ├── User.ts
│ ├── User.test.ts # Unit tests co-located
│ ├── UserRepository.ts
│ └── UserRepository.test.ts
├── order/
│ ├── Order.ts
│ └── Order.test.ts
tests/
├── integration/
│ ├── api/
│ │ └── users.test.ts # API integration tests
│ └── database/
│ └── repositories.test.ts
└── e2e/
├── checkout.spec.ts # E2E tests by journey
└── registration.spec.ts
# Unit tests: [unit].test.ts
User.test.ts
OrderCalculator.test.ts
# Integration tests: [feature].integration.test.ts
users.integration.test.ts
payment-gateway.integration.test.ts
# E2E tests: [journey].e2e.ts or [journey].spec.ts
checkout.e2e.ts
user-onboarding.spec.ts
// Factory: Dynamic test data
const createUser = (overrides = {}) => ({
id: faker.string.uuid(),
email: faker.internet.email(),
name: faker.person.fullName(),
...overrides
});
// Fixture: Static reference data
const ADMIN_USER = {
id: 'admin-001',
email: 'admin@test.com',
role: 'admin'
};
// Transaction rollback pattern
beforeEach(async () => {
transaction = await db.beginTransaction();
});
afterEach(async () => {
await transaction.rollback();
});
// Truncate pattern (slower but more isolated)
afterEach(async () => {
await db.truncateAll();
});
test:
stages:
- lint-and-typecheck # Fast feedback first
- unit-tests # Parallel execution
- integration-tests # With test database
- e2e-tests # Against staging environment
parallelization:
unit: 4 workers
integration: 2 workers
e2e: 1 worker (sequential for stability)
artifacts:
- coverage reports
- test results (JUnit XML)
- E2E screenshots/videos on failure
On Test Failure:
1. Capture: Screenshots, logs, database state
2. Report: Notify team channel
3. Block: Prevent merge/deploy
4. Retry: Once for E2E (flaky detection)
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.