16 KiB
name, description
| name | description |
|---|---|
| systematic-testing | Use when writing tests, debugging failures, or investigating bugs - provides TDD guidance, test generation patterns, systematic debugging framework. Activates when user says "write tests", "this test fails", "debug this", mentions "TDD", "test coverage", or encounters errors/bugs. |
Systematic Testing Skill
Purpose
Guide test-driven development (TDD), generate comprehensive test suites, and provide systematic debugging frameworks. Ensures code is well-tested and bugs are resolved methodically rather than through trial-and-error.
Activation Triggers
Activate this skill when:
- User implements new functionality (auto-suggest tests)
- Tests fail (activate debugging framework)
- User says "write tests for this"
- User mentions "TDD" or "test-driven"
- User asks about debugging or troubleshooting
- User says "this bug..." or "error..."
- Before marking feature complete (verify test coverage)
Core Capabilities
1. Test-Driven Development (TDD)
For complete TDD workflow, use Skill tool to invoke: dev-workflow:test-driven-development
How to activate:
Use Skill tool: Skill(skill: "dev-workflow:test-driven-development")
The test-driven-development skill provides:
- Full RED-GREEN-REFACTOR cycle explanation
- Strict TDD enforcement and discipline
- Anti-patterns and rationalizations to avoid
- Detailed examples and verification checklist
Quick TDD Summary:
- RED: Write failing test first
- GREEN: Write minimal code to pass
- REFACTOR: Clean up while tests stay green
This skill focuses on test generation strategies and systematic debugging. For TDD methodology details, use the dedicated skill.
2. Test Generation
Goal: Generate comprehensive test suites covering all scenarios
Test Categories
Normal Cases (Happy Path)
Test expected, typical usage:
describe('calculatePositionSize', () => {
it('should calculate correct position size for valid inputs', () => {
const result = calculatePositionSize(
10000, // account balance
0.02, // 2% risk
150.50, // entry price
148.00 // stop loss
);
expect(result).toBe(80); // $200 risk / $2.50 per share
});
it('should handle large account balances', () => {
const result = calculatePositionSize(
1000000, // $1M account
0.01, // 1% risk
100,
99
);
expect(result).toBe(10000);
});
});
Edge Cases (Boundary Conditions)
Test limits and boundaries:
describe('calculatePositionSize - edge cases', () => {
it('should handle very small risk percentage', () => {
const result = calculatePositionSize(
10000,
0.001, // 0.1% risk
100,
99
);
expect(result).toBe(10); // $10 risk / $1 per share
});
it('should handle entry price equal to stop loss', () => {
expect(() => {
calculatePositionSize(10000, 0.02, 100, 100);
}).toThrow('Entry price cannot equal stop loss');
});
it('should handle very small price differences', () => {
const result = calculatePositionSize(
10000,
0.02,
100.10,
100.00
);
expect(result).toBe(2000); // $200 / $0.10
});
it('should round down fractional shares', () => {
const result = calculatePositionSize(
10000,
0.02,
150.75, // Creates fractional result
148.00
);
// Should be whole number, not fractional
expect(Number.isInteger(result)).toBe(true);
});
});
Error Cases (Invalid Inputs)
Test error handling:
describe('calculatePositionSize - error cases', () => {
it('should reject negative account balance', () => {
expect(() => {
calculatePositionSize(-10000, 0.02, 100, 99);
}).toThrow('Account balance must be positive');
});
it('should reject zero account balance', () => {
expect(() => {
calculatePositionSize(0, 0.02, 100, 99);
}).toThrow('Account balance must be positive');
});
it('should reject risk percentage over 100%', () => {
expect(() => {
calculatePositionSize(10000, 1.5, 100, 99);
}).toThrow('Risk percentage must be between 0 and 1');
});
it('should reject negative risk percentage', () => {
expect(() => {
calculatePositionSize(10000, -0.02, 100, 99);
}).toThrow('Risk percentage must be between 0 and 1');
});
it('should handle null inputs gracefully', () => {
expect(() => {
calculatePositionSize(null, 0.02, 100, 99);
}).toThrow();
});
it('should handle undefined inputs gracefully', () => {
expect(() => {
calculatePositionSize(undefined, 0.02, 100, 99);
}).toThrow();
});
});
Integration Cases
Test component interactions:
describe('login flow - integration', () => {
it('should complete full authentication flow', async () => {
// Test entire flow from request to response
const response = await request(app)
.post('/api/auth/login')
.send({
email: 'user@example.com',
password: 'SecureP@ss123'
});
// Verify response
expect(response.status).toBe(200);
expect(response.body.data.accessToken).toBeTruthy();
// Verify token is valid
const decoded = jwt.verify(
response.body.data.accessToken,
process.env.JWT_SECRET
);
expect(decoded.id).toBeTruthy();
// Verify user can access protected route
const protectedResponse = await request(app)
.get('/api/user/profile')
.set('Authorization', `Bearer ${response.body.data.accessToken}`);
expect(protectedResponse.status).toBe(200);
});
it('should prevent access with expired token', async () => {
// Create expired token
const expiredToken = jwt.sign(
{ id: 'user-id' },
process.env.JWT_SECRET,
{ expiresIn: '-1h' } // Already expired
);
// Attempt to access protected route
const response = await request(app)
.get('/api/user/profile')
.set('Authorization', `Bearer ${expiredToken}`);
expect(response.status).toBe(401);
expect(response.body.error.code).toBe('TOKEN_EXPIRED');
});
});
Test Generation Template
describe('[Component/Function Name]', () => {
// Setup and teardown
beforeEach(() => {
// Reset state, create test data
});
afterEach(() => {
// Clean up, reset mocks
});
// NORMAL CASES
describe('normal operation', () => {
it('should [expected behavior for typical input]', () => {
// Test implementation
});
});
// EDGE CASES
describe('edge cases', () => {
it('should handle [boundary condition]', () => {
// Test implementation
});
});
// ERROR CASES
describe('error handling', () => {
it('should reject [invalid input]', () => {
expect(() => {
// Call with invalid input
}).toThrow('Expected error message');
});
});
// INTEGRATION
describe('integration', () => {
it('should work with [other component]', () => {
// Test interaction
});
});
});
3. Systematic Debugging Framework
Goal: Resolve bugs methodically, not through random trial-and-error
Four-Phase Framework:
Phase 1: Root Cause Investigation
Goal: Understand exactly what's going wrong
Process:
-
Reproduce Bug Consistently
Steps to reproduce: 1. Navigate to login page 2. Enter email: user@example.com 3. Enter password: TestPass123 4. Click submit 5. Observe: Error message "Network request failed" Reproducibility: 10/10 attempts failed -
Identify Symptoms
- What's the visible error?
- What error messages appear?
- What's the expected vs actual behavior?
-
Gather Evidence
# Check logs tail -f logs/app.log # Check network requests (browser dev tools) # Check console errors # Check server logsCollect:
- Error messages (full text)
- Stack traces
- Log entries
- Network requests/responses
- Input values that trigger bug
-
Form Initial Hypothesis
Hypothesis: API endpoint is returning 500 error instead of 400 Evidence: - Console shows "Network request failed" - Server logs show 500 error at time of attempt - Database logs show constraint violation Next step: Check what's causing the 500
Phase 2: Pattern Analysis
Goal: Identify when bug occurs and when it doesn't
Questions to Answer:
-
When Does It Fail?
- Specific inputs?
- Certain users?
- Particular time of day?
- After specific sequence of actions?
-
When Does It Work?
- Any inputs that succeed?
- Any users unaffected?
- Worked before, broken now?
-
What Changed Recently?
# Check recent commits git log --since="2 days ago" --oneline # Check what changed in specific file git log -p path/to/file.js # See when bug was introduced git bisect -
Environmental Factors?
- Works locally, fails in production?
- Browser-specific?
- Network conditions?
- Load-related (works with 1 user, fails with 100)?
Pattern Analysis Example:
Bug Pattern Analysis:
FAILS when:
- Email contains + symbol (e.g., user+test@example.com)
- Password is exactly 8 characters
- User account was created after 2025-01-01
WORKS when:
- Email is standard format (user@example.com)
- Password is 9+ characters
- User account is older
Pattern identified: Email validation regex doesn't handle + symbol
Phase 3: Hypothesis Testing
Goal: Test theories systematically until root cause found
Process:
-
Create Minimal Test Case
// Isolate the bug it('should accept email with plus symbol', () => { const email = 'user+test@example.com'; const result = validateEmail(email); expect(result).toBe(true); // Currently fails }); -
Add Instrumentation
function validateEmail(email) { console.log('Input email:', email); const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const isValid = regex.test(email); console.log('Regex test result:', isValid); console.log('Regex used:', regex); return isValid; } -
Test Hypothesis
// Hypothesis: Regex doesn't handle + symbol // Test with minimal input validateEmail('user+test@example.com'); // Output: // Input email: user+test@example.com // Regex test result: true // // Hypothesis REJECTED: Regex accepts + symbol // Form new hypothesis: Something else is rejecting it // Check database constraint // Found: Database column has CHECK constraint excluding + // Hypothesis CONFIRMED! -
Iterate Until Root Cause Found Keep forming and testing hypotheses until you identify exact cause.
Phase 4: Implementation
Goal: Fix bug permanently with regression protection
Process:
-
Write Test Reproducing Bug
// This test should FAIL before fix it('should accept email with plus symbol', async () => { const email = 'user+test@example.com'; const password = 'SecureP@ss123'; const response = await request(app) .post('/api/auth/register') .send({ email, password }); expect(response.status).toBe(201); expect(response.body.user.email).toBe(email); });Run test - verify it fails:
npm test # ✗ should accept email with plus symbol # Expected: 201, Received: 400 -
Fix the Bug
-- Remove overly restrictive database constraint ALTER TABLE users DROP CONSTRAINT email_format_check; -- Or update to correct constraint ALTER TABLE users ADD CONSTRAINT email_format_check CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}$'); -
Verify Test Passes
npm test # ✓ should accept email with plus symbol -
Add Regression Tests
// Add more tests for similar edge cases describe('email validation - special characters', () => { const validEmails = [ 'user+test@example.com', 'user.name@example.com', 'user_name@example.com', 'user-name@example.com', 'user123@example.com' ]; validEmails.forEach(email => { it(`should accept ${email}`, async () => { const response = await request(app) .post('/api/auth/register') .send({ email, password: 'SecureP@ss123' }); expect(response.status).toBe(201); }); }); }); -
Document Root Cause
/* * Bug Fix: Email validation now accepts RFC-compliant characters * * Root Cause: * Database CHECK constraint was too restrictive, excluding the + symbol * which is valid in email addresses per RFC 5322. * * Fix: * Updated CHECK constraint to use proper regex pattern matching RFC 5322. * * Date: 2025-01-17 * Related Issue: #234 */ -
Commit Fix with Context
git add migrations/003_fix_email_constraint.sql git add tests/auth/register.test.js git commit -m "fix: Accept + symbol in email addresses Database CHECK constraint was rejecting valid emails containing the + symbol. Updated constraint to match RFC 5322 standard. Closes #234"
Test Coverage Analysis
Goal: Identify untested code
Check Coverage:
# Generate coverage report
npm run test:coverage
# Typical output:
# File | % Stmts | % Branch | % Funcs | % Lines |
# ---------------|---------|----------|---------|---------|
# src/auth.js | 87.5 | 75.0 | 100.0 | 87.5 |
# src/orders.js | 45.2 | 33.3 | 60.0 | 45.2 |
Identify Gaps:
- Functions without any tests
- Branches not covered (if statements, error paths)
- Edge cases not tested
- Integration paths missing
Prioritize:
- Critical business logic (highest priority)
- Security-sensitive code
- Complex algorithms
- Error handling paths
- Edge cases
Best Practices
TDD Best Practices
- Always write test first: No exceptions
- One test at a time: Don't write multiple tests before implementing
- Smallest possible step: Each cycle should be quick (5-10 minutes)
- Test behavior, not implementation: Don't test private methods
- Keep tests simple: Tests should be easier to understand than code
Test Quality
- Clear test names: Should read like documentation
- Arrange-Act-Assert: Structure tests consistently
- One assertion per test: Makes failures clear
- No logic in tests: Tests should be simple data
- Independent tests: No test depends on another
Debugging Best Practices
- Reproduce first: Can't fix what you can't reproduce
- Understand before fixing: Don't guess and check
- Fix root cause: Don't just treat symptoms
- Add regression test: Prevent bug from returning
- Document why: Help future debuggers
Integration with Superpowers
If superpowers:test-driven-development available:
- Use for guided TDD workflow
- Enhanced RED-GREEN-REFACTOR cycle
- Additional TDD best practices
If superpowers:systematic-debugging available:
- Use for complex debugging scenarios
- Root cause tracing framework
- Advanced instrumentation guidance
Common Anti-Patterns to Avoid
❌ Writing tests after implementation ❌ Changing tests to match implementation ❌ Testing implementation details instead of behavior ❌ Skipping refactor phase ❌ Testing code that's already tested (overwriting working tests) ❌ Making multiple changes before testing ❌ Debugging by randomly changing code ❌ Committing "console.log" debugging code
Notes
- TDD is slower initially but faster overall (fewer bugs, less debugging)
- Good tests are documentation that never gets outdated
- Debugging is detective work, not guessing
- Always add regression tests after fixing bugs
- Test coverage is a minimum bar, not a goal