pytest 通过实例讲清单元测试、集成测试、测试覆盖率

1. 单元测试

概念

  • 定义: 单元测试是对代码中最小功能单元的测试,通常是函数或类的方法。
  • 目标: 验证单个功能是否按照预期工作,而不依赖其他模块或外部资源。
  • 特点: 快速、独立,通常是开发者最先编写的测试。

示例:pytest 实现单元测试

python 复制代码
# 功能模块:一个简单的数学函数
def add(x, y):
    """
    加法函数
    """
    return x + y

def divide(x, y):
    """
    除法函数,包含除零检查
    """
    if y == 0:
        raise ValueError("Cannot divide by zero")
    return x / y

# 测试模块:单元测试
def test_add():
    """
    测试 add 函数
    """
    assert add(2, 3) == 5  # 正常情况
    assert add(-1, 1) == 0  # 边界值
    assert add(0, 0) == 0  # 特殊情况

def test_divide():
    """
    测试 divide 函数
    """
    assert divide(10, 2) == 5  # 正常情况
    with pytest.raises(ValueError, match="Cannot divide by zero"):
        divide(1, 0)  # 测试除零异常

执行命令

运行单元测试:

bash 复制代码
pytest test_example.py

优点

  • 快速反馈代码问题。
  • 单一功能模块的高覆盖率。

2. 集成测试

概念

  • 定义: 集成测试是验证多个模块的交互行为是否正常,确保它们组合在一起能够按预期工作。
  • 目标: 检查模块之间的接口和协作行为,可能涉及数据库、API 或文件系统等外部依赖。
  • 特点: 比单元测试慢,但更贴近实际场景。

示例:pytest 实现集成测试

使用数据库模拟的场景

假设我们有一个用户管理模块,需要测试用户的创建、查询和删除功能:

python 复制代码
# 功能模块:用户管理
class UserDatabase:
    """
    模拟用户数据库
    """
    def __init__(self):
        self.users = {}

    def add_user(self, username, email):
        """
        添加用户
        """
        if username in self.users:
            raise ValueError("User already exists")
        self.users[username] = email

    def get_user(self, username):
        """
        获取用户
        """
        return self.users.get(username)

    def delete_user(self, username):
        """
        删除用户
        """
        if username in self.users:
            del self.users[username]
        else:
            raise ValueError("User does not exist")

# 测试模块:集成测试
def test_user_database():
    """
    测试用户数据库模块的集成功能
    """
    db = UserDatabase()

    # 添加用户
    db.add_user("alice", "alice@example.com")
    assert db.get_user("alice") == "alice@example.com"

    # 删除用户
    db.delete_user("alice")
    assert db.get_user("alice") is None

    # 测试异常情况
    with pytest.raises(ValueError, match="User does not exist"):
        db.delete_user("alice")

执行命令

运行集成测试:

bash 复制代码
pytest test_example.py

单元测试与集成测试的区别

特性 单元测试 集成测试
测试范围 单一模块或函数 多个模块之间的交互
目标 验证单独功能是否正确 验证整体功能是否按预期工作
速度 快速 较慢
复杂度 较低 较高,可能涉及外部依赖
测试工具 模拟对象 (Mock) 实际环境或部分模拟环境

3. pytest 中的 Mock 模拟(用于集成测试中的外部依赖)

在集成测试中,我们可能需要模拟外部依赖(如数据库、API)。pytest 支持使用 unittest.mock 来实现 Mock。

示例:模拟外部 API

假设我们有一个函数需要从外部 API 获取数据:

python 复制代码
# 功能模块:从外部 API 获取数据
def fetch_data(api_client):
    """
    从外部 API 客户端获取数据
    """
    response = api_client.get("/data")
    if response.status_code == 200:
        return response.json()
    else:
        raise ValueError("Failed to fetch data")
测试:使用 Mock 模拟 API
python 复制代码
from unittest.mock import MagicMock

def test_fetch_data():
    """
    测试 fetch_data 函数,使用 Mock 模拟 API 行为
    """
    # 创建 Mock API 客户端
    mock_client = MagicMock()
    
    # 模拟成功响应
    mock_client.get.return_value.status_code = 200
    mock_client.get.return_value.json.return_value = {"key": "value"}

    # 调用函数并验证返回值
    result = fetch_data(mock_client)
    assert result == {"key": "value"}

    # 验证 API 是否被正确调用
    mock_client.get.assert_called_once_with("/data")

运行测试

使用以下命令运行测试:

bash 复制代码
pytest test_example.py

4. 测试组合:单元测试 + 集成测试

实际开发中,建议结合单元测试和集成测试:

  1. 单元测试:覆盖每个功能单元,确保模块内部逻辑正确。
  2. 集成测试:验证模块之间的交互和整体功能。

最佳实践

  • 单元测试优先: 先确保每个功能单元稳定。
  • 集成测试补充: 验证整体流程时,再引入集成测试。
  • Mock 外部依赖: 在集成测试中尽量减少对真实资源(数据库、网络)的依赖。

什么是项目的测试覆盖率?

测试覆盖率(Test Coverage)是衡量一个项目中有多少代码被测试用例覆盖的指标。它表示项目代码的质量保证程度。测试覆盖率通常以百分比的形式表示,如 80% 表示代码中 80% 的部分已经被测试用例运行过。

覆盖率分类
  1. 行覆盖率(Line Coverage)

    检测每一行代码是否被执行。

  2. 分支覆盖率(Branch Coverage)

    检测代码中的条件语句(如 if-else)的所有分支是否都被测试。

  3. 函数覆盖率(Function Coverage)

    检测所有函数是否被调用。

  4. 路径覆盖率(Path Coverage)

    检测所有可能的执行路径是否都被测试。

为什么测试覆盖率重要?
  1. 质量保证:确保关键代码路径经过充分测试。
  2. 维护性:发现未被测试的代码,优化测试用例。
  3. 团队规范:强制要求开发者在提交代码前编写测试。

如何计算测试覆盖率?

工具

在 Python 项目中,通常使用以下工具计算测试覆盖率:

  1. pytest-cov :配合 pytest 使用,易于集成。
  2. Coverage.py:独立的覆盖率工具,可生成详细的覆盖率报告。
  3. CodecovCoveralls:托管服务,用于在 GitHub 等平台展示测试覆盖率。

在 GitHub 上展示测试覆盖率

许多开源项目在 GitHub 上会显示覆盖率指标,通过徽章(Badge)的形式展示,通常借助 CodecovCoveralls 服务实现。

如何在 GitHub 项目中添加测试覆盖率?
1. 安装依赖

确保已安装以下工具:

bash 复制代码
pip install pytest pytest-cov
pip install codecov
2. 配置 pytest-cov

在项目中运行测试并生成覆盖率报告:

bash 复制代码
pytest --cov=my_project --cov-report=xml

这将生成一个 coverage.xml 文件,供上传到 Codecov 或其他服务。

3. 集成 Codecov

(1)登录 Codecov 并连接你的 GitHub 项目。

(2)在项目根目录添加一个 .github/workflows/codecov.yml 文件:

yaml 复制代码
name: CI

on:
  push:
    branches:
      - main

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.9'
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install pytest pytest-cov codecov
    - name: Run tests with coverage
      run: |
        pytest --cov=my_project
    - name: Upload coverage to Codecov
      uses: codecov/codecov-action@v3
        with:
          file: ./coverage.xml

(3)提交后,GitHub Actions 会自动运行测试并上传覆盖率到 Codecov。

4. 添加徽章

在 Codecov 项目的设置中获取徽章链接,将其添加到你的 README.md 文件中,例如:

markdown 复制代码
[![codecov](https://codecov.io/gh/<username>/<repo>/branch/main/graph/badge.svg)](https://codecov.io/gh/<username>/<repo>)

覆盖率目标

  1. 行业标准

    • 一般项目:60%-80% 及格。
    • 关键项目:95%+(例如金融系统、医疗系统)。
  2. 不能盲目追求100% :覆盖率高不一定代表没有 bug,关注测试的质量比单纯提高覆盖率更重要。

通过这些步骤,你的项目可以在 GitHub 上显示测试覆盖率,并增强项目的专业性和可信度!

相关推荐
麻衣带我去上学10 小时前
Pytest使用Jpype调用jar包报错:Windows fatal exception: access violation
windows·python·pytest·jar
测试老哥12 小时前
pytest之收集用例规则与运行指定用例
自动化测试·软件测试·python·测试工具·职场和发展·测试用例·pytest
菜鸟小贤贤13 小时前
pyhton+yaml+pytest+allure框架封装-全局变量接口关联
开发语言·python·macos·自动化·pytest
乐闻x1 天前
最佳实践:如何在 Vue.js 项目中使用 Jest 进行单元测试
前端·vue.js·单元测试
菜鸟小贤贤1 天前
pyhton+yaml+pytest+allure框架封装-全局变量渲染
python·macos·pytest·接口自动化·jinja2
總鑽風1 天前
解决单元测试时找不到类名
java·单元测试·mybatis
菜鸟小贤贤1 天前
python+pytest+allure利用fix实现接口关联
python·macos·自动化·pytest
测试老哥2 天前
基于Pytest接口自动化的requests模块项目实战以及接口关联方法
自动化测试·软件测试·python·测试工具·职场和发展·测试用例·pytest
小码哥说测试2 天前
python excel接口自动化测试框架!
自动化测试·软件测试·python·测试工具·测试用例·pytest·postman