testing-strategies
3
总安装量
2
周安装量
#54732
全站排名
安装命令
npx skills add https://github.com/sunnypatneedi/claude-starter-kit --skill testing-strategies
Agent 安装分布
mcpjam
2
neovate
2
gemini-cli
2
antigravity
2
windsurf
2
zencoder
2
Skill 文档
Testing Strategies
Complete framework for building effective, maintainable test suites that give you confidence to ship.
When to Use
- Setting up testing for new projects
- Improving existing test coverage
- Practicing Test-Driven Development (TDD)
- Debugging flaky tests
- Deciding what to test (and what not to)
- Organizing test files and structure
Core Testing Principles
Test Behavior, Not Implementation:
- Test what the code does, not how
- Tests should survive refactoring
- Focus on public interfaces
Testing Pyramid:
/\
/ \ E2E (Few, slow, comprehensive)
/----\
/ \ Integration (Some, medium)
/--------\
/ \ Unit (Many, fast, isolated)
/------------\
Write Tests That Fail Usefully:
- Clear error messages
- Pinpoint failure location
- Show expected vs actual
Workflow
Step 1: Choose the Right Test Level
Unit Tests (70% of tests):
WHEN TO USE:
- Business logic
- Data transformations
- Validation rules
- Pure functions
- Edge cases
CHARACTERISTICS:
- Fast (<100ms each)
- Isolated (no external dependencies)
- Many tests
- Run on every commit
Integration Tests (20% of tests):
WHEN TO USE:
- API endpoints
- Database operations
- External service integration
- Multiple components working together
CHARACTERISTICS:
- Medium speed (100ms-1s each)
- Real dependencies (test DB, APIs)
- Fewer than unit tests
- Run before merge
E2E Tests (10% of tests):
WHEN TO USE:
- Critical user journeys
- Complete workflows
- Cross-browser compatibility
CHARACTERISTICS:
- Slow (seconds per test)
- Full application stack
- Fewest tests
- Run before release
Step 2: Write Unit Tests (AAA Pattern)
Arrange-Act-Assert:
describe('UserService', () => {
describe('createUser', () => {
it('creates a user with valid data', async () => {
// ARRANGE: Set up test data and dependencies
const userData = {
email: 'test@example.com',
name: 'Test User'
};
const mockRepo = {
save: jest.fn().mockResolvedValue({ id: '1', ...userData })
};
const service = new UserService(mockRepo);
// ACT: Execute the code under test
const result = await service.createUser(userData);
// ASSERT: Verify the outcome
expect(result).toEqual({ id: '1', ...userData });
expect(mockRepo.save).toHaveBeenCalledWith(userData);
});
it('throws validation error for invalid email', async () => {
// ARRANGE
const userData = { email: 'invalid', name: 'Test' };
const service = new UserService(mockRepo);
// ACT & ASSERT
await expect(service.createUser(userData))
.rejects.toThrow('Invalid email format');
});
});
});
What to Unit Test:
â
DO TEST:
- Business logic
- Data transformations
- Validation rules
- Edge cases (null, empty, max values)
- Error conditions
- Pure functions
â DON'T TEST:
- Third-party libraries
- Simple getters/setters
- Framework code
- Configuration
- Trivial code
Mocking Guidelines:
// â
GOOD: Mock external dependencies
const mockEmailService = {
send: jest.fn().mockResolvedValue(true)
};
const mockDatabase = {
query: jest.fn().mockResolvedValue([{ id: 1 }])
};
// â
GOOD: Use dependency injection
class UserService {
constructor(private db: Database, private email: EmailService) {}
async createUser(data) {
const user = await this.db.save(data);
await this.email.send(user.email, 'Welcome!');
return user;
}
}
// In test:
const service = new UserService(mockDb, mockEmail);
// â BAD: Mocking internal implementation details
// This couples tests to implementation
Step 3: Write Integration Tests
API Integration Tests:
describe('POST /api/users', () => {
let db: Database;
beforeAll(async () => {
db = await createTestDatabase();
});
afterAll(async () => {
await db.close();
});
beforeEach(async () => {
await db.query('DELETE FROM users');
});
it('creates a user and returns 201', async () => {
const response = await request(app)
.post('/api/users')
.send({ email: 'test@example.com', name: 'Test' })
.expect(201);
expect(response.body).toMatchObject({
id: expect.any(String),
email: 'test@example.com'
});
// Verify in database
const user = await db.users.findOne({ email: 'test@example.com' });
expect(user).toBeDefined();
});
it('returns 400 for invalid data', async () => {
const response = await request(app)
.post('/api/users')
.send({ email: 'invalid' })
.expect(400);
expect(response.body.error).toBe('Invalid email format');
});
it('returns 409 for duplicate email', async () => {
await createUser({ email: 'test@example.com' });
await request(app)
.post('/api/users')
.send({ email: 'test@example.com', name: 'Test' })
.expect(409);
});
});
Step 4: Write E2E Tests (Page Object Pattern)
Page Object:
// pages/LoginPage.ts
class LoginPage {
constructor(private page: Page) {}
async navigate() {
await this.page.goto('/login');
}
async login(email: string, password: string) {
await this.page.fill('[data-testid="email"]', email);
await this.page.fill('[data-testid="password"]', password);
await this.page.click('[data-testid="submit"]');
}
async getErrorMessage() {
return this.page.textContent('[data-testid="error"]');
}
}
// tests/login.spec.ts
describe('Login', () => {
it('logs in successfully with valid credentials', async () => {
const loginPage = new LoginPage(page);
await loginPage.navigate();
await loginPage.login('user@example.com', 'password');
await expect(page).toHaveURL('/dashboard');
});
it('shows error for invalid credentials', async () => {
const loginPage = new LoginPage(page);
await loginPage.navigate();
await loginPage.login('user@example.com', 'wrong');
const error = await loginPage.getErrorMessage();
expect(error).toBe('Invalid credentials');
});
});
E2E Best Practices:
â
DO:
- Use data-testid attributes
- Test user journeys, not features
- Run in realistic environment
- Handle async properly
- Clean up test data
â DON'T:
- Test every edge case with E2E
- Use brittle selectors (.class-name-xyz)
- Share state between tests
- Skip cleanup
- Run E2E on every commit (too slow)
Step 5: Test-Driven Development (TDD)
Red-Green-Refactor Cycle:
1. RED: Write a failing test
âââ Test describes desired behavior
2. GREEN: Write minimal code to pass
âââ Just enough to make test pass
3. REFACTOR: Clean up
âââ Improve code while tests pass
TDD Example:
// Step 1: RED - Write failing test
describe('Cart', () => {
it('calculates total price with discount', () => {
const cart = new Cart();
cart.addItem({ price: 100, quantity: 2 });
cart.applyDiscount(10); // 10%
expect(cart.total).toBe(180); // (100 * 2) - 10%
});
});
// Run test: FAILS (Cart doesn't exist yet)
// Step 2: GREEN - Minimal implementation
class Cart {
items = [];
discount = 0;
addItem(item) {
this.items.push(item);
}
applyDiscount(percent) {
this.discount = percent;
}
get total() {
const subtotal = this.items.reduce(
(sum, i) => sum + i.price * i.quantity,
0
);
return subtotal * (1 - this.discount / 100);
}
}
// Run test: PASSES
// Step 3: REFACTOR - Clean up
class Cart {
private items: CartItem[] = [];
private discountPercent: number = 0;
addItem(item: CartItem): void {
this.items.push(item);
}
applyDiscount(percent: number): void {
this.discountPercent = percent;
}
get total(): number {
const subtotal = this.calculateSubtotal();
return this.applyDiscountToSubtotal(subtotal);
}
private calculateSubtotal(): number {
return this.items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
}
private applyDiscountToSubtotal(subtotal: number): number {
return subtotal * (1 - this.discountPercent / 100);
}
}
// Run test: STILL PASSES (refactoring didn't break anything)
Step 6: Test Coverage
Coverage Metrics:
LINE COVERAGE: % of lines executed
âââ Easy to game
âââ Doesn't ensure quality
BRANCH COVERAGE: % of decision branches
âââ Better than line coverage
âââ Catches missing else cases
MUTATION TESTING: % of mutations caught
âââ Introduces bugs, checks if tests catch them
âââ High quality metric
âââ Slow to run
Coverage Guidelines:
## Coverage Targets
| Type | Target | Priority |
|------|--------|----------|
| Unit | 80% | Business logic |
| Integration | 60% | API endpoints |
| E2E | Key flows | User journeys |
### What Must Be Tested
- [ ] All public API functions
- [ ] All error handling paths
- [ ] Business rule edge cases
- [ ] Security-sensitive code
### What Can Be Skipped
- Configuration files
- Generated code
- Simple getters/setters
- Third-party wrappers
Test Organization
File Structure:
src/
âââ users/
â âââ user.service.ts
â âââ user.service.test.ts # Unit tests
â âââ user.repository.ts
â âââ user.repository.test.ts
âââ __tests__/
â âââ integration/
â âââ user.api.test.ts # Integration tests
tests/
âââ e2e/
â âââ login.spec.ts # E2E tests
â âââ checkout.spec.ts
âââ fixtures/
â âââ users.json # Test data
âââ setup.ts # Test configuration
Naming Conventions:
// Describe the unit being tested
describe('UserService', () => {
// Describe the method
describe('createUser', () => {
// Describe the scenario
describe('when email is valid', () => {
// State what it does
it('creates a new user', () => {});
it('sends welcome email', () => {});
});
describe('when email already exists', () => {
it('throws DuplicateEmailError', () => {});
});
});
});
Test Quality Checklist
## Test Review Checklist
### Structure
- [ ] Tests follow AAA pattern
- [ ] One assertion concept per test
- [ ] Descriptive test names
- [ ] Tests are independent
- [ ] No test interdependence
### Coverage
- [ ] Happy path tested
- [ ] Error cases tested
- [ ] Edge cases tested
- [ ] Boundary conditions tested
### Maintainability
- [ ] No flaky tests
- [ ] Setup/teardown is minimal
- [ ] Mocks are at boundaries
- [ ] Tests survive refactoring
### Performance
- [ ] Unit tests <100ms each
- [ ] Integration tests reasonable
- [ ] E2E tests focused
- [ ] Parallelization enabled
Common Testing Mistakes
| Don’t | Do |
|---|---|
| Test implementation details | Test public behavior |
| One assertion per test (too rigid) | One concept per test |
| Copy-paste tests | Use test helpers/factories |
| Share state between tests | Isolate each test |
| Skip edge cases | Test boundaries and errors |
| Mock everything | Mock at boundaries only |
| Ignore flaky tests | Fix or delete flaky tests |
| Write tests after code (always) | Use TDD when beneficial |
Tools & Frameworks
JavaScript/TypeScript:
- Jest (unit + integration)
- Vitest (fast Jest alternative)
- Playwright (E2E)
- Cypress (E2E)
- Testing Library (React/Vue/etc)
Python:
- pytest (unit + integration)
- unittest (standard library)
- Selenium (E2E)
Ruby:
- RSpec (unit + integration)
- Capybara (E2E)
Go:
- testing package (standard library)
- testify (assertions)
Related Skills
/code-review– Reviewing test quality/devops-cicd– Running tests in CI/CD/debugging– Using tests to find bugs
Last Updated: 2026-01-22