shinpr

testing-principles

102
25
# Install this skill:
npx skills add shinpr/claude-code-workflows --skill "testing-principles"

Install specific skill from multi-skill repository

# Description

Language-agnostic testing principles including TDD, test quality, coverage standards, and test design patterns. Use when writing tests, designing test strategies, or reviewing test quality.

# SKILL.md


name: testing-principles
description: Language-agnostic testing principles including TDD, test quality, coverage standards, and test design patterns. Use when writing tests, designing test strategies, or reviewing test quality.


Language-Agnostic Testing Principles

Core Testing Philosophy

  1. Tests are First-Class Code: Maintain test quality equal to production code
  2. Fast Feedback: Tests should run quickly and provide immediate feedback
  3. Reliability: Tests should be deterministic and reproducible
  4. Independence: Each test should run in isolation

Test-Driven Development (TDD)

The RED-GREEN-REFACTOR Cycle

Always follow this cycle:

  1. RED: Write a failing test first
  2. Write the test before implementation
  3. Ensure the test fails for the right reason
  4. Verify test can actually fail

  5. GREEN: Write minimal code to pass

  6. Implement just enough to make the test pass
  7. Don't optimize prematurely
  8. Focus on making it work

  9. REFACTOR: Improve code structure

  10. Clean up implementation
  11. Eliminate duplication
  12. Improve naming and clarity
  13. Keep all tests passing

  14. VERIFY: Ensure all tests still pass

  15. Run full test suite
  16. Check for regressions
  17. Validate refactoring didn't break anything

TDD Benefits

  • Better design through testability requirements
  • Comprehensive test coverage by default
  • Living documentation of expected behavior
  • Confidence to refactor

Quality Requirements

Coverage Standards

  • Minimum 80% code coverage for production code
  • Prioritize critical paths and business logic
  • Don't sacrifice quality for coverage percentage
  • Use coverage as a guide, not a goal

Test Characteristics

All tests must be:

  • Independent: No dependencies between tests
  • Reproducible: Same input always produces same output
  • Fast: Complete test suite runs in reasonable time
  • Self-checking: Clear pass/fail without manual verification
  • Timely: Written close to the code they test

Test Types

Unit Tests

Purpose: Test individual components in isolation

Characteristics:
- Test single function, method, or class
- Fast execution (milliseconds)
- No external dependencies
- Mock external services
- Majority of your test suite

Example Scope:

βœ“ Test calculateTotal() function
βœ“ Test UserValidator class
βœ“ Test parseDate() utility

Integration Tests

Purpose: Test interactions between components

Characteristics:
- Test multiple components together
- May include database, file system, or APIs
- Slower than unit tests
- Verify contracts between modules
- Smaller portion of test suite

Example Scope:

βœ“ Test UserService with Database
βœ“ Test API endpoint with authentication
βœ“ Test file processing pipeline

End-to-End (E2E) Tests

Purpose: Test complete workflows from user perspective

Characteristics:
- Test entire application stack
- Simulate real user interactions
- Slowest test type
- Fewest in number
- Highest confidence level

Example Scope:

βœ“ Test user registration flow
βœ“ Test checkout process
βœ“ Test complete report generation

Test Pyramid

Follow the test pyramid structure:

    /\    ← Few E2E Tests (High confidence, slow)
   /  \
  /    \  ← Some Integration Tests (Medium confidence, medium speed)
 /      \
/________\ ← Many Unit Tests (Fast, foundational)

Test Design Principles

AAA Pattern (Arrange-Act-Assert)

Structure every test in three clear phases:

// Arrange: Setup test data and conditions
user = createTestUser()
validator = createValidator()

// Act: Execute the code under test
result = validator.validate(user)

// Assert: Verify expected outcome
assert(result.isValid == true)

Adaptation: Apply this structure using your language's idioms (methods, functions, procedures)

One Assertion Per Concept

  • Test one behavior per test case
  • Multiple assertions OK if testing single concept
  • Split unrelated assertions into separate tests

Good:

test("validates user email format")
test("validates user age is positive")
test("validates required fields are present")

Bad:

test("validates user") // Tests everything at once

Descriptive Test Names

Test names should clearly describe:
- What is being tested
- Under what conditions
- What the expected outcome is

Recommended format: "should [expected behavior] when [condition]"

Examples:

test("should return error when email is invalid")
test("should calculate discount when user is premium")
test("should throw exception when file not found")

Adaptation: Follow your project's naming convention (camelCase, snake_case, describe/it blocks)

Test Independence

Isolation Requirements

  • No shared state: Each test creates its own data
  • No execution order dependency: Tests pass in any order
  • Clean up after tests: Reset state, close connections
  • Avoid global variables: Use local test data

Setup and Teardown

  • Use setup hooks to prepare test environment
  • Use teardown hooks to clean up resources
  • Keep setup minimal and focused
  • Ensure teardown runs even if test fails

Mocking and Test Doubles

When to Use Mocks

  • Mock external dependencies: APIs, databases, file systems
  • Mock slow operations: Network calls, heavy computations
  • Mock unpredictable behavior: Random values, current time
  • Mock unavailable services: Third-party services

Mocking Principles

  • Mock at boundaries, not internally
  • Keep mocks simple and focused
  • Verify mock expectations when relevant
  • Don't mock external libraries/frameworks you don't control (prefer adapters)

Types of Test Doubles

  • Stub: Returns predetermined values
  • Mock: Verifies it was called correctly
  • Spy: Records information about calls
  • Fake: Simplified working implementation
  • Dummy: Passed but never used

Test Quality Practices

Keep Tests Active

  • Fix or delete failing tests: Resolve failures immediately
  • Remove commented-out tests: Fix them or delete entirely
  • Keep tests running: Broken tests lose value quickly
  • Maintain test suite: Refactor tests as needed

Test Code Quality

  • Apply same standards as production code
  • Use descriptive variable names
  • Extract test helpers to reduce duplication
  • Keep tests readable and maintainable
  • Review test code thoroughly

Test Helpers and Utilities

  • Create reusable test data builders
  • Extract common setup into helper functions
  • Build test utilities for complex scenarios
  • Share helpers across test files appropriately

What to Test

Focus on Behavior

Test observable behavior, not implementation:

βœ“ Good: Test that function returns expected output
βœ“ Good: Test that correct API endpoint is called
βœ— Bad: Test that internal variable was set
βœ— Bad: Test order of private method calls

Test Public APIs

  • Test through public interfaces
  • Avoid testing private methods directly
  • Test return values, outputs, exceptions
  • Test side effects (database, files, logs)

Test Edge Cases

Always test:
- Boundary conditions: Min/max values, empty collections
- Error cases: Invalid input, null values, missing data
- Edge cases: Special characters, extreme values
- Happy path: Normal, expected usage

Test Quality Criteria

These criteria ensure reliable, maintainable tests.

Literal Expected Values

  • Use hardcoded literal values in assertions
  • Calculate expected values independently from the implementation
  • If the implementation has a bug, the test catches it through independent verification
  • If expected value equals mock return value unchanged, the test verifies nothing (no transformation occurred)

Result-Based Verification

  • Verify final results and observable outcomes
  • Assert on return values, output data, or system state changes
  • For mock verification, check that correct arguments were passed

Meaningful Assertions

  • Every test must include at least one assertion
  • Assertions must validate observable behavior
  • A test without assertions always passes and provides no value

Appropriate Mock Scope

  • Mock direct external I/O dependencies: databases, HTTP clients, file systems
  • Use real implementations for internal utilities and business logic
  • Over-mocking reduces test value by verifying wiring instead of behavior

Boundary Value Testing

Test at boundaries of valid input ranges:
- Minimum valid value
- Maximum valid value
- Just below minimum (invalid)
- Just above maximum (invalid)
- Empty input (where applicable)

Test Independence Verification

Each test must:
- Create its own test data
- Not depend on execution order
- Clean up its own state
- Pass when run in isolation

Verification Requirements

Before Commit

  • βœ“ All tests pass
  • βœ“ No tests skipped or commented
  • βœ“ No debug code left in tests
  • βœ“ Test coverage meets standards
  • βœ“ Tests run in reasonable time

Zero Tolerance Policy

  • Zero failing tests: Fix immediately
  • Zero skipped tests: Delete or fix
  • Zero flaky tests: Make deterministic
  • Zero slow tests: Optimize or split

Test Organization

File Structure

  • Mirror production structure: Tests follow code organization
  • Clear naming conventions: Follow project's test file patterns
  • Examples: UserService.test.*, user_service_test.*, test_user_service.*, UserServiceTests.*
  • Logical grouping: Group related tests together
  • Separate test types: Unit, integration, e2e in separate directories

Test Suite Organization

tests/
β”œβ”€β”€ unit/           # Fast, isolated unit tests
β”œβ”€β”€ integration/    # Integration tests
β”œβ”€β”€ e2e/            # End-to-end tests
β”œβ”€β”€ fixtures/       # Test data and fixtures
└── helpers/        # Shared test utilities

Performance Considerations

Test Speed

  • Unit tests: < 100ms each
  • Integration tests: < 1s each
  • Full suite: Should run frequently (< 10 minutes)

Optimization Strategies

  • Run tests in parallel when possible
  • Use in-memory databases for tests
  • Mock expensive operations
  • Split slow test suites
  • Profile and optimize slow tests

Continuous Integration

CI/CD Requirements

  • Run full test suite on every commit
  • Block merges if tests fail
  • Run tests in isolated environments
  • Test on target platforms/versions

Test Reports

  • Generate coverage reports
  • Track test execution time
  • Identify flaky tests
  • Monitor test trends

Common Anti-Patterns to Avoid

Test Smells

  • βœ— Tests that test nothing (always pass)
  • βœ— Tests that depend on execution order
  • βœ— Tests that depend on external state
  • βœ— Tests with complex logic (tests shouldn't need tests)
  • βœ— Testing implementation details
  • βœ— Excessive mocking (mocking everything)
  • βœ— Test code duplication

Flaky Tests

Eliminate tests that fail intermittently:
- Remove timing dependencies
- Avoid random data in tests
- Ensure proper cleanup
- Fix race conditions
- Make tests deterministic

Regression Testing

Prevent Regressions

  • Add test for every bug fix
  • Maintain comprehensive test suite
  • Run full suite regularly
  • Don't delete tests without good reason

Legacy Code

  • Add characterization tests before refactoring
  • Test existing behavior first
  • Gradually improve coverage
  • Refactor with confidence

Testing Best Practices by Language Paradigm

Type System Utilization

For languages with static type systems:
- Leverage compile-time verification for correctness
- Focus tests on business logic and runtime behavior
- Use language's type system to prevent invalid states

For languages with dynamic typing:
- Add comprehensive runtime validation tests
- Explicitly test data contract validation
- Consider property-based testing for broader coverage

Programming Paradigm Considerations

Functional approach:
- Test pure functions thoroughly (deterministic, no side effects)
- Test side effects at system boundaries
- Leverage property-based testing for invariants

Object-oriented approach:
- Test behavior through public interfaces
- Mock dependencies via abstraction layers
- Test polymorphic behavior carefully

Common principle: Adapt testing strategy to leverage language strengths while ensuring comprehensive coverage

Documentation and Communication

Tests as Documentation

  • Tests document expected behavior
  • Use clear, descriptive test names
  • Include examples of usage
  • Show edge cases and error handling

Test Failure Messages

  • Provide clear, actionable error messages
  • Include actual vs expected values
  • Add context about what was being tested
  • Make debugging easier

Continuous Improvement

Review and Refactor Tests

  • Refactor tests as you refactor code
  • Remove obsolete tests
  • Improve test clarity
  • Update tests for new patterns

Learn from Failures

  • Analyze test failures thoroughly
  • Add tests for discovered edge cases
  • Improve test coverage where gaps found
  • Share learnings with team

# Supported AI Coding Agents

This skill is compatible with the SKILL.md standard and works with all major AI coding agents:

Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.