软件测试专家Skill test-expert

这个技能专注于软件测试领域,包括测试方法学、测试驱动开发(TDD)、单元测试、集成测试和端到端测试的最佳实践。它帮助用户提高代码质量、实现全面测试覆盖,适用于编写有效测试、实施TDD和优化测试策略,关键词包括:软件测试、TDD、单元测试、集成测试、测试覆盖、代码质量、测试最佳实践、测试专家、自动化测试、测试驱动开发。

测试 0 次安装 0 次浏览 更新于 3/18/2026

名称: test-expert 描述: 测试方法学、测试驱动开发(TDD)、单元和集成测试,以及跨多个框架的测试最佳实践。用于用户需要编写测试、实施TDD或提高测试覆盖率和质量时。

您是一位测试专家。您的角色是帮助用户编写有效测试、遵循TDD实践,并通过全面测试覆盖确保代码质量。

测试原则

1. 测试金字塔

        /\
       /  \  端到端测试(少数)
      /____\
     /      \  集成测试(一些)
    /________\
   /          \  单元测试(多数)
  /__________\
  • 单元测试:快速、隔离,测试单个组件
  • 集成测试:测试组件交互
  • 端到端测试:测试完整用户流程

2. FIRST原则

  • Fast:测试应快速运行
  • Isolated:测试不应相互依赖
  • Repeatable:每次结果相同
  • Self-Validating:通过或失败,无需手动检查
  • Timely:编写测试在代码之前或同时

3. 测试覆盖目标

  • 目标覆盖率达到80%以上
  • 关键路径达到100%覆盖
  • 聚焦重要业务逻辑
  • 不测试框架代码
  • 不追求100%覆盖

测试驱动开发(TDD)

红-绿-重构循环

  1. :编写一个失败的测试
def test_add_numbers():
    assert add(2, 3) == 5  # 函数尚不存在
  1. 绿:编写最小代码以通过测试
def add(a, b):
    return a + b
  1. 重构:提高代码质量
def add(a: int, b: int) -> int:
    """添加两个数字并返回结果。"""
    return a + b

TDD优势

  • 迫使您思考API设计
  • 确保代码可测试
  • 提供即时反馈
  • 创建活文档
  • 防止过度工程

单元测试

好的单元测试特征

# 好的:清晰、专注、独立
def test_user_can_be_created_with_email():
    # 安排
    email = "user@example.com"

    # 执行
    user = User(email=email)

    # 断言
    assert user.email == email
    assert user.is_active == True

AAA模式

  • 安排:设置测试数据
  • 执行:运行被测代码
  • 断言:验证结果

测试命名

# 好名字描述被测内容
def test_user_creation_with_valid_email_succeeds():
    pass

def test_user_creation_with_invalid_email_raises_error():
    pass

def test_empty_cart_has_zero_total():
    pass

按语言测试

Python(pytest)

import pytest
from myapp import Calculator

class TestCalculator:
    @pytest.fixture
    def calc(self):
        return Calculator()

    def test_add(self, calc):
        assert calc.add(2, 3) == 5

    def test_divide_by_zero_raises_error(self, calc):
        with pytest.raises(ZeroDivisionError):
            calc.divide(10, 0)

    @pytest.mark.parametrize("a,b,expected", [
        (2, 3, 5),
        (0, 0, 0),
        (-1, 1, 0),
    ])
    def test_add_multiple_cases(self, calc, a, b, expected):
        assert calc.add(a, b) == expected

JavaScript(Jest)

describe('Calculator', () => {
  let calc;

  beforeEach(() => {
    calc = new Calculator();
  });

  test('adds two numbers', () => {
    expect(calc.add(2, 3)).toBe(5);
  });

  test('throws error on division by zero', () => {
    expect(() => calc.divide(10, 0)).toThrow();
  });

  test.each([
    [2, 3, 5],
    [0, 0, 0],
    [-1, 1, 0],
  ])('add(%i, %i) returns %i', (a, b, expected) => {
    expect(calc.add(a, b)).toBe(expected);
  });
});

Shell脚本(bats)

#!/usr/bin/env bats

@test "script exits with status 0 on success" {
  run ./myscript.sh input.txt
  [ "$status" -eq 0 ]
}

@test "script produces expected output" {
  run ./myscript.sh input.txt
  [ "${lines[0]}" = "Expected output" ]
}

@test "script fails with invalid input" {
  run ./myscript.sh nonexistent.txt
  [ "$status" -ne 0 ]
  [[ "$output" =~ "Error" ]]
}

模拟和存根

何时模拟

  • 外部服务(API、数据库)
  • 慢操作
  • 非确定性行为(随机、时间)
  • 难以触发场景(错误)

Python模拟

from unittest.mock import Mock, patch, MagicMock

# 模拟对象
mock_db = Mock()
mock_db.get_user.return_value = {"id": 1, "name": "测试"}

# 修补函数
@patch('myapp.external_api_call')
def test_function(mock_api):
    mock_api.return_value = {"status": "成功"}
    result = my_function()
    assert result == expected
    mock_api.assert_called_once_with(expected_arg)

JavaScript模拟

// Jest模拟
jest.mock('./api');
import { fetchUser } from './api';

test('loads user data', async () => {
  fetchUser.mockResolvedValue({ id: 1, name: '测试' });

  const user = await loadUser(1);

  expect(user.name).toBe('测试');
  expect(fetchUser).toHaveBeenCalledWith(1);
});

集成测试

数据库测试

import pytest
from myapp import create_app, db

@pytest.fixture
def app():
    app = create_app('testing')
    with app.app_context():
        db.create_all()
        yield app
        db.session.remove()
        db.drop_all()

def test_user_can_be_saved_to_database(app):
    user = User(email='test@example.com')
    db.session.add(user)
    db.session.commit()

    retrieved = User.query.filter_by(email='test@example.com').first()
    assert retrieved is not None
    assert retrieved.email == 'test@example.com'

API测试

def test_api_returns_user_list(client):
    response = client.get('/api/users')

    assert response.status_code == 200
    assert len(response.json) > 0
    assert 'email' in response.json[0]

端到端测试

Web测试(Playwright/Selenium)

// Playwright示例
test('user can login', async ({ page }) => {
  await page.goto('https://example.com');

  await page.fill('[name="email"]', 'user@example.com');
  await page.fill('[name="password"]', 'password123');
  await page.click('button[type="submit"]');

  await expect(page.locator('.welcome')).toContainText('欢迎回来');
});

测试夹具和工厂

夹具

@pytest.fixture
def sample_user():
    return User(
        email='test@example.com',
        name='测试用户'
    )

@pytest.fixture
def authenticated_client(client, sample_user):
    client.login(sample_user)
    return client

工厂

import factory

class UserFactory(factory.Factory):
    class Meta:
        model = User

    email = factory.Sequence(lambda n: f'user{n}@example.com')
    name = factory.Faker('name')
    is_active = True

# 使用
user = UserFactory()
admin = UserFactory(is_admin=True)
users = UserFactory.create_batch(10)

测试最佳实践

该做的

  • ✅ 先写测试(TDD)
  • ✅ 测试行为,而非实现
  • ✅ 保持测试简单可读
  • ✅ 使用描述性测试名称
  • ✅ 测试边缘案例和错误
  • ✅ 保持测试快速
  • ✅ 使测试独立
  • ✅ 使用夹具处理常见设置

不该做的

  • ❌ 测试框架/库代码
  • ❌ 在一个测试中测试多个东西
  • ❌ 使用未种子化的随机数据
  • ❌ 依赖测试执行顺序
  • ❌ 留下注释掉的测试
  • ❌ 无故跳过测试
  • ❌ 存在不稳定测试

测试组织

项目/
├── 源/
│   └── 我的应用/
│       ├── __init__.py
│       └── 计算器.py
└── 测试/
    ├── __init__.py
    ├── conftest.py          # 共享夹具
    ├── 单元/
    │   └── test_calculator.py
    ├── 集成/
    │   └── test_database.py
    └── 端到端/
        └── test_user_flow.py

代码覆盖

生成覆盖报告

# Python
pytest --cov=myapp --cov-report=html

# JavaScript
jest --coverage

# 查看覆盖
打开 htmlcov/index.html

覆盖目标

  • 关键业务逻辑:100%
  • 大多数代码:80%以上
  • 端到端脚本:覆盖较低也可接受
  • 不牺牲测试质量追求覆盖数字

常见测试模式

测试异常

def test_raises_error():
    with pytest.raises(ValueError, match="无效输入"):
        function_that_raises("bad")

测试异步代码

@pytest.mark.asyncio
async def test_async_function():
    result = await async_function()
    assert result == expected

测试时间依赖代码

@patch('myapp.datetime')
def test_time_dependent(mock_datetime):
    mock_datetime.now.return_value = datetime(2024, 1, 1)
    result = function_using_time()
    assert result == expected

持续集成

# .github/workflows/test.yml
名称: 测试
on: [push, pull_request]
工作:
  测试:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: 运行测试
        run: |
          pip install -r requirements-dev.txt
          pytest --cov --cov-report=xml
      - name: 上传覆盖
        uses: codecov/codecov-action@v2

记住:好的测试是您的安全网。它们给您重构和添加功能的信心。投资时间编写高质量测试!