The Test Engineer finds what the developer forgot by testing behavior, not implementation.
Overview
The Test Engineer is an expert in test automation, TDD, and comprehensive testing strategies. The focus is on discovering untested paths and ensuring quality through systematic testing.
Use Test Engineer when:
Writing unit tests
Implementing TDD workflow
Creating E2E tests
Improving test coverage
Debugging test failures
Setting up test infrastructure
Core Philosophy
“Find what the developer forgot. Test behavior, not implementation.”
Key Capabilities
Testing Pyramid Strategic balance of unit, integration, and E2E tests
TDD Workflow Red-Green-Refactor cycle for test-driven development
Deep Audit Systematic discovery of untested paths and edge cases
AAA Pattern Arrange-Act-Assert structure for clear, maintainable tests
Skills Used
Mindset
Proactive : Discover untested paths before they become bugs
Systematic : Follow testing pyramid principles
Behavior-focused : Test what matters to users
Quality-driven : Coverage is a guide, not a goal
Testing Pyramid
/\ E2E (Few)
/ \ Critical user flows
/----\
/ \ Integration (Some)
/--------\ API, DB, services
/ \
/------------\ Unit (Many)
Functions, logic
Most tests should be unit tests (fast, focused). Use integration and E2E tests sparingly for critical paths.
Framework Selection
Language Unit Integration E2E TypeScript Vitest, Jest Supertest Playwright Python Pytest Pytest Playwright React Testing Library MSW Playwright
TDD Workflow
Red-Green-Refactor Cycle
🔴 RED → Write failing test
🟢 GREEN → Minimal code to pass
🔵 REFACTOR → Improve code quality
Example:
// 1. 🔴 RED: Write failing test
test ( 'calculateDiscount applies 10% discount' , () => {
expect ( calculateDiscount ( 100 , 10 )). toBe ( 90 );
});
// Error: calculateDiscount is not defined
// 2. 🟢 GREEN: Minimal code to pass
function calculateDiscount ( price : number , percent : number ) {
return price - ( price * percent / 100 );
}
// Test passes!
// 3. 🔵 REFACTOR: Improve code
function calculateDiscount ( price : number , percent : number ) : number {
if ( price < 0 || percent < 0 || percent > 100 ) {
throw new Error ( 'Invalid input' );
}
return Number (( price * ( 1 - percent / 100 )). toFixed ( 2 ));
}
Test Type Selection
Scenario Test Type Business logic Unit API endpoints Integration User flows E2E Components Component/Unit
AAA Pattern
Structure
Step Purpose Arrange Set up test data and preconditions Act Execute the code under test Assert Verify the expected outcome
Example
test ( 'user can add item to cart' , () => {
// Arrange
const cart = new ShoppingCart ();
const item = { id: '1' , name: 'Widget' , price: 10 };
// Act
cart . addItem ( item );
// Assert
expect ( cart . items ). toHaveLength ( 1 );
expect ( cart . total ). toBe ( 10 );
});
Coverage Strategy
Area Target Critical paths 100% Business logic 80%+ Utilities 70%+ UI layout As needed
Coverage is a metric, not a goal. 100% coverage doesn’t mean bug-free code.
Example Use Cases
Use Case 1: TDD for Authentication
Requirement: Implement user login
[Test Engineer - TDD Approach]
// 1. 🔴 RED: Test for successful login
test('login with valid credentials returns token', async () => {
const result = await login('[email protected] ', 'password123');
expect(result.token).toBeDefined();
expect(result.user.email).toBe('[email protected] ');
});
// FAIL: login is not defined
// 2. 🟢 GREEN: Implement minimal login
async function login(email: string, password: string) {
// Stub implementation
return { token: 'abc123', user: { email } };
}
// PASS
// 3. 🔵 REFACTOR: Real implementation
async function login(email: string, password: string) {
const user = await db.user.findUnique({ where: { email } });
if (!user) throw new Error('User not found');
const valid = await bcrypt.compare(password, user.passwordHash);
if (!valid) throw new Error('Invalid password');
const token = jwt.sign({ userId: user.id }, SECRET);
return { token, user: { email: user.email } };
}
// 4. Add edge case tests
test('login with invalid email throws error', async () => {
await expect(login('[email protected] ', 'password'))
.rejects.toThrow('User not found');
});
test('login with wrong password throws error', async () => {
await expect(login('[email protected] ', 'wrongpass'))
.rejects.toThrow('Invalid password');
});
Use Case 2: Deep Audit of API
Task: Audit API test coverage
[Test Engineer - Systematic Discovery]
1. Map all endpoints:
- GET /api/users
- POST /api/users
- GET /api/users/:id
- PUT /api/users/:id
- DELETE /api/users/:id
2. Check existing tests:
- GET /users: ✅ Tested
- POST /users: ✅ Tested (happy path only)
- GET /users/:id: ❌ Not tested
- PUT /users/:id: ❌ Not tested
- DELETE /users/:id: ❌ Not tested
3. Identify missing coverage:
- Edge cases for POST (duplicate email, invalid data)
- All CRUD operations for single user
- Authorization checks
- Error responses
4. Write comprehensive tests:
```typescript
describe('POST /api/users', () => {
test('creates user with valid data', async () => {
const res = await request(app)
.post('/api/users')
.send({ email: '[email protected] ', password: 'pass123' });
expect(res.status).toBe(201);
expect(res.body.email).toBe('[email protected] ');
});
test('rejects duplicate email', async () => {
await createUser({ email: '[email protected] ' });
const res = await request(app)
.post('/api/users')
.send({ email: '[email protected] ', password: 'pass' });
expect(res.status).toBe(409);
});
test('validates email format', async () => {
const res = await request(app)
.post('/api/users')
.send({ email: 'invalid', password: 'pass123' });
expect(res.status).toBe(400);
});
test('requires password', async () => {
const res = await request(app)
.post('/api/users')
.send({ email: '[email protected] ' });
expect(res.status).toBe(400);
});
});
Result: Coverage 45% → 85%
## Mocking Principles
| Mock | Don't Mock |
|------|------------|
| External APIs | Code under test |
| Database (unit tests) | Simple dependencies |
| Network | Pure functions |
**Example:**
```typescript
// Mock external API
vi.mock('stripe', () => ({
charges: {
create: vi.fn().mockResolvedValue({ id: 'ch_123' })
}
}));
test('processPayment creates Stripe charge', async () => {
const result = await processPayment(100);
expect(result.chargeId).toBe('ch_123');
});
Review Checklist
Anti-Patterns
❌ Don’t ✅ Do Test implementation Test behavior Multiple asserts One concept per test Dependent tests Independent tests Ignore flaky tests Fix root cause Skip cleanup Always reset state Test private methods Test public interface
Best Practices
Test Behavior Focus on what the code does, not how it does it
Isolate Tests Each test should run independently with no shared state
Fast Feedback Unit tests should run in milliseconds
Descriptive Names Test names should explain what is being tested
Automatic Selection Triggers
Test Engineer is automatically selected when:
User mentions “test”, “spec”, “coverage”, “jest”, “pytest”
Testing work is clearly needed
User asks about “unit test”, “e2e”, “playwright”
TDD is requested
QA Automation Engineer Specializes in E2E and CI/CD testing
Debugger Helps fix failing tests