
Test Specialist
- 856 installs
- 432 repo stars
- Updated November 11, 2025
- ailabs-393/ai-labs-claude-skills
test-specialist is a Claude agent skill that follows a repeatable 5-step bug analysis process to turn vague defects into reliable fixes for developers working with AI coding agents.
About
test-specialist is version 1.0.0 from ailabs-393/ai-labs-claude-skills and packages a systematic bug analysis and debugging framework for Claude-powered agents. The skill walks through reproduction, environment identification, root-cause isolation, fix implementation, and verification so vague bug reports become actionable repair tasks. Developers invoke test-specialist when agent-assisted debugging produces inconsistent or shallow fixes. The published npm-style package exposes index.js, though implementers should treat the SKILL.md framework as the primary debugging playbook.
- 5-step systematic bug analysis framework: Reproduction, Isolation, Root Cause Analysis, Fix Implementation, Validation
- Always write a failing test before implementing the fix
- Includes bug categories and detection patterns for faster triage
- Forces minimal reproduction cases before debugging
- Hard-gate: never skip reproduction and isolation steps
Test Specialist by the numbers
- 856 all-time installs (skills.sh)
- +6 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #57 of 597 Debugging skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/ailabs-393/ai-labs-claude-skills --skill test-specialistAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 856 |
|---|---|
| repo stars | ★ 432 |
| Security audit | 3 / 3 scanners passed |
| Last updated | November 11, 2025 |
| Repository | ailabs-393/ai-labs-claude-skills ↗ |
How do you turn vague bugs into reliable fixes?
Follow a repeatable 5-step process that turns vague bugs into reliable fixes when working with Claude-powered agents.
Who is it for?
Developers using Claude agents who need a structured debugging workflow instead of ad-hoc patch attempts.
Skip if: Greenfield test suite authoring or CI pipeline setup where dedicated testing skills are more appropriate.
When should I use this skill?
A bug report is vague, reproduction is unclear, or agent-generated fixes fail verification repeatedly.
What you get
Reproduction steps, root-cause analysis, verified fix, and documented verification evidence
- Bug reproduction steps
- Root-cause analysis notes
- Verified fix with test evidence
By the numbers
- Follows a 5-step systematic bug analysis process
- Version 1.0.0 in @ai-labs-claude-skills/test-specialist
Files
Test Specialist
Overview
Apply systematic testing methodologies and debugging techniques to JavaScript/TypeScript applications. This skill provides comprehensive testing strategies, bug analysis frameworks, and automated tools for identifying coverage gaps and untested code.
Core Capabilities
1. Writing Test Cases
Write comprehensive tests covering unit, integration, and end-to-end scenarios.
Unit Testing Approach
Structure tests using the AAA pattern (Arrange-Act-Assert):
describe('ExpenseCalculator', () => {
describe('calculateTotal', () => {
test('sums expense amounts correctly', () => {
// Arrange
const expenses = [
{ amount: 100, category: 'food' },
{ amount: 50, category: 'transport' },
{ amount: 25, category: 'entertainment' }
];
// Act
const total = calculateTotal(expenses);
// Assert
expect(total).toBe(175);
});
test('handles empty expense list', () => {
expect(calculateTotal([])).toBe(0);
});
test('handles negative amounts', () => {
const expenses = [
{ amount: 100, category: 'food' },
{ amount: -50, category: 'refund' }
];
expect(calculateTotal(expenses)).toBe(50);
});
});
});Key principles:
- Test one behavior per test
- Cover happy path, edge cases, and error conditions
- Use descriptive test names that explain the scenario
- Keep tests independent and isolated
Integration Testing Approach
Test how components work together, including database, API, and service interactions:
describe('ExpenseAPI Integration', () => {
beforeAll(async () => {
await database.connect(TEST_DB_URL);
});
afterAll(async () => {
await database.disconnect();
});
beforeEach(async () => {
await database.clear();
await seedTestData();
});
test('POST /expenses creates expense and updates total', async () => {
const response = await request(app)
.post('/api/expenses')
.send({
amount: 50,
category: 'food',
description: 'Lunch'
})
.expect(201);
expect(response.body).toMatchObject({
id: expect.any(Number),
amount: 50,
category: 'food'
});
// Verify database state
const total = await getTotalExpenses();
expect(total).toBe(50);
});
});End-to-End Testing Approach
Test complete user workflows using tools like Playwright or Cypress:
test('user can track expense from start to finish', async ({ page }) => {
// Navigate to app
await page.goto('/');
// Add new expense
await page.click('[data-testid="add-expense-btn"]');
await page.fill('[data-testid="amount"]', '50.00');
await page.selectOption('[data-testid="category"]', 'food');
await page.fill('[data-testid="description"]', 'Lunch');
await page.click('[data-testid="submit"]');
// Verify expense appears in list
await expect(page.locator('[data-testid="expense-item"]')).toContainText('Lunch');
await expect(page.locator('[data-testid="total"]')).toContainText('$50.00');
});2. Systematic Bug Analysis
Apply structured debugging methodology to identify and fix issues.
Five-Step Analysis Process
1. Reproduction: Reliably reproduce the bug
- Document exact steps to trigger
- Identify required environment/state
- Note expected vs actual behavior
2. Isolation: Narrow down the problem
- Binary search through code path
- Create minimal reproduction case
- Remove unrelated dependencies
3. Root Cause Analysis: Determine underlying cause
- Trace execution flow
- Check assumptions and preconditions
- Review recent changes (git blame)
4. Fix Implementation: Implement solution
- Write failing test first (TDD)
- Implement the fix
- Verify test passes
5. Validation: Ensure completeness
- Run full test suite
- Test edge cases
- Verify no regressions
Common Bug Patterns
Race Conditions:
// Test concurrent operations
test('handles concurrent updates correctly', async () => {
const promises = Array.from({ length: 100 }, () =>
incrementExpenseCount()
);
await Promise.all(promises);
expect(getExpenseCount()).toBe(100);
});Null/Undefined Errors:
// Test null safety
test.each([null, undefined, '', 0, false])
('handles invalid input: %p', (input) => {
expect(() => processExpense(input)).toThrow('Invalid expense');
});Off-by-One Errors:
// Test boundaries explicitly
describe('pagination', () => {
test('handles empty list', () => {
expect(paginate([], 1, 10)).toEqual([]);
});
test('handles single item', () => {
expect(paginate([item], 1, 10)).toEqual([item]);
});
test('handles last page with partial items', () => {
const items = Array.from({ length: 25 }, (_, i) => i);
expect(paginate(items, 3, 10)).toHaveLength(5);
});
});3. Identifying Potential Issues
Proactively identify issues before they become bugs.
Security Vulnerabilities
Test for common security issues:
describe('security', () => {
test('prevents SQL injection', async () => {
const malicious = "'; DROP TABLE expenses; --";
await expect(
searchExpenses(malicious)
).resolves.not.toThrow();
});
test('sanitizes XSS in descriptions', () => {
const xss = '<script>alert("xss")</script>';
const expense = createExpense({ description: xss });
expect(expense.description).not.toContain('<script>');
});
test('requires authentication for expense operations', async () => {
await request(app)
.post('/api/expenses')
.send({ amount: 50 })
.expect(401);
});
});Performance Issues
Test for performance problems:
test('processes large expense list efficiently', () => {
const largeList = Array.from({ length: 10000 }, (_, i) => ({
amount: i,
category: 'test'
}));
const start = performance.now();
const total = calculateTotal(largeList);
const duration = performance.now() - start;
expect(duration).toBeLessThan(100); // Should complete in <100ms
expect(total).toBe(49995000);
});Logic Errors
Use parameterized tests to catch edge cases:
test.each([
// [input, expected, description]
[[10, 20, 30], 60, 'normal positive values'],
[[0, 0, 0], 0, 'all zeros'],
[[-10, 20, -5], 5, 'mixed positive and negative'],
[[0.1, 0.2], 0.3, 'decimal precision'],
[[Number.MAX_SAFE_INTEGER], Number.MAX_SAFE_INTEGER, 'large numbers'],
])('calculateTotal(%p) = %p (%s)', (amounts, expected, description) => {
const expenses = amounts.map(amount => ({ amount, category: 'test' }));
expect(calculateTotal(expenses)).toBeCloseTo(expected);
});4. Test Coverage Analysis
Use automated tools to identify gaps in test coverage.
Finding Untested Code
Run the provided script to identify source files without tests:
python3 scripts/find_untested_code.py srcThe script will:
- Scan source directory for all code files
- Identify which files lack corresponding test files
- Categorize untested files by type (components, services, utils, etc.)
- Prioritize files that need testing most
Interpretation:
- API/Services: High priority - test business logic and data operations
- Models: High priority - test data validation and transformations
- Hooks: Medium priority - test stateful behavior
- Components: Medium priority - test complex UI logic
- Utils: Low priority - test as needed for complex functions
Analyzing Coverage Reports
Run the coverage analysis script after generating coverage:
# Generate coverage (using Jest example)
npm test -- --coverage
# Analyze coverage gaps
python3 scripts/analyze_coverage.py coverage/coverage-final.jsonThe script identifies:
- Files below coverage threshold (default 80%)
- Statement, branch, and function coverage percentages
- Priority files to improve
Coverage targets:
- Critical paths: 90%+ coverage
- Business logic: 85%+ coverage
- UI components: 75%+ coverage
- Utilities: 70%+ coverage
5. Test Maintenance and Quality
Ensure tests remain valuable and maintainable.
Test Code Quality Principles
DRY (Don't Repeat Yourself):
// Extract common setup
function createTestExpense(overrides = {}) {
return {
amount: 50,
category: 'food',
description: 'Test expense',
date: new Date('2024-01-01'),
...overrides
};
}
test('filters by category', () => {
const expenses = [
createTestExpense({ category: 'food' }),
createTestExpense({ category: 'transport' }),
];
// ...
});Clear test data:
// Bad: Magic numbers
expect(calculateDiscount(100, 0.15)).toBe(85);
// Good: Named constants
const ORIGINAL_PRICE = 100;
const DISCOUNT_RATE = 0.15;
const EXPECTED_PRICE = 85;
expect(calculateDiscount(ORIGINAL_PRICE, DISCOUNT_RATE)).toBe(EXPECTED_PRICE);Avoid test interdependence:
// Bad: Tests depend on execution order
let sharedState;
test('test 1', () => {
sharedState = { value: 1 };
});
test('test 2', () => {
expect(sharedState.value).toBe(1); // Depends on test 1
});
// Good: Independent tests
test('test 1', () => {
const state = { value: 1 };
expect(state.value).toBe(1);
});
test('test 2', () => {
const state = { value: 1 };
expect(state.value).toBe(1);
});Workflow Decision Tree
Follow this decision tree to determine the testing approach:
1. Adding new functionality?
- Yes → Write tests first (TDD)
- Write failing test
- Implement feature
- Verify test passes
- Refactor
- No → Go to step 2
2. Fixing a bug?
- Yes → Apply bug analysis process
- Reproduce the bug
- Write failing test demonstrating bug
- Fix the implementation
- Verify test passes
- No → Go to step 3
3. Improving test coverage?
- Yes → Use coverage tools
- Run
find_untested_code.pyto identify gaps - Run
analyze_coverage.pyon coverage reports - Prioritize critical paths
- Write tests for untested code
- No → Go to step 4
4. Analyzing code quality?
- Yes → Systematic review
- Check for security vulnerabilities
- Test edge cases and error handling
- Verify performance characteristics
- Review error handling
Testing Frameworks and Tools
Recommended Stack
Unit/Integration Testing:
- Jest or Vitest for test runner
- Testing Library for React components
- Supertest for API testing
- MSW (Mock Service Worker) for API mocking
E2E Testing:
- Playwright or Cypress
- Page Object Model pattern
Coverage:
- Istanbul (built into Jest/Vitest)
- Coverage reports in JSON format
Running Tests
# Run all tests
npm test
# Run with coverage
npm test -- --coverage
# Run specific test file
npm test -- ExpenseCalculator.test.ts
# Run in watch mode
npm test -- --watch
# Run E2E tests
npm run test:e2eReference Documentation
For detailed patterns and techniques, refer to:
references/testing_patterns.md- Comprehensive testing patterns, best practices, and code examplesreferences/bug_analysis.md- In-depth bug analysis framework, common bug patterns, and debugging techniques
These references contain extensive examples and advanced techniques. Load them when:
- Dealing with complex testing scenarios
- Need specific pattern implementations
- Debugging unusual issues
- Seeking best practices for specific situations
Scripts
analyze_coverage.py
Analyze Jest/Istanbul coverage reports to identify gaps:
python3 scripts/analyze_coverage.py [coverage-file]Automatically finds common coverage file locations if not specified.
Output:
- Files below coverage threshold
- Statement, branch, and function coverage percentages
- Priority files to improve
find_untested_code.py
Find source files without corresponding test files:
python3 scripts/find_untested_code.py [src-dir] [--pattern test|spec]Output:
- Total source and test file counts
- Test file coverage percentage
- Untested files categorized by type (API, services, components, etc.)
- Recommendations for prioritization
Best Practices Summary
1. Write tests first (TDD) when adding new features 2. Test behavior, not implementation - tests should survive refactoring 3. Keep tests independent - no shared state between tests 4. Use descriptive names - test names should explain the scenario 5. Cover edge cases - null, empty, boundary values, error conditions 6. Mock external dependencies - tests should be fast and reliable 7. Maintain high coverage - 80%+ for critical code 8. Fix failing tests immediately - never commit broken tests 9. Refactor tests - apply same quality standards as production code 10. Use tools - automate coverage analysis and gap identification
export default async function test_specialist(input) {
console.log("🧠 Running skill: test-specialist");
// TODO: implement actual logic for this skill
return {
message: "Skill 'test-specialist' executed successfully!",
input
};
}
{
"name": "@ai-labs-claude-skills/test-specialist",
"version": "1.0.0",
"description": "Claude AI skill: test-specialist",
"main": "index.js",
"files": [
"."
],
"license": "MIT",
"author": "AI Labs"
}Bug Analysis and Debugging Framework
Systematic Bug Analysis Approach
1. Reproduction
First, reliably reproduce the bug:
- Identify exact steps to trigger the issue
- Determine required environment/state
- Document inputs that cause the failure
- Note expected vs actual behavior
2. Isolation
Narrow down the problem:
- Binary search through the code path
- Remove unrelated code/dependencies
- Create minimal reproduction case
- Identify the smallest failing unit
3. Root Cause Analysis
Determine the underlying cause:
- Trace execution flow
- Check assumptions and preconditions
- Review recent changes (git blame, git log)
- Look for similar patterns in codebase
4. Fix Implementation
Implement the solution:
- Write a failing test first
- Implement the fix
- Verify test passes
- Check for similar issues elsewhere
5. Validation
Ensure the fix is complete:
- Run full test suite
- Test edge cases
- Verify no regressions
- Update documentation if needed
Bug Categories and Detection
Logic Errors
Symptoms:
- Incorrect calculations
- Wrong conditional branches taken
- Unexpected state transitions
- Data corruption
Detection Strategies:
// Add assertions to verify invariants
function transfer(from: Account, to: Account, amount: number) {
const beforeTotal = from.balance + to.balance;
from.balance -= amount;
to.balance += amount;
const afterTotal = from.balance + to.balance;
console.assert(beforeTotal === afterTotal, 'Balance mismatch');
}
// Unit tests for all branches
test.each([
[100, 50, true], // Normal case
[100, 100, true], // Exact balance
[100, 101, false], // Insufficient funds
[100, 0, false], // Zero amount
[100, -50, false], // Negative amount
])('transfer validation', (balance, amount, shouldSucceed) => {
// Test implementation
});Race Conditions
Symptoms:
- Intermittent failures
- Tests pass/fail randomly
- Different behavior in production vs development
- Issues under load
Detection Strategies:
// Test concurrent operations
test('handles concurrent updates correctly', async () => {
const promises = Array.from({ length: 100 }, () =>
incrementCounter()
);
await Promise.all(promises);
expect(getCounter()).toBe(100);
});
// Add delays to expose timing issues
test('handles async race condition', async () => {
const result1 = fetchData();
await new Promise(resolve => setTimeout(resolve, 10));
const result2 = fetchData();
await expect(Promise.all([result1, result2])).resolves.toBeTruthy();
});Memory Leaks
Symptoms:
- Increasing memory usage over time
- Application slowdown
- Out of memory errors
- Event listeners not cleaned up
Detection Strategies:
// Test for cleanup
test('cleans up event listeners', () => {
const component = new Component();
const listenerCount = getEventListenerCount();
component.mount();
expect(getEventListenerCount()).toBeGreaterThan(listenerCount);
component.unmount();
expect(getEventListenerCount()).toBe(listenerCount);
});
// Monitor memory in long-running tests
test('does not leak memory', async () => {
const initialMemory = process.memoryUsage().heapUsed;
for (let i = 0; i < 1000; i++) {
await performOperation();
}
// Force garbage collection if available
if (global.gc) global.gc();
const finalMemory = process.memoryUsage().heapUsed;
const growth = finalMemory - initialMemory;
// Allow some growth but not linear with iterations
expect(growth).toBeLessThan(10 * 1024 * 1024); // 10MB threshold
});Off-by-One Errors
Symptoms:
- Array index out of bounds
- Loop iterations incorrect
- Boundary values handled wrong
Detection Strategies:
// Test boundary conditions explicitly
describe('array operations', () => {
test('handles empty array', () => {
expect(processArray([])).toEqual([]);
});
test('handles single element', () => {
expect(processArray([1])).toEqual([1]);
});
test('handles first element', () => {
const result = processArray([1, 2, 3]);
expect(result[0]).toBe(1);
});
test('handles last element', () => {
const result = processArray([1, 2, 3]);
expect(result[result.length - 1]).toBe(3);
});
});Null/Undefined Reference Errors
Symptoms:
- Cannot read property of undefined
- Null reference exceptions
- Type errors at runtime
Detection Strategies:
// Test with missing/null/undefined values
describe('null safety', () => {
test.each([
[null, 'handles null'],
[undefined, 'handles undefined'],
['', 'handles empty string'],
[0, 'handles zero'],
[false, 'handles false'],
])('safely handles %p', (input, description) => {
expect(() => processInput(input)).not.toThrow();
});
// Use optional chaining and nullish coalescing
const value = obj?.nested?.property ?? defaultValue;
});
// Enable strict null checks in TypeScript
// tsconfig.json: "strictNullChecks": trueAPI Integration Errors
Symptoms:
- Unexpected response formats
- Network timeouts
- Authentication failures
- Rate limiting issues
Detection Strategies:
// Test error responses
test('handles API errors', async () => {
mockApi.get.mockRejectedValue({
status: 500,
message: 'Internal Server Error'
});
await expect(fetchData()).rejects.toThrow('Internal Server Error');
expect(errorHandler).toHaveBeenCalled();
});
// Test timeout scenarios
test('handles timeout', async () => {
jest.useFakeTimers();
const promise = fetchWithTimeout('https://api.example.com', 5000);
jest.advanceTimersByTime(6000);
await expect(promise).rejects.toThrow('Timeout');
jest.useRealTimers();
});
// Test malformed responses
test('handles invalid JSON', async () => {
mockApi.get.mockResolvedValue('invalid json{');
await expect(fetchData()).rejects.toThrow();
});State Management Bugs
Symptoms:
- Stale data displayed
- State updates not reflected
- Inconsistent UI state
- Lost updates
Detection Strategies:
// Test state transitions
describe('state machine', () => {
test('transitions from idle to loading', () => {
const state = { status: 'idle' };
const newState = transition(state, 'FETCH');
expect(newState.status).toBe('loading');
});
test('prevents invalid transitions', () => {
const state = { status: 'idle' };
expect(() => transition(state, 'COMPLETE')).toThrow();
});
});
// Test state consistency
test('maintains state consistency', async () => {
const store = createStore();
const action1 = store.dispatch(updateUser({ name: 'Alice' }));
const action2 = store.dispatch(updateUser({ name: 'Bob' }));
await Promise.all([action1, action2]);
const state = store.getState();
expect(['Alice', 'Bob']).toContain(state.user.name);
});Performance Bugs
Symptoms:
- Slow response times
- UI freezing/blocking
- High CPU/memory usage
- Inefficient algorithms
Detection Strategies:
// Benchmark performance
test('performs efficiently with large datasets', () => {
const largeDataset = generateData(10000);
const start = performance.now();
const result = processData(largeDataset);
const duration = performance.now() - start;
expect(duration).toBeLessThan(100); // 100ms threshold
expect(result.length).toBe(10000);
});
// Test for N+1 queries
test('avoids N+1 database queries', async () => {
const queryCounter = createQueryCounter();
await fetchUsersWithPosts(100);
// Should use JOIN, not individual queries per user
expect(queryCounter.count).toBeLessThan(5);
});
// Monitor re-renders
test('minimizes unnecessary re-renders', () => {
const renderSpy = jest.fn();
const { rerender } = render(<Component onRender={renderSpy} />);
rerender(<Component onRender={renderSpy} />);
expect(renderSpy).toHaveBeenCalledTimes(1); // No props changed
});Security Vulnerabilities
Symptoms:
- Unauthorized access
- Data leaks
- Injection attacks possible
- Improper input validation
Detection Strategies:
// Test authentication/authorization
test('prevents unauthorized access', async () => {
const response = await request(app)
.get('/api/admin/users')
.set('Authorization', 'Bearer user-token')
.expect(403);
});
// Test input sanitization
test('sanitizes user input', () => {
const maliciousInputs = [
'<script>alert("xss")</script>',
'"; DROP TABLE users; --',
'../../../etc/passwd',
'${process.env.SECRET}',
];
maliciousInputs.forEach(input => {
const sanitized = sanitizeInput(input);
expect(sanitized).not.toContain('<script>');
expect(sanitized).not.toContain('DROP TABLE');
expect(sanitized).not.toContain('../');
});
});
// Test CSRF protection
test('requires CSRF token', async () => {
await request(app)
.post('/api/transfer')
.send({ amount: 1000 })
.expect(403);
await request(app)
.post('/api/transfer')
.set('X-CSRF-Token', validToken)
.send({ amount: 1000 })
.expect(200);
});Debugging Techniques
Console Debugging
// Strategic logging
function complexCalculation(data: Data[]) {
console.log('Input:', data);
const filtered = data.filter(item => item.active);
console.log('After filter:', filtered);
const mapped = filtered.map(transform);
console.log('After transform:', mapped);
return mapped;
}
// Use debug library for conditional logging
import debug from 'debug';
const log = debug('app:module');
log('Processing %d items', items.length);Test-Driven Debugging
// 1. Write a test that reproduces the bug
test('bug: incorrect total with negative values', () => {
const items = [10, -5, 20];
const result = calculateTotal(items);
expect(result).toBe(25); // This will fail
});
// 2. Fix the implementation
// 3. Verify the test passes
// 4. Add more edge casesBinary Search Debugging
// Isolate the problem by commenting out sections
function problematicFunction(data: any) {
const step1 = processStep1(data);
console.log('Step 1 OK'); // Check if we reach here
const step2 = processStep2(step1);
console.log('Step 2 OK'); // Problem occurs before this?
const step3 = processStep3(step2);
console.log('Step 3 OK');
return step3;
}Differential Testing
// Compare old vs new implementation
test('new implementation matches old behavior', () => {
const inputs = generateTestCases(100);
inputs.forEach(input => {
const oldResult = oldImplementation(input);
const newResult = newImplementation(input);
expect(newResult).toEqual(oldResult);
});
});Prevention Strategies
Defensive Programming
// Validate inputs
function divide(a: number, b: number): number {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new TypeError('Arguments must be numbers');
}
if (b === 0) {
throw new Error('Division by zero');
}
if (!Number.isFinite(a) || !Number.isFinite(b)) {
throw new Error('Arguments must be finite');
}
return a / b;
}
// Use TypeScript strict mode
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noUnusedLocals": true,
"noUnusedParameters": true
}
}Code Review Checklist
- [ ] All edge cases handled?
- [ ] Error handling in place?
- [ ] Input validation implemented?
- [ ] Tests cover new code?
- [ ] No obvious performance issues?
- [ ] Security considerations addressed?
- [ ] Documentation updated?
- [ ] No console.log statements left?
Static Analysis
// ESLint configuration
{
"rules": {
"no-unused-vars": "error",
"no-console": "warn",
"eqeqeq": "error",
"no-eval": "error",
"@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/explicit-function-return-type": "warn"
}
}Common Bug Patterns
Pattern 1: Async/Await Mistakes
// ❌ Wrong: Not awaiting properly
async function wrong() {
const result = fetch('/api'); // Missing await
return result.data; // Will fail
}
// ✅ Correct
async function correct() {
const result = await fetch('/api');
return result.data;
}
// ❌ Wrong: Not handling errors
async function noErrorHandling() {
const data = await fetch('/api'); // Throws on error
return data;
}
// ✅ Correct
async function withErrorHandling() {
try {
const data = await fetch('/api');
return data;
} catch (error) {
logger.error(error);
throw new Error('Failed to fetch data');
}
}Pattern 2: Closure Issues
// ❌ Wrong: Stale closure
function createHandlers() {
let count = 0;
const handlers = [];
for (var i = 0; i < 3; i++) {
handlers.push(() => console.log(i)); // All will log 3
}
return handlers;
}
// ✅ Correct
function createHandlersFixed() {
const handlers = [];
for (let i = 0; i < 3; i++) { // Use let instead of var
handlers.push(() => console.log(i));
}
return handlers;
}Pattern 3: Mutation Issues
// ❌ Wrong: Mutating input
function sortItems(items: Item[]) {
return items.sort((a, b) => a.value - b.value); // Mutates original
}
// ✅ Correct
function sortItemsFixed(items: Item[]) {
return [...items].sort((a, b) => a.value - b.value);
}Pattern 4: Floating Point Precision
// ❌ Wrong: Direct float comparison
test('adds decimals', () => {
expect(0.1 + 0.2).toBe(0.3); // Fails!
});
// ✅ Correct
test('adds decimals', () => {
expect(0.1 + 0.2).toBeCloseTo(0.3);
});Testing Patterns and Best Practices
Unit Testing Patterns
AAA Pattern (Arrange-Act-Assert)
Structure tests in three clear phases:
- Arrange: Set up test data and dependencies
- Act: Execute the function/method being tested
- Assert: Verify the expected outcome
test('calculateTotal adds items correctly', () => {
// Arrange
const items = [10, 20, 30];
// Act
const result = calculateTotal(items);
// Assert
expect(result).toBe(60);
});Test Doubles
Mocks
Use mocks to verify interactions and calls:
const mockFn = jest.fn();
service.subscribe(mockFn);
service.notify('event');
expect(mockFn).toHaveBeenCalledWith('event');Stubs
Use stubs to provide predetermined responses:
const stub = jest.fn().mockReturnValue(42);Spies
Use spies to track calls while preserving original implementation:
const spy = jest.spyOn(object, 'method');Parameterized Tests
Test multiple scenarios efficiently:
test.each([
[1, 1, 2],
[2, 3, 5],
[10, -5, 5],
])('adds %i + %i to equal %i', (a, b, expected) => {
expect(add(a, b)).toBe(expected);
});Integration Testing Patterns
Test Database Setup
Use test-specific databases with proper setup/teardown:
beforeAll(async () => {
await database.connect(TEST_DB_URL);
});
afterAll(async () => {
await database.disconnect();
});
beforeEach(async () => {
await database.clear();
await seedTestData();
});API Testing Pattern
Test API endpoints with proper request/response validation:
test('POST /users creates user', async () => {
const response = await request(app)
.post('/users')
.send({ name: 'John', email: 'john@test.com' })
.expect(201);
expect(response.body).toMatchObject({
id: expect.any(Number),
name: 'John',
email: 'john@test.com'
});
});End-to-End Testing Patterns
Page Object Model
Encapsulate page interactions:
class LoginPage {
async navigate() {
await page.goto('/login');
}
async login(email: string, password: string) {
await page.fill('[data-testid="email"]', email);
await page.fill('[data-testid="password"]', password);
await page.click('[data-testid="submit"]');
}
async getErrorMessage() {
return page.textContent('[data-testid="error"]');
}
}User Journey Testing
Test complete user workflows:
test('user can complete purchase flow', async () => {
await homePage.navigate();
await homePage.searchProduct('laptop');
await productPage.addToCart();
await cartPage.proceedToCheckout();
await checkoutPage.fillShippingInfo(testAddress);
await checkoutPage.fillPaymentInfo(testCard);
await checkoutPage.submitOrder();
expect(await confirmationPage.getOrderNumber()).toBeTruthy();
});Test Coverage Strategies
Coverage Targets
- Statements: 80%+ for critical paths
- Branches: 75%+ to ensure all conditionals tested
- Functions: 90%+ for public APIs
- Lines: 80%+ overall
Critical Path Priority
1. Business-critical functionality (payment, auth, data integrity) 2. Complex logic with multiple branches 3. Bug-prone areas (identified from production issues) 4. Public APIs and interfaces 5. Edge cases and error handling
Error and Edge Case Testing
Boundary Testing
Test values at boundaries:
describe('age validation', () => {
test.each([
[-1, false],
[0, true],
[17, false],
[18, true],
[120, true],
[121, false],
])('validates age %i as %s', (age, expected) => {
expect(isValidAge(age)).toBe(expected);
});
});Error Handling
Test both success and failure paths:
test('handles network errors gracefully', async () => {
mockApi.get.mockRejectedValue(new Error('Network failure'));
await expect(fetchData()).rejects.toThrow('Network failure');
expect(logger.error).toHaveBeenCalled();
});Null/Undefined Testing
Test with missing or invalid data:
test.each([null, undefined, '', {}, []])
('handles invalid input: %p', (input) => {
expect(() => processData(input)).toThrow();
});Async Testing Patterns
Promise Testing
test('async operation succeeds', async () => {
await expect(fetchUser(1)).resolves.toMatchObject({
id: 1,
name: expect.any(String)
});
});
test('async operation fails', async () => {
await expect(fetchUser(-1)).rejects.toThrow('User not found');
});Timeout Handling
test('operation completes within timeout', async () => {
const start = Date.now();
await performOperation();
const duration = Date.now() - start;
expect(duration).toBeLessThan(1000);
}, 5000); // 5 second test timeoutSnapshot Testing
Use snapshots for UI components and data structures:
test('renders correctly', () => {
const tree = renderer.create(<Component prop="value" />).toJSON();
expect(tree).toMatchSnapshot();
});When to use snapshots:
- UI component rendering
- Large data structure validation
- API response format verification
When to avoid snapshots:
- Dynamic data (timestamps, IDs)
- Frequently changing structures
- When specific assertions are clearer
Test Organization
File Structure
src/
components/
Button.tsx
Button.test.tsx
services/
api.ts
api.test.ts
__tests__/
integration/
user-flow.test.ts
e2e/
checkout.test.tsTest Naming
describe('UserService', () => {
describe('createUser', () => {
test('creates user with valid data', () => {});
test('throws error when email is invalid', () => {});
test('throws error when email already exists', () => {});
});
});Performance Testing
Benchmarking
test('processes large dataset efficiently', () => {
const largeArray = Array.from({ length: 10000 }, (_, i) => i);
const start = performance.now();
processArray(largeArray);
const duration = performance.now() - start;
expect(duration).toBeLessThan(100); // Should complete in <100ms
});Security Testing Patterns
Input Validation
test('prevents SQL injection', () => {
const maliciousInput = "'; DROP TABLE users; --";
expect(() => queryDatabase(maliciousInput)).not.toThrow();
expect(database.getTables()).toContain('users');
});
test('prevents XSS attacks', () => {
const maliciousScript = '<script>alert("XSS")</script>';
const sanitized = sanitizeInput(maliciousScript);
expect(sanitized).not.toContain('<script>');
});Authentication Testing
test('requires authentication for protected routes', async () => {
const response = await request(app)
.get('/api/protected')
.expect(401);
});
test('validates JWT tokens', async () => {
const invalidToken = 'invalid.jwt.token';
const response = await request(app)
.get('/api/protected')
.set('Authorization', `Bearer ${invalidToken}`)
.expect(401);
});Common Anti-Patterns to Avoid
1. Testing Implementation Details: Test behavior, not internal implementation 2. Fragile Tests: Avoid tests that break with minor refactoring 3. Test Interdependence: Each test should be independent 4. Excessive Mocking: Don't mock everything; test real integrations when possible 5. Long Tests: Keep tests focused on a single behavior 6. Magic Numbers: Use named constants for test values 7. Ignoring Test Failures: Never commit with failing tests 8. Testing Third-Party Code: Focus on your code, not libraries
#!/usr/bin/env python3
"""
Analyze test coverage and identify gaps.
This script parses coverage reports and provides insights on:
- Files with low coverage
- Uncovered critical paths
- Coverage trends
Usage:
python analyze_coverage.py [coverage-file]
"""
import json
import sys
from pathlib import Path
from typing import Dict, List, Tuple
def parse_coverage_json(filepath: str) -> Dict:
"""Parse Jest/Istanbul coverage JSON file."""
with open(filepath, 'r') as f:
return json.load(f)
def analyze_file_coverage(file_data: Dict) -> Dict:
"""Analyze coverage for a single file."""
statements = file_data.get('s', {})
branches = file_data.get('b', {})
functions = file_data.get('f', {})
lines = file_data.get('l', {})
# Count covered vs total
def count_coverage(data):
if isinstance(data, dict):
values = list(data.values())
if not values:
return 0, 0
# Handle branch coverage (nested arrays)
if isinstance(values[0], list):
total = sum(len(v) for v in values)
covered = sum(sum(1 for x in v if x > 0) for v in values)
else:
total = len(values)
covered = sum(1 for v in values if v > 0)
return covered, total
return 0, 0
stmt_covered, stmt_total = count_coverage(statements)
branch_covered, branch_total = count_coverage(branches)
func_covered, func_total = count_coverage(functions)
def pct(covered, total):
return (covered / total * 100) if total > 0 else 100
return {
'statements': {'pct': pct(stmt_covered, stmt_total), 'covered': stmt_covered, 'total': stmt_total},
'branches': {'pct': pct(branch_covered, branch_total), 'covered': branch_covered, 'total': branch_total},
'functions': {'pct': pct(func_covered, func_total), 'covered': func_covered, 'total': func_total},
}
def identify_coverage_gaps(coverage_data: Dict, threshold: float = 80.0) -> List[Tuple[str, Dict]]:
"""Identify files with coverage below threshold."""
gaps = []
for filepath, file_data in coverage_data.items():
if 'path' in file_data:
filepath = file_data['path']
analysis = analyze_file_coverage(file_data)
# Check if any metric is below threshold
below_threshold = any(
metric['pct'] < threshold
for metric in analysis.values()
)
if below_threshold:
gaps.append((filepath, analysis))
return sorted(gaps, key=lambda x: min(m['pct'] for m in x[1].values()))
def print_coverage_report(gaps: List[Tuple[str, Dict]]):
"""Print formatted coverage report."""
print("\n=== Coverage Gap Analysis ===\n")
if not gaps:
print("✅ All files meet coverage threshold!")
return
print(f"Found {len(gaps)} files with coverage gaps:\n")
for filepath, analysis in gaps:
print(f"📄 {filepath}")
print(f" Statements: {analysis['statements']['pct']:.1f}% ({analysis['statements']['covered']}/{analysis['statements']['total']})")
print(f" Branches: {analysis['branches']['pct']:.1f}% ({analysis['branches']['covered']}/{analysis['branches']['total']})")
print(f" Functions: {analysis['functions']['pct']:.1f}% ({analysis['functions']['covered']}/{analysis['functions']['total']})")
print()
def get_priority_files(gaps: List[Tuple[str, Dict]]) -> List[str]:
"""Identify high-priority files to test (lowest coverage)."""
return [filepath for filepath, _ in gaps[:5]]
def main():
if len(sys.argv) < 2:
# Try to find coverage file automatically
common_paths = [
'coverage/coverage-final.json',
'coverage/coverage.json',
'.coverage/coverage-final.json',
]
coverage_file = None
for path in common_paths:
if Path(path).exists():
coverage_file = path
break
if not coverage_file:
print("Usage: python analyze_coverage.py [coverage-file]")
print("\nCommon coverage file locations:")
print(" - coverage/coverage-final.json")
print(" - coverage/coverage.json")
sys.exit(1)
else:
coverage_file = sys.argv[1]
if not Path(coverage_file).exists():
print(f"Error: Coverage file not found: {coverage_file}")
sys.exit(1)
print(f"Analyzing coverage from: {coverage_file}")
coverage_data = parse_coverage_json(coverage_file)
gaps = identify_coverage_gaps(coverage_data)
print_coverage_report(gaps)
if gaps:
print("\n🎯 Priority files to improve (top 5):")
for filepath in get_priority_files(gaps):
print(f" - {filepath}")
print()
if __name__ == '__main__':
main()
#!/usr/bin/env python3
"""
Find source files that don't have corresponding test files.
This script helps identify code that lacks test coverage by finding
source files without matching test files.
Usage:
python find_untested_code.py [src-dir] [--pattern test|spec]
"""
import os
import sys
from pathlib import Path
from typing import List, Set, Tuple
def find_source_files(src_dir: str, extensions: List[str] = None) -> Set[Path]:
"""Find all source files in directory."""
if extensions is None:
extensions = ['.ts', '.tsx', '.js', '.jsx']
source_files = set()
src_path = Path(src_dir)
for ext in extensions:
source_files.update(src_path.rglob(f'*{ext}'))
# Exclude test files
test_patterns = ['.test.', '.spec.', '__tests__', '__mocks__']
source_files = {
f for f in source_files
if not any(pattern in str(f) for pattern in test_patterns)
}
return source_files
def find_test_files(src_dir: str, pattern: str = 'test') -> Set[Path]:
"""Find all test files in directory."""
src_path = Path(src_dir)
test_files = set()
# Common test file patterns
patterns = [
f'*.{pattern}.ts',
f'*.{pattern}.tsx',
f'*.{pattern}.js',
f'*.{pattern}.jsx',
]
for p in patterns:
test_files.update(src_path.rglob(p))
# Also check __tests__ directories
test_dirs = src_path.rglob('__tests__')
for test_dir in test_dirs:
if test_dir.is_dir():
for ext in ['.ts', '.tsx', '.js', '.jsx']:
test_files.update(test_dir.rglob(f'*{ext}'))
return test_files
def get_tested_sources(test_files: Set[Path], src_dir: str) -> Set[Path]:
"""Determine which source files have tests."""
src_path = Path(src_dir)
tested = set()
for test_file in test_files:
# Get the base name without .test/.spec
name = test_file.name
for pattern in ['.test.', '.spec.']:
name = name.replace(pattern, '.')
# Look for matching source file
# Check same directory
source_file = test_file.parent / name
if source_file.exists():
tested.add(source_file)
continue
# Check parent if in __tests__ directory
if '__tests__' in str(test_file):
parent_source = test_file.parent.parent / name
if parent_source.exists():
tested.add(parent_source)
return tested
def categorize_untested(untested: Set[Path], src_dir: str) -> dict:
"""Categorize untested files by type and importance."""
categories = {
'components': [],
'services': [],
'utils': [],
'api': [],
'hooks': [],
'models': [],
'other': []
}
for file in untested:
rel_path = file.relative_to(src_dir)
path_str = str(rel_path).lower()
if 'component' in path_str or 'components' in path_str:
categories['components'].append(file)
elif 'service' in path_str or 'services' in path_str:
categories['services'].append(file)
elif 'util' in path_str or 'utils' in path_str or 'helper' in path_str:
categories['utils'].append(file)
elif 'api' in path_str or 'endpoint' in path_str:
categories['api'].append(file)
elif 'hook' in path_str or 'hooks' in path_str:
categories['hooks'].append(file)
elif 'model' in path_str or 'models' in path_str or 'schema' in path_str:
categories['models'].append(file)
else:
categories['other'].append(file)
return categories
def print_report(source_files: Set[Path], test_files: Set[Path],
untested: Set[Path], src_dir: str):
"""Print formatted report."""
print("\n=== Test Coverage Analysis ===\n")
print(f"📁 Source directory: {src_dir}")
print(f"📄 Total source files: {len(source_files)}")
print(f"✅ Test files found: {len(test_files)}")
print(f"🔴 Untested files: {len(untested)}")
coverage_pct = ((len(source_files) - len(untested)) / len(source_files) * 100) if source_files else 0
print(f"📊 Test file coverage: {coverage_pct:.1f}%\n")
if untested:
print("=== Untested Files by Category ===\n")
categories = categorize_untested(untested, src_dir)
# Priority order
priority_order = ['api', 'services', 'models', 'hooks', 'components', 'utils', 'other']
for category in priority_order:
files = categories[category]
if files:
print(f"{category.upper()} ({len(files)} files):")
for file in sorted(files)[:10]: # Show max 10 per category
rel_path = file.relative_to(src_dir)
print(f" - {rel_path}")
if len(files) > 10:
print(f" ... and {len(files) - 10} more")
print()
print("\n💡 Recommendations:")
print(" 1. Prioritize testing API endpoints and services")
print(" 2. Add tests for business logic and data models")
print(" 3. Test custom hooks for correct behavior")
print(" 4. Consider testing complex components")
print(" 5. Utils can be tested as needed\n")
else:
print("🎉 All source files have corresponding test files!\n")
def main():
src_dir = sys.argv[1] if len(sys.argv) > 1 else 'src'
pattern = 'test'
if '--pattern' in sys.argv:
idx = sys.argv.index('--pattern')
if idx + 1 < len(sys.argv):
pattern = sys.argv[idx + 1]
if not Path(src_dir).exists():
print(f"Error: Source directory not found: {src_dir}")
print("\nUsage: python find_untested_code.py [src-dir] [--pattern test|spec]")
sys.exit(1)
source_files = find_source_files(src_dir)
test_files = find_test_files(src_dir, pattern)
tested_sources = get_tested_sources(test_files, src_dir)
untested = source_files - tested_sources
print_report(source_files, test_files, untested, src_dir)
if __name__ == '__main__':
main()
Related skills
FAQ
What are the test-specialist debugging steps?
test-specialist follows a 5-step bug analysis approach covering reproduction, required environment and state, root-cause isolation, fix implementation, and post-fix verification.
Is test-specialist only for Claude agents?
test-specialist is packaged as a Claude AI skill in ai-labs-claude-skills v1.0.0, but the systematic reproduction and verification framework applies to any agent-assisted debugging session.
Is Test Specialist safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.