单元测试框架

1.什么是单元测试框架?

单元测试框架是自动化测试或者白盒测试中对软件的最小单元进行测试的框架。

2.单元测试框架主要做什么?

  • 发现测试用例:从多个py文件里按照一定的规则找到测试用例。
  • 执行测试用例:按照一定的顺序执行测试用例,并生成结果。
  • 判断测试结果:断言。
  • 生成测试报告:pytest-html,allure报告。

Pytest简介

Pytest是一个非常成熟的单元测试框架,简化了测试用例的编写和执行过程,支持从简单的单元测试到复杂的功能测试,广泛应用于Python项目的测试环节。

安装Pytest

使用pip进行安装。

pip install pytest

Pytest插件

以下是一些常用插件及其功能

插件名称 功能描述
pytest-html 生成美观的 HTML 测试报告,展示测试结果、执行时间、失败详情等指标。
pytest-xdist 支持多进程 / 分布式执行测试,大幅提升测试速度(如 pytest -n 4 并行执行)。
pytest-ordering 控制测试用例执行顺序(通过 @pytest.mark.run(order=1) 标记)。
pytest-rerunfailures 自动重试失败的测试用例(如 pytest --reruns 3),适用于不稳定的测试场景。
allure-pytest 生成 Allure 格式的测试报告,支持可视化测试结果、依赖关系和执行路径。
pyyaml 提供 YAML 文件解析能力,用于读取测试配置或数据(需结合 yaml.safe_load() 使用)。
requests 简化 HTTP 请求,用于测试 API 接口(如 requests.get(url).json())。

在项目的根目录下新建一个requirements.txt文件保存插件,requirements.txt文件内容如下:

pytest
pytest-html
pytest-xdist
pytest-order
pytest-rerunfailures
allure-pytest
pyyaml
requests

然后通过一下命令安装:

pip install -r requirements.txt

Pytest用例规则

1.测试文件必须以test_开头或以_test.py结尾,例如:

  • test_example.py
  • example_test.py

2.测试函数必须以test_开头,例如:

def test_addition():
    assert 1 + 1 == 2

3.测试类必须以Test开头(注意不能包含__init__方法),类中的测试方法也必须以test_开头:

class TestCalculator:
    def test_subtraction(self):
        assert 5 - 3 == 2

Pytest运行方式

1.运行当前目录下的所有测试

在终端中直接输入:

pytest

Pytest 会自动发现并执行当前目录子目录中所有符合命名规则的测试文件和函数。

image-20250704190140366

2.可以在项目根目录创建pytest.initox.ini文件,定义默认参数:

[pytest]
addopts = -v --durations=5
testpaths = testcases/test_example.py

此时直接在终端运行pytest会自动使用配置文件中的参数。

3.主函数方式

import pytest

if __name__ == '__main__':
    pytest.main()

常见参数:

-v:输出更加详细的信息。比如文件和用例名称等。

-s:输出调试信息。打印信息等。

可以合并成:-vs

--reruns 数字:失败重跑

-x:出现1个失败就停止测试

--maxfail=2 出现N个失败就终止测试

--html=report.html 生成html的测试报告

-n:多线程

-k:运行测试用例名称中包含指定字符串的用例

指定文件运行

import pytest

if __name__ == '__main__':
    pytest.main(['-vs','testcases/test_example.py'])

Pytest标记

基本语法:

通过@pytest.mark.标记名来为测试函数或类添加标记:

# 为单个测试函数添加标记
@pytest.mark.slow
def test_expensive_operation():
    # 模拟耗时操作
    pass

# 为整个测试类添加标记
@pytest.mark.integration
class TestAPIClient:
    def test_get_data(self):
        # 集成测试逻辑
        pass

运行指定的测试:

使用-m参数指定要运行的标记,在pytest.ini文件中配置

[pytest]
addopts = -vs -m 'slow'											#只运行被标记为slow的测试
[pytest]
addopts = -vs -m 'integration or slow'			# 运行多个标记的测试(使用or逻辑)
[pytest]
addopts = -vs -m 'integration and slow'			# 运行同时满足多个标记的测试(使用and逻辑)

注册自定义标记:

pytest.ini中注册标记

[pytest]
markers =
    slow: 标记运行缓慢的测试
    integration: 标记集成测试
    api: 标记API相关测试
    smoke: 标记冒烟测试(快速执行的基本功能测试)

组合使用多个标记:

一个测试可以有多个标记:

@pytest.mark.slow
@pytest.mark.api
def test_slow_api_endpoint():
    # 测试逻辑
    pass

Pytest用例顺序

使用@pytest.mark.order标记,通过数字指定执行顺序(越小越先执行):

#test_mark_order.py
import pytest

@pytest.mark.order(2)
def test_second():
    assert True
    
@pytest.mark.order(1)
def test_first():
    assert True
    
@pytest.mark.order(3)
def test_third():
    assert True
image-20250704221403472

注意:有order装饰器的优先,相同的从上到下,然后再是没有装饰器的

Pytest跳过测试用例

1.无条件跳过测试

使用 @pytest.mark.skip 标记,需提供 reason 参数说明跳过原因:

import pytest
@pytest.mark.skip(reason="功能尚未实现")
def test_todo():
    pass

2.有条件跳过

使用 @pytest.mark.skipif 标记,根据条件判断是否跳过:

import pytest
import sys
@pytest.mark.skipif(sys.version_info < (3,12),reason="需要Python3.8+")
def test_python38_feature():
    pass

Pytest前后置

在Pytest中,前后置操作是测试框架的重要组成部份,用于准备测试环境(前置) 和清理资源(后置)。

1.模块级

import pytest

def setup_module():
    print("整个模块开始前执行一次")

def teardown_module():
    print("整个模块结束后执行一次")
    
def setup_function():
    print("函数用例前置方法执行")
   
def teardown_function():
    print("函数用例后置方法执行")
    
def test_method01():
    print("Method01")
    
def test_method02():
    print("Method02")
image-20250705001951093

2.类级

import pytest

class TestClass:
    def setup_class(cls):
        print("类开始前执行一次")
    
    def teardown_class(cls):
        print("类结束后执行一次")
        
    def setup_method(self):
        print("类中每个测试方法开始前执行")
    
    def teardown_method(self):
        print("类中每个测试方法结束后开始执行")
        
    def test_method01(self):
        print("Method01 is Running")
    
    def test_method02(self):
        print("Method02 is Running")
image-20250705002940648