返回 Skills
ailabs-393/ai-labs-claude-skills· MIT 内容可用

test-specialist

This skill should be used when writing test cases, fixing bugs, analyzing code for potential issues, or improving test coverage for JavaScript/TypeScript applications. Use this for unit tests, integration tests, end-to-end tests, debugging runtime errors, logic bugs, performance issues, security vulnerabilities, and systematic code analysis.

安装

与 skills.sh 相同的 Command / Prompt 安装方式


name: test-specialist description: This skill should be used when writing test cases, fixing bugs, analyzing code for potential issues, or improving test coverage for JavaScript/TypeScript applications. Use this for unit tests, integration tests, end-to-end tests, debugging runtime errors, logic bugs, performance issues, security vulnerabilities, and systematic code analysis.

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 src

The 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.json

The 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.py to identify gaps
      • Run analyze_coverage.py on 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:e2e

Reference Documentation

For detailed patterns and techniques, refer to:

  • references/testing_patterns.md - Comprehensive testing patterns, best practices, and code examples
  • references/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

附带文件

index.js
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
  };
}
package.json
{
  "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"
}
references/bug_analysis.md
# 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:**
```typescript
// 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:**
```typescript
// 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:**
```typescript
// 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:**
```typescript
// 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:**
```typescript
// 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": true
```

### API Integration Errors

**Symptoms:**
- Unexpected response formats
- Network timeouts
- Authentication failures
- Rate limiting issues

**Detection Strategies:**
```typescript
// 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:**
```typescript
// 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:**
```typescript
// 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:**
```typescript
// 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
```typescript
// 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
```typescript
// 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 cases
```

### Binary Search Debugging
```typescript
// 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
```typescript
// 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
```typescript
// 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
```json
// 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
```typescript
// ❌ 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
```typescript
// ❌ 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
```typescript
// ❌ 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
```typescript
// ❌ 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);
});
```
references/testing_patterns.md
# 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

```typescript
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:
```typescript
const mockFn = jest.fn();
service.subscribe(mockFn);
service.notify('event');
expect(mockFn).toHaveBeenCalledWith('event');
```

#### Stubs
Use stubs to provide predetermined responses:
```typescript
const stub = jest.fn().mockReturnValue(42);
```

#### Spies
Use spies to track calls while preserving original implementation:
```typescript
const spy = jest.spyOn(object, 'method');
```

### Parameterized Tests
Test multiple scenarios efficiently:
```typescript
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:
```typescript
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:
```typescript
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:
```typescript
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:
```typescript
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:
```typescript
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:
```typescript
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:
```typescript
test.each([null, undefined, '', {}, []])
  ('handles invalid input: %p', (input) => {
    expect(() => processData(input)).toThrow();
  });
```

## Async Testing Patterns

### Promise Testing
```typescript
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
```typescript
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 timeout
```

## Snapshot Testing

Use snapshots for UI components and data structures:
```typescript
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.ts
```

### Test Naming
```typescript
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
```typescript
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
```typescript
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
```typescript
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
scripts/analyze_coverage.py
#!/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()
scripts/find_untested_code.py
#!/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()
    test-specialist | Prompt Minder