AgentSkillsCN

test-architect

打造全面且具备生产级水准的测试套件,以有意义的覆盖率为核心。适用于以下场景:(1) 编写新的测试文件或测试套件;(2) 提升现有代码的测试覆盖率;(3) 用户提及“测试”、“测试工作”、“编写测试”、“测试覆盖率”或“单元测试”时;(4) 创建集成测试、端到端测试,或 API 测试;(5) 通过测试验证代码质量;(6) 搭建测试基础设施;或 (7) 当用户明确希望测试不仅能覆盖表面细节,更能真正捕获真实缺陷时。

SKILL.md
--- frontmatter
name: test-architect
description: Elite testing skill for creating comprehensive, production-grade test suites with meaningful coverage. Use when (1) writing new test files or test suites, (2) improving test coverage for existing code, (3) the user requests "test", "testing", "write tests", "test coverage", or "unit tests", (4) creating integration, e2e, or API tests, (5) validating code quality through testing, (6) setting up testing infrastructure, or (7) the user explicitly wants tests that catch real bugs, not just superficial coverage.

Test Architect

Elite testing skill that transforms test writing into a systematic, production-grade process. Every test is written to catch real bugs and protect against regressions—not just to satisfy coverage metrics.

Core Philosophy

"A test that doesn't catch bugs is just noise in your CI pipeline."

This skill embodies the Quality Over Quantity paradigm: meaningful tests that verify behavior, not just lines covered. Every test decision is made with the understanding that:

  1. Tests are documentation — They show how code should behave
  2. Tests catch regressions — They prevent bugs from returning
  3. Tests enable refactoring — They provide safety nets for changes
  4. Tests validate edge cases — They verify behavior under unusual conditions

Test Writing Process

Test architecture involves these sequential steps:

  1. Analyze Code → Understand what needs testing and its complexity
  2. Identify Test Categories → Determine which test types are needed
  3. Design Test Structure → Plan test organization and fixtures
  4. Write Tests Systematically → Apply the testing pyramid approach
  5. Validate Coverage Quality → Ensure meaningful coverage, not just line coverage

Test Categories

Determine the appropriate test types based on the code being tested:

Code TypePrimary TestsReference
Business logic / Pure functionsUnit Testsunit-testing.md
API endpoints / RoutesAPI Testsapi-testing.md
Database operations / ServicesIntegration Testsintegration-testing.md
User flows / Critical pathsE2E Testse2e-testing.md
React components / UIComponent Testsfrontend-testing.md
State management / HooksHook Testsfrontend-testing.md

The Testing Pyramid

Apply the testing pyramid principle for optimal coverage:

code
          ┌─────────┐
          │  E2E    │  ← Few, slow, verify critical paths
         ┌┴─────────┴┐
         │Integration│  ← Some, verify component interactions
        ┌┴───────────┴┐
        │ Unit Tests  │  ← Many, fast, verify isolated logic
        └─────────────┘

Test Quality Standards

The 4 Pillars of a Good Test

  1. Arrangement Clarity — Setup is clear and minimal
  2. Action Precision — Exactly one behavior is tested
  3. Assertion Specificity — Assertions verify exact expectations
  4. Isolation — Tests don't depend on other tests or shared state

Test Naming Convention

Use descriptive, behavior-focused names:

python
# Python - Use underscore_case with behavior description
class TestUserService:
    def test_create_user_with_valid_data_returns_user_id(self):
        # Follows: test_[method]_[scenario]_[expected_result]
        pass
    
    def test_create_user_with_duplicate_email_raises_conflict_error(self):
        pass
typescript
// TypeScript - Use camelCase with behavior description
describe('UserService', () => {
  describe('createUser', () => {
    it('should return user id when data is valid', () => {
      // Follows: should [expected result] when [scenario]
    });
    
    it('should throw ConflictError when email already exists', () => {
    });
  });
});

Pre-Testing Checklist

Before writing ANY tests, verify:

code
□ Do I understand WHAT the code should do (requirements)?
□ Do I understand HOW the code does it (implementation)?
□ Have I identified ALL logical branches?
□ Have I identified edge cases (null, empty, boundary values)?
□ Have I identified error conditions?
□ What external dependencies need mocking?
□ What is the minimum test fixture needed?

The Test Architect's 7 Commandments

1. Test Behavior, Not Implementation

  • Focus on WHAT the code does, not HOW
  • Tests should survive refactoring
  • Avoid testing private methods directly

2. One Assertion Per Concept

  • Each test verifies one logical concept
  • Multiple related assertions are OK (same concept)
  • Avoid unrelated assertions in one test

3. Test Edge Cases First

  • Null/undefined inputs
  • Empty collections/strings
  • Boundary values (0, -1, MAX_INT)
  • Invalid state combinations

4. Mock at Boundaries

  • Mock external services (APIs, databases, file system)
  • Don't over-mock (test real code)
  • Use dependency injection for testability

5. Deterministic Tests Only

  • No random values without seeding
  • No tests that depend on time
  • No tests that depend on network
  • Always use fixed test data

6. Fast Feedback Loop

  • Unit tests < 100ms each
  • Integration tests < 1s each
  • E2E tests < 10s each
  • Parallelize where possible

7. Meaningful Assertions

  • Assert specific values, not just "no error"
  • Assert data shape and content
  • Assert side effects (function calls, state changes)
  • Assert error messages, not just error types

Coverage Quality Metrics

Track these metrics beyond line coverage:

MetricTargetDescription
Line Coverage>85%Percentage of lines executed
Branch Coverage>80%Percentage of conditional branches
Path Coverage>70%Percentage of execution paths
Mutation Score>60%Percentage of mutations killed

Test Structure Templates

Python (Pytest)

python
"""Tests for [module_name].

Test coverage for:
- Happy path scenarios
- Edge cases
- Error handling
- Boundary conditions
"""

from __future__ import annotations

import pytest
from unittest.mock import Mock, patch, MagicMock
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from _pytest.fixtures import FixtureRequest


# ============================================================================
# Fixtures
# ============================================================================

@pytest.fixture
def sample_user_data() -> dict:
    """Provide minimal valid user data for testing."""
    return {
        "username": "testuser",
        "email": "test@example.com",
        "password": "SecurePass123!",
    }


@pytest.fixture
def mock_database() -> Mock:
    """Provide a mock database session."""
    mock_db = Mock()
    mock_db.commit = Mock()
    mock_db.rollback = Mock()
    return mock_db


# ============================================================================
# Unit Tests
# ============================================================================

class TestUserCreation:
    """Tests for user creation functionality."""

    # Happy Path Tests
    def test_create_user_with_valid_data_returns_user_object(
        self, sample_user_data: dict, mock_database: Mock
    ) -> None:
        """Verify user creation with valid data returns a User object."""
        # Arrange
        service = UserService(db=mock_database)
        
        # Act
        result = service.create_user(**sample_user_data)
        
        # Assert
        assert result is not None
        assert result.username == sample_user_data["username"]
        assert result.email == sample_user_data["email"]
        mock_database.commit.assert_called_once()

    # Edge Case Tests
    @pytest.mark.parametrize("invalid_email", [
        "",           # Empty
        None,         # Null
        "notanemail", # No @ symbol
        "@nodomain",  # No local part
        "no@",        # No domain
    ])
    def test_create_user_with_invalid_email_raises_validation_error(
        self, invalid_email: str | None, sample_user_data: dict
    ) -> None:
        """Verify invalid emails are rejected with appropriate error."""
        # Arrange
        sample_user_data["email"] = invalid_email
        service = UserService(db=Mock())
        
        # Act & Assert
        with pytest.raises(ValidationError) as exc_info:
            service.create_user(**sample_user_data)
        
        assert "email" in str(exc_info.value).lower()

    # Error Handling Tests
    def test_create_user_rolls_back_on_database_error(
        self, sample_user_data: dict, mock_database: Mock
    ) -> None:
        """Verify database errors trigger rollback."""
        # Arrange
        mock_database.commit.side_effect = DatabaseError("Connection lost")
        service = UserService(db=mock_database)
        
        # Act & Assert
        with pytest.raises(DatabaseError):
            service.create_user(**sample_user_data)
        
        mock_database.rollback.assert_called_once()


# ============================================================================
# Integration Tests (with real dependencies)
# ============================================================================

class TestUserServiceIntegration:
    """Integration tests with real database."""

    @pytest.fixture(autouse=True)
    def setup_database(self, test_database: Database) -> None:
        """Set up clean database for each test."""
        self.db = test_database
        self.db.clear_all_tables()

    def test_user_creation_persists_to_database(
        self, sample_user_data: dict
    ) -> None:
        """Verify created users are persisted and retrievable."""
        # Arrange
        service = UserService(db=self.db)
        
        # Act
        created_user = service.create_user(**sample_user_data)
        retrieved_user = service.get_user_by_id(created_user.id)
        
        # Assert
        assert retrieved_user is not None
        assert retrieved_user.email == sample_user_data["email"]

TypeScript/React (Vitest)

typescript
/**
 * Tests for UserService
 * 
 * Coverage:
 * - Happy path scenarios
 * - Edge cases
 * - Error handling
 * - Async operations
 */

import { describe, it, expect, beforeEach, vi } from 'vitest';
import { renderHook, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

import { useUserProfile } from './useUserProfile';
import { api } from '../services/api';

// ============================================================================
// Mocks
// ============================================================================

vi.mock('../services/api', () => ({
  api: {
    get: vi.fn(),
    post: vi.fn(),
  },
}));

// ============================================================================
// Test Utilities
// ============================================================================

const createTestQueryClient = () =>
  new QueryClient({
    defaultOptions: {
      queries: {
        retry: false,
      },
    },
  });

const createWrapper = () => {
  const queryClient = createTestQueryClient();
  return ({ children }: { children: React.ReactNode }) => (
    <QueryClientProvider client={queryClient}>
      {children}
    </QueryClientProvider>
  );
};

// ============================================================================
// Fixtures
// ============================================================================

const mockUserProfile = {
  id: 'user-123',
  email: 'test@example.com',
  displayName: 'Test User',
  avatarUrl: null,
  createdAt: new Date('2024-01-01'),
};

// ============================================================================
// Tests
// ============================================================================

describe('useUserProfile', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe('Happy Path', () => {
    it('should return profile data when fetch succeeds', async () => {
      // Arrange
      vi.mocked(api.get).mockResolvedValueOnce({ data: mockUserProfile });

      // Act
      const { result } = renderHook(
        () => useUserProfile({ userId: 'user-123' }),
        { wrapper: createWrapper() }
      );

      // Assert - initial loading state
      expect(result.current.isLoading).toBe(true);
      expect(result.current.profile).toBeNull();

      // Assert - after fetch completes
      await waitFor(() => {
        expect(result.current.isLoading).toBe(false);
      });

      expect(result.current.profile).toEqual(mockUserProfile);
      expect(result.current.error).toBeNull();
    });
  });

  describe('Edge Cases', () => {
    it('should handle empty userId gracefully', async () => {
      // Act
      const { result } = renderHook(
        () => useUserProfile({ userId: '' }),
        { wrapper: createWrapper() }
      );

      // Assert
      await waitFor(() => {
        expect(result.current.error).toBeTruthy();
      });

      expect(result.current.error?.message).toContain('Invalid userId');
    });

    it('should handle null avatarUrl in profile', async () => {
      // Arrange
      vi.mocked(api.get).mockResolvedValueOnce({
        data: { ...mockUserProfile, avatarUrl: null },
      });

      // Act
      const { result } = renderHook(
        () => useUserProfile({ userId: 'user-123' }),
        { wrapper: createWrapper() }
      );

      // Assert
      await waitFor(() => {
        expect(result.current.profile).toBeTruthy();
      });

      expect(result.current.profile?.avatarUrl).toBeNull();
    });
  });

  describe('Error Handling', () => {
    it('should set error state when API call fails', async () => {
      // Arrange
      const apiError = new Error('Network error');
      vi.mocked(api.get).mockRejectedValueOnce(apiError);

      // Act
      const { result } = renderHook(
        () => useUserProfile({ userId: 'user-123' }),
        { wrapper: createWrapper() }
      );

      // Assert
      await waitFor(() => {
        expect(result.current.error).toBeTruthy();
      });

      expect(result.current.error?.message).toBe('Network error');
      expect(result.current.profile).toBeNull();
    });

    it('should call onError callback when fetch fails', async () => {
      // Arrange
      const onError = vi.fn();
      vi.mocked(api.get).mockRejectedValueOnce(new Error('API Error'));

      // Act
      renderHook(
        () => useUserProfile({ userId: 'user-123', onError }),
        { wrapper: createWrapper() }
      );

      // Assert
      await waitFor(() => {
        expect(onError).toHaveBeenCalledTimes(1);
      });

      expect(onError).toHaveBeenCalledWith(expect.any(Error));
    });
  });

  describe('Refresh Functionality', () => {
    it('should refetch data when refresh is called', async () => {
      // Arrange
      vi.mocked(api.get)
        .mockResolvedValueOnce({ data: mockUserProfile })
        .mockResolvedValueOnce({
          data: { ...mockUserProfile, displayName: 'Updated Name' },
        });

      // Act
      const { result } = renderHook(
        () => useUserProfile({ userId: 'user-123' }),
        { wrapper: createWrapper() }
      );

      await waitFor(() => {
        expect(result.current.profile).toBeTruthy();
      });

      await result.current.refresh();

      // Assert
      await waitFor(() => {
        expect(result.current.profile?.displayName).toBe('Updated Name');
      });

      expect(api.get).toHaveBeenCalledTimes(2);
    });
  });
});

Anti-Patterns to Avoid

See anti-patterns.md for comprehensive test anti-pattern detection.

Quick Reference Card

code
┌─────────────────────────────────────────────────────────────────────┐
│                  TEST ARCHITECT QUICK REFERENCE                     │
├─────────────────────────────────────────────────────────────────────┤
│ BEFORE WRITING TESTS                                                │
│   □ Understand the code's purpose and requirements                  │
│   □ Identify all branches and edge cases                            │
│   □ Plan fixture strategy and mock boundaries                       │
│   □ Determine test categories (unit, integration, e2e)              │
├─────────────────────────────────────────────────────────────────────┤
│ WHILE WRITING TESTS                                                 │
│   □ Use descriptive test names (behavior-focused)                   │
│   □ Follow Arrange-Act-Assert pattern                               │
│   □ One logical concept per test                                    │
│   □ Keep tests deterministic (no random, no time-dependent)         │
│   □ Mock external dependencies only                                 │
│   □ Test edge cases: null, empty, boundary values                   │
│   □ Test error scenarios explicitly                                 │
├─────────────────────────────────────────────────────────────────────┤
│ COVERAGE FOCUS                                                      │
│   □ Happy path (basic functionality works)                          │
│   □ Edge cases (unusual but valid inputs)                           │
│   □ Error handling (invalid inputs, failures)                       │
│   □ Boundary conditions (limits, transitions)                       │
│   □ State transitions (before/after operations)                     │
├─────────────────────────────────────────────────────────────────────┤
│ TEST QUALITY                                                        │
│   □ Tests are fast (<100ms for unit tests)                          │
│   □ Tests are isolated (no shared state)                            │
│   □ Tests are repeatable (same result every time)                   │
│   □ Tests are self-validating (pass/fail is clear)                  │
│   □ Tests are timely (written with the code)                        │
├─────────────────────────────────────────────────────────────────────┤
│ FINAL CHECK                                                         │
│   □ Would this test catch a regression?                             │
│   □ Would this test survive a refactoring?                          │
│   □ Is this testing behavior or implementation?                     │
│   □ Would I trust this test to protect production?                  │
└─────────────────────────────────────────────────────────────────────┘

Activation Protocol

When Test Architect mode is activated:

  1. Analyze the target code — Understand what needs testing
  2. Identify coverage gaps — Find untested paths and edge cases
  3. Choose test types — Unit, integration, e2e as appropriate
  4. Design minimal fixtures — Only what's needed for the tests
  5. Write tests systematically — Happy path → edge cases → errors
  6. Validate coverage quality — Branch coverage, not just line coverage

Remember: The goal isn't 100% coverage—it's meaningful tests that catch bugs and prevent regressions. Tests that your future self (and teammates) will trust.