Initial commit
This commit is contained in:
621
skills/systematic-testing/SKILL.md
Normal file
621
skills/systematic-testing/SKILL.md
Normal file
@@ -0,0 +1,621 @@
|
||||
---
|
||||
name: systematic-testing
|
||||
description: 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:**
|
||||
1. **RED:** Write failing test first
|
||||
2. **GREEN:** Write minimal code to pass
|
||||
3. **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:
|
||||
|
||||
```javascript
|
||||
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:
|
||||
|
||||
```javascript
|
||||
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:
|
||||
|
||||
```javascript
|
||||
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:
|
||||
|
||||
```javascript
|
||||
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
|
||||
|
||||
```javascript
|
||||
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:**
|
||||
|
||||
1. **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
|
||||
```
|
||||
|
||||
2. **Identify Symptoms**
|
||||
- What's the visible error?
|
||||
- What error messages appear?
|
||||
- What's the expected vs actual behavior?
|
||||
|
||||
3. **Gather Evidence**
|
||||
```bash
|
||||
# Check logs
|
||||
tail -f logs/app.log
|
||||
|
||||
# Check network requests (browser dev tools)
|
||||
# Check console errors
|
||||
# Check server logs
|
||||
```
|
||||
|
||||
Collect:
|
||||
- Error messages (full text)
|
||||
- Stack traces
|
||||
- Log entries
|
||||
- Network requests/responses
|
||||
- Input values that trigger bug
|
||||
|
||||
4. **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:**
|
||||
|
||||
1. **When Does It Fail?**
|
||||
- Specific inputs?
|
||||
- Certain users?
|
||||
- Particular time of day?
|
||||
- After specific sequence of actions?
|
||||
|
||||
2. **When Does It Work?**
|
||||
- Any inputs that succeed?
|
||||
- Any users unaffected?
|
||||
- Worked before, broken now?
|
||||
|
||||
3. **What Changed Recently?**
|
||||
```bash
|
||||
# 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
|
||||
```
|
||||
|
||||
4. **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:**
|
||||
|
||||
1. **Create Minimal Test Case**
|
||||
```javascript
|
||||
// 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
|
||||
});
|
||||
```
|
||||
|
||||
2. **Add Instrumentation**
|
||||
```javascript
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
||||
3. **Test Hypothesis**
|
||||
```javascript
|
||||
// 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!
|
||||
```
|
||||
|
||||
4. **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:**
|
||||
|
||||
1. **Write Test Reproducing Bug**
|
||||
```javascript
|
||||
// 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:
|
||||
```bash
|
||||
npm test
|
||||
# ✗ should accept email with plus symbol
|
||||
# Expected: 201, Received: 400
|
||||
```
|
||||
|
||||
2. **Fix the Bug**
|
||||
```sql
|
||||
-- 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,}$');
|
||||
```
|
||||
|
||||
3. **Verify Test Passes**
|
||||
```bash
|
||||
npm test
|
||||
# ✓ should accept email with plus symbol
|
||||
```
|
||||
|
||||
4. **Add Regression Tests**
|
||||
```javascript
|
||||
// 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
5. **Document Root Cause**
|
||||
```javascript
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
```
|
||||
|
||||
6. **Commit Fix with Context**
|
||||
```bash
|
||||
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:**
|
||||
```bash
|
||||
# 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:**
|
||||
1. Critical business logic (highest priority)
|
||||
2. Security-sensitive code
|
||||
3. Complex algorithms
|
||||
4. Error handling paths
|
||||
5. Edge cases
|
||||
|
||||
## Best Practices
|
||||
|
||||
### TDD Best Practices
|
||||
1. **Always write test first**: No exceptions
|
||||
2. **One test at a time**: Don't write multiple tests before implementing
|
||||
3. **Smallest possible step**: Each cycle should be quick (5-10 minutes)
|
||||
4. **Test behavior, not implementation**: Don't test private methods
|
||||
5. **Keep tests simple**: Tests should be easier to understand than code
|
||||
|
||||
### Test Quality
|
||||
1. **Clear test names**: Should read like documentation
|
||||
2. **Arrange-Act-Assert**: Structure tests consistently
|
||||
3. **One assertion per test**: Makes failures clear
|
||||
4. **No logic in tests**: Tests should be simple data
|
||||
5. **Independent tests**: No test depends on another
|
||||
|
||||
### Debugging Best Practices
|
||||
1. **Reproduce first**: Can't fix what you can't reproduce
|
||||
2. **Understand before fixing**: Don't guess and check
|
||||
3. **Fix root cause**: Don't just treat symptoms
|
||||
4. **Add regression test**: Prevent bug from returning
|
||||
5. **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
|
||||
Reference in New Issue
Block a user