Claude Code Plugins

Community-maintained marketplace

Feedback

testing-strategies

@rsmdt/the-startup
127
0

Apply test pyramid principles, coverage targets, and framework-specific patterns. Use when designing test suites, reviewing test coverage, or implementing tests. Covers Jest, Pytest, and common testing frameworks with naming conventions and organization patterns.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name testing-strategies
description Apply test pyramid principles, coverage targets, and framework-specific patterns. Use when designing test suites, reviewing test coverage, or implementing tests. Covers Jest, Pytest, and common testing frameworks with naming conventions and organization patterns.

Testing Strategies

A development skill that provides comprehensive testing methodology including test pyramid structure, coverage targets, framework-specific patterns, and test organization conventions.

When to Use

  • Designing test suites for new features or projects
  • Reviewing test coverage and identifying gaps
  • Implementing unit, integration, or E2E tests
  • Establishing test naming and organization conventions
  • Selecting appropriate testing frameworks
  • Planning test automation strategies

Test Pyramid

The test pyramid guides test distribution for optimal feedback speed and confidence:

        /\
       /  \        E2E Tests (5-10%)
      /----\       - Critical user journeys
     /      \      - Slow, expensive, flaky-prone
    /--------\
   /          \    Integration Tests (20-30%)
  /  Service   \   - API contracts, database
 /--------------\  - Component interactions
/                \
/   Unit Tests    \  Unit Tests (60-70%)
/==================\ - Fast, isolated, deterministic
                     - Business logic focus

Distribution Guidelines

Test Type Target % Execution Time Scope
Unit 60-70% < 100ms each Single function/class
Integration 20-30% < 5s each Service boundaries
E2E 5-10% < 30s each Critical user paths

Core Patterns

Pattern 1: Test Behavior, Not Implementation

Tests should verify observable behavior, not internal implementation details.

// CORRECT: Tests behavior
describe('ShoppingCart', () => {
  it('calculates total with quantity discounts', () => {
    const cart = new ShoppingCart();
    cart.add({ sku: 'WIDGET', price: 10, quantity: 5 });

    expect(cart.total).toBe(45); // 10% discount for 5+ items
  });
});

// INCORRECT: Tests implementation
describe('ShoppingCart', () => {
  it('calls _applyDiscount method', () => {
    const cart = new ShoppingCart();
    const spy = jest.spyOn(cart, '_applyDiscount');

    cart.add({ sku: 'WIDGET', price: 10, quantity: 5 });

    expect(spy).toHaveBeenCalledWith(0.1);
  });
});

Pattern 2: Arrange-Act-Assert Structure

Every test follows the AAA pattern for clarity and consistency.

def test_user_registration_sends_welcome_email():
    # Arrange
    email_service = MockEmailService()
    user_service = UserService(email_service=email_service)
    registration_data = {"email": "new@user.com", "name": "New User"}

    # Act
    user_service.register(registration_data)

    # Assert
    assert email_service.sent_emails == [
        {"to": "new@user.com", "template": "welcome"}
    ]

Pattern 3: One Assertion Per Behavior

Each test verifies one specific behavior. Multiple assertions are acceptable when verifying a single logical outcome.

// CORRECT: One behavior, multiple related assertions
it('creates order with correct initial state', () => {
  const order = createOrder(items);

  expect(order.status).toBe('pending');
  expect(order.items).toHaveLength(items.length);
  expect(order.createdAt).toBeInstanceOf(Date);
});

// INCORRECT: Multiple unrelated behaviors
it('creates and processes order', () => {
  const order = createOrder(items);
  expect(order.status).toBe('pending');

  processPayment(order);
  expect(order.status).toBe('paid');  // Different behavior
});

Pattern 4: Descriptive Test Names

Test names describe the scenario and expected outcome.

// Format: [unit]_[scenario]_[expected outcome]
// or: [action]_[condition]_[result]

// CORRECT
it('calculateTotal returns zero for empty cart')
it('validateEmail rejects addresses without @ symbol')
it('UserService throws NotFoundError when user does not exist')

// INCORRECT
it('test calculateTotal')
it('validateEmail works')
it('error handling')

Pattern 5: Test Isolation

Tests must be independent and not share mutable state.

# CORRECT: Fresh fixture per test
class TestOrderService:
    def setup_method(self):
        self.db = InMemoryDatabase()
        self.service = OrderService(self.db)

    def test_create_order(self):
        order = self.service.create(items=[...])
        assert order.id is not None

    def test_list_orders_empty(self):
        orders = self.service.list()
        assert orders == []

# INCORRECT: Shared state between tests
db = Database()  # Shared!

def test_create_order():
    order = OrderService(db).create(items=[...])

def test_list_orders_empty():
    orders = OrderService(db).list()  # May see order from previous test!

Coverage Targets

Recommended Coverage by Code Type

Code Type Statement Branch Target
Business Logic 90% 85% High
API Controllers 80% 75% Medium
Utility Functions 95% 90% High
UI Components 70% 65% Medium
Generated Code N/A N/A Skip

Coverage Quality Over Quantity

Coverage percentage alone is insufficient. Prioritize:

  1. Critical paths: Authentication, payments, data integrity
  2. Edge cases: Boundaries, empty states, error conditions
  3. Regression prevention: Previously broken functionality
  4. Complex logic: High cyclomatic complexity areas

Framework-Specific Patterns

Jest (JavaScript/TypeScript)

// File structure
src/
  services/
    UserService.ts
    UserService.test.ts  // Co-located

// Test setup
describe('UserService', () => {
  let userService: UserService;
  let mockRepo: jest.Mocked<UserRepository>;

  beforeEach(() => {
    mockRepo = {
      findById: jest.fn(),
      save: jest.fn(),
    };
    userService = new UserService(mockRepo);
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  describe('getUser', () => {
    it('returns user when found', async () => {
      const expected = { id: '1', name: 'Alice' };
      mockRepo.findById.mockResolvedValue(expected);

      const result = await userService.getUser('1');

      expect(result).toEqual(expected);
      expect(mockRepo.findById).toHaveBeenCalledWith('1');
    });

    it('throws NotFoundError when user does not exist', async () => {
      mockRepo.findById.mockResolvedValue(null);

      await expect(userService.getUser('999'))
        .rejects.toThrow(NotFoundError);
    });
  });
});

Pytest (Python)

# File structure
src/
  services/
    user_service.py
tests/
  services/
    test_user_service.py  # Mirror structure

# conftest.py - Shared fixtures
import pytest
from unittest.mock import Mock

@pytest.fixture
def mock_user_repo():
    return Mock(spec=UserRepository)

@pytest.fixture
def user_service(mock_user_repo):
    return UserService(repository=mock_user_repo)

# test_user_service.py
class TestUserService:
    def test_get_user_returns_user_when_found(
        self, user_service, mock_user_repo
    ):
        expected = User(id="1", name="Alice")
        mock_user_repo.find_by_id.return_value = expected

        result = user_service.get_user("1")

        assert result == expected
        mock_user_repo.find_by_id.assert_called_once_with("1")

    def test_get_user_raises_not_found_when_missing(
        self, user_service, mock_user_repo
    ):
        mock_user_repo.find_by_id.return_value = None

        with pytest.raises(NotFoundError):
            user_service.get_user("999")

    @pytest.mark.parametrize("invalid_email", [
        "",
        "no-at-sign",
        "@no-local-part.com",
        "no-domain@",
    ])
    def test_validate_email_rejects_invalid_formats(
        self, user_service, invalid_email
    ):
        assert not user_service.validate_email(invalid_email)

React Testing Library

// Component test
import { render, screen, userEvent } from '@testing-library/react';
import { LoginForm } from './LoginForm';

describe('LoginForm', () => {
  it('calls onSubmit with credentials when form is submitted', async () => {
    const onSubmit = jest.fn();
    const user = userEvent.setup();

    render(<LoginForm onSubmit={onSubmit} />);

    await user.type(screen.getByLabelText('Email'), 'test@example.com');
    await user.type(screen.getByLabelText('Password'), 'secret123');
    await user.click(screen.getByRole('button', { name: 'Sign In' }));

    expect(onSubmit).toHaveBeenCalledWith({
      email: 'test@example.com',
      password: 'secret123',
    });
  });

  it('displays validation error for invalid email', async () => {
    const user = userEvent.setup();

    render(<LoginForm onSubmit={jest.fn()} />);

    await user.type(screen.getByLabelText('Email'), 'invalid');
    await user.click(screen.getByRole('button', { name: 'Sign In' }));

    expect(screen.getByText('Please enter a valid email')).toBeInTheDocument();
  });
});

Test Organization

File Naming Conventions

Framework Test File Pattern Example
Jest *.test.ts UserService.test.ts
Pytest test_*.py test_user_service.py
Go *_test.go user_service_test.go
JUnit *Test.java UserServiceTest.java

Directory Structure Patterns

Co-located tests (preferred for unit tests):

src/
  services/
    UserService.ts
    UserService.test.ts
  utils/
    validators.ts
    validators.test.ts

Separate test directory (for integration/E2E):

src/
  services/
    UserService.ts
tests/
  unit/
    services/
      UserService.test.ts
  integration/
    api/
      users.test.ts
  e2e/
    user-registration.spec.ts

Best Practices

  • Run tests before committing; never commit failing tests
  • Keep unit tests under 100ms execution time
  • Mock external dependencies at service boundaries only
  • Use factories or fixtures for test data, not raw literals
  • Delete flaky tests or fix them immediately
  • Review tests during code review with same rigor as production code
  • Name tests as specifications that document behavior
  • Prefer real implementations over mocks when practical
  • Test edge cases: nulls, empty collections, boundaries
  • Avoid conditional logic in tests

Anti-Patterns to Avoid

Testing Implementation Details

// WRONG: Brittle test that breaks on refactoring
it('stores user in _users array', () => {
  const service = new UserService();
  service.addUser(user);
  expect(service._users).toContain(user);
});

Shared Mutable State

# WRONG: Tests interfere with each other
users = []

def test_add_user():
    users.append(User())

def test_user_count():
    assert len(users) == 0  # Fails if test_add_user runs first

Over-Mocking

// WRONG: Mocking the system under test
it('processes payment', () => {
  const processor = new PaymentProcessor();
  jest.spyOn(processor, 'validate').mockReturnValue(true);
  jest.spyOn(processor, 'charge').mockResolvedValue({ success: true });

  // What are we even testing?
  const result = processor.process(payment);
});

Test Duplication

# WRONG: Copy-paste tests with minor variations
def test_validate_email_empty():
    assert not validate_email("")

def test_validate_email_no_at():
    assert not validate_email("invalid")

def test_validate_email_no_domain():
    assert not validate_email("user@")

# CORRECT: Parameterized test
@pytest.mark.parametrize("invalid_email", ["", "invalid", "user@"])
def test_validate_email_rejects_invalid_formats(invalid_email):
    assert not validate_email(invalid_email)

References

  • examples/test-pyramid.md - Detailed test pyramid implementation guide with framework examples