skills/i2oland/dotfiles/test-design

test-design

SKILL.md

Testing Strategies

Roleplay as a development specialist that provides comprehensive testing methodology including test pyramid structure, coverage targets, framework-specific patterns, and test organization conventions.

TestDesign { Activation { When designing test suites for new features or projects When reviewing test coverage and identifying gaps When implementing unit, integration, or E2E tests When establishing test naming and organization conventions When selecting appropriate testing frameworks When planning test automation strategies }

Constraints { Test behavior, not implementation One assertion per behavior (multiple related assertions acceptable) Tests must be independent and not share mutable state Run tests before committing; never commit failing tests Keep unit tests under 100ms execution time Delete flaky tests or fix them immediately }

TestPyramid { /\ / \ 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

DistributionGuidelines {
  | 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      |
}

}

CorePatterns { TestBehaviorNotImplementation { Tests should verify observable behavior, not internal implementation details

  ```typescript
  // 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);
    });
  });
  ```
}

ArrangeActAssertStructure {
  Every test follows the AAA pattern for clarity and consistency

  ```python
  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"}
      ]
  ```
}

OneAssertionPerBehavior {
  Each test verifies one specific behavior
  Multiple assertions acceptable when verifying a single logical outcome

  ```typescript
  // 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
  });
  ```
}

DescriptiveTestNames {
  Test names describe the scenario and expected outcome

  ```typescript
  // 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')
  ```
}

TestIsolation {
  Tests must be independent and not share mutable state

  ```python
  # 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!
  ```
}

}

CoverageTargets { RecommendedCoverageByCodeType { | 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 | }

CoverageQualityOverQuantity {
  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
}

}

FrameworkSpecificPatterns { Jest { ```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)
  ```
}

ReactTestingLibrary {
  ```typescript
  // 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();
    });
  });
  ```
}

}

TestOrganization { FileNamingConventions { | 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 | }

DirectoryStructurePatterns {
  CoLocatedTests {
    Preferred for unit tests:
    ```
    src/
      services/
        UserService.ts
        UserService.test.ts
      utils/
        validators.ts
        validators.test.ts
    ```
  }

  SeparateTestDirectory {
    For integration/E2E:
    ```
    src/
      services/
        UserService.ts
    tests/
      unit/
        services/
          UserService.test.ts
      integration/
        api/
          users.test.ts
      e2e/
        user-registration.spec.ts
    ```
  }
}

}

BestPractices { - 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 }

AntiPatterns { TestingImplementationDetails { typescript // 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); }); }

SharedMutableState {
  ```python
  # 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
  ```
}

OverMocking {
  ```typescript
  // 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);
  });
  ```
}

TestDuplication {
  ```python
  # 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
Weekly Installs
1
First Seen
12 days ago
Installed on
amp1
cline1
opencode1
cursor1
kimi-cli1
codex1