目录
[1. 主流接口自动化框架](#1. 主流接口自动化框架)
[1.1 pytest 安装](#1.1 pytest 安装)
[2. pytest 运行规则](#2. pytest 运行规则)
[3. pytest 命令参数](#3. pytest 命令参数)
[4. pytest 配置文件](#4. pytest 配置文件)
[4.1 addopts](#4.1 addopts)
[4.2 testpaths](#4.2 testpaths)
[4.3 python_files](#4.3 python_files)
[4.4 python_classes](#4.4 python_classes)
[4.5 python_functions](#4.5 python_functions)
[4.6 总结](#4.6 总结)
[5. pytest - 前后置](#5. pytest - 前后置)
[5.1 setup_method 和 teardown_method](#5.1 setup_method 和 teardown_method)
[5.2 setup_class 和 teardown_class](#5.2 setup_class 和 teardown_class)
[6. 断言](#6. 断言)
[6.1 普通数据类型断言](#6.1 普通数据类型断言)
[6.2 接口返回值断言](#6.2 接口返回值断言)
[7. 参数化](#7. 参数化)
[7.1 参数化单个参数](#7.1 参数化单个参数)
[7.2 参数化多个参数](#7.2 参数化多个参数)
[7.3 参数化类](#7.3 参数化类)
[7.4 全局参数化](#7.4 全局参数化)
[7.5 自定义参数化数据源](#7.5 自定义参数化数据源)
[8. fixture](#8. fixture)
[8.1 基本使用](#8.1 基本使用)
[8.2 fixture 嵌套](#8.2 fixture 嵌套)
[8.3 请求多个 fixture](#8.3 请求多个 fixture)
[8.4 yield](#8.4 yield)
[8.5 fixture 参数的使用](#8.5 fixture 参数的使用)
[8.5.1 scope = function](#8.5.1 scope = function)
[8.5.2 scope = class](#8.5.2 scope = class)
[8.5.3 scope = module](#8.5.3 scope = module)
[8.5.3.1 conftest.py 配置文件](#8.5.3.1 conftest.py 配置文件)
[8.5.4 scope = session](#8.5.4 scope = session)
[8.5.5 autouse](#8.5.5 autouse)
[8.5.6 params](#8.5.6 params)
1. 主流接口自动化框架
|-----------|---------------------------|---------------------------------------------------|------------------------------|
| 维度 | unittest (Python内置) | pytest | Robot Framework |
| 安装方式 | 无需安装 (Python标准库) | pip install pytest | pip install robotframework |
| 语法风格 | 基于类 (需继承 TestCase) | 函数式或面向对象 (无需样板代码) | 关键字驱动 (表格化用例) |
| 断言方法 | self.assertEqual() 等 | 原生 assert 表达式 | 关键字断言 (如 Should Be Equal) |
| 参数化支持 | 需 subTest 或第三方库 | 内置 (@pytest.mark.parametrize) | 数据驱动 (Test Template) |
| 插件生态 | 少 (依赖扩展库如 HTMLTestRunner) | 丰富 (如 pytest-html、pytest-xdist、allure-pytest) | 一般 (需安装额外库如 RequestsLibrary) |
| 测试报告 | 需插件生成报告 | 支持多格式报告 (HTML、Allure等) | 自带详细日志和报告 |
| 学习曲线 | 中等 (需熟悉xUnit模式) | 低 (语法简洁) | 高 (需掌握关键字和语法) |
| BDD支持 | 不支持 | 支持 (通过 pytest-bdd 插件) | 支持 (通过 robotframework-bdd) |
| 适用场景 | 简单项目或遗留系统维护 | 复杂项目、高扩展性需求 | 团队协作、非技术人员参与 |
这里我们使用 pytest 进行接口自动化测试.
pytest 官方文档: https://docs.pytest.org/en/stable/getting-started.html
1.1 pytest 安装
python
pip install pytest==8.3.2
注意: 安装 8.3.2 版本的 pytest, Python 版本需大于 3.8:
|-----------|--------------|
| pytest 版本 | 最低 Python 版本 |
| 8.0+ | 3.8+ |
| 7.1+ | 3.7+ |
| 6.2 - 7.0 | 3.6+ |
| 5.0 - 6.1 | 3.5+ |
| 3.3 - 4.6 | 2.7, 3.4+ |
查看是否安装成功:

2. pytest 运行规则
pytest 安装成功后, 它会自动识别当前目录或子目录中符合条件的方法, 在终端输入 pytest 后, 这些符合条件的方法就会自动被执行, 此外, 这些符合条件的方法前, 也会出现运行按钮:
(一个方法, 就是一个 case)

注意: 并不是所有的方法都会被 pytest 识别, 只有符合以下命名规则的方法, 才会被 pytest 识别并自动被执行:
- 文件名必须以 test_ 开头, 或者以 test_ 结尾.
- 类名必须以 Test 开头, 并且不能有 init 初始化方法.
- 测试方法必须以 test 开头.
至于为什么类中不能有初始化方法, 是由于 pytest 的测试用例收集机制导致的, 感兴趣的可以了解一下:
- pytest 需要实例化测试类才能执行其中以 test 开头的测试方法, 而如果类中有初始化方法, 那么创建类实例时, 这个 init 方法就会被调用, 这个初始化方法的执行可能会引入额外的副作用, 影响测试结果的准确性, 因此禁止测试类中含有初始化方法.
为了避免使⽤ init ⽅法, 建议在 pytest 中使⽤其他替代⽅案, 如使⽤ setUp() 和
tearDown() ⽅法、使⽤类属性、使⽤ fixture 函数(具体使⽤后续会讲解).
注意: 大家不要把 Python 的初始化方法给搞错了, init 才是初始化方法, 而 init 只是一个普通的方法:
pythonclass Student: def __init__(self): print('普通方法.') def init(self): print('初始化方法.') # 初始化方法(__init__)被调用, 普通方法不被调用. student = Student()
3. pytest 命令参数
pytest 提供了丰富的命令参数, 我们可以选择合适的命令参数来观察测试结果:
|---------------------------|----------------------------------------------------------|-------------------------|
| 命令 | 描述 | 备注 |
| pytest | 在当前目录及其子目录中搜索并运行测试。 | |
| pytest -v | 展示测试方法的名称. | |
| pytest -s | 展示测试方法中 print 打印出的语句. | |
| pytest -vs | pytest -v 和 pytest-s 的结合, 既展示方法名称, 也展示 print 打印出的语句. | |
| pytest test_module.py | 运行指定的测试模块。 | |
| pytest test_dir/ | 运行指定目录下的所有测试。 | |
| pytest -k <keyword> | 只运行测试名包含指定关键字的测试。 | |
| pytest -m <marker> | 只运行标记为指定标记的测试。 | |
| pytest -q | 减少输出的详细程度。 | |
| pytest --html=report.html | 生成 HTML 格式的测试报告。 | 需要安装 pytest-html 插件 |
| pytest --cov | 测量测试覆盖率 | 需要安装 pytest-cov 插件 |
pytest 命令, 只会展示测试用例(方法)所在的文件名称以及通过的用例数量, 不会展示具体的方法名以及方法中的打印结果:

pytest -v 命令, 会对测试结果进行更加丰富的描述, 展示类和测试用例(方法)的名称, 但是也不会展示方法中的打印结果:

注意:
在 pytest 中, 文件和类之间, 类和方法之间, 使用 :: 进行分割.
pytest -s 命令, 会展示测试方法中的打印结果, 但是不会展示测试方法的名称:

pytest -vs 命令, 是 pytest -v 和 pytest -s 的结合, 既能展示类和测试方法的名称, 也能展示方法中 print 打印出的信息:

此外, 也可以使用 pytest xxx.py指定运行哪个文件(以当前项目路径为根目录):

4. pytest 配置文件
在项目目录下创建 pytest.ini 文件(注意: 必须是这个名!!), 并在首行写入: [pytest], 表明这是 pytest 的配置文件.
通过配置文件, 我们可以修改 pytest 默认的执行规则.
常见的配置项如下:
|----------------------|------------------------------|
| 参数 | 解释 |
| addopts | 指定在命令行中默认包含的选项。 |
| testpaths | 指定搜索测试的目录。 |
| python_files | 指定发现测试模块时使用的文件匹配模式。 |
| python_classes | 指定发现测试类时使用的类名前缀或模式。 |
| python_functions | 指定发现测试函数和方法时使用的函数名前缀或模式。 |
| norecursedirs | 指定在搜索测试时应该避免递归进入的目录模式。 |
| markers | 定义测试标记,用于标记测试用例。 |
4.1 addopts
可以使用 addopts 设置命令行中默认包含的内容, 这样可以简化命令的使用:

4.2 testpaths
pytest 默认的搜索路径是以项目目录为起点, 我们可以通过 testpaths 指定 pytest 的搜索路径:

4.3 python_files
pytest 默认识别的文件是以 test_ 开头或以 _test 结尾, 我们可以通过 python_files 修改 pytest 文件的识别规则:

4.4 python_classes
pytest 默认识别的类是以 Test 开头, 我们可以通过 python_classes 修改 pytest 对类的识别规则:

4.5 python_functions
pytest 默认识别的是以 test 开头的方法, 我们可以通过 python_functions 修改 pytest 对方法的识别规则:

4.6 总结
因此, 上述配置文件的效果是: 识别 case 目录下, 以 case_ 开头的文件中, (如果有类)以 Case 开头的类, 以 sky 开头的方法.
5. pytest - 前后置
利用 pytest 的前后置, 我们可以在测试用例执行前执行一些前置操作, 以及在测试用例执行后执行一些后置操作. 这些前后置操作可以帮助我们搭建测试环境、准备测试数据等等.
pytest 提供了以下几组方法帮助我们进行前后置操作:
- setup_method 和 teardown_method :这两个⽅法⽤于类中的每个测试⽅法的前置和后置操作
- setup_class 和 teardown_class :这两个⽅法⽤于整个测试类的前置和后置操作
- fixture :这是 pytest 推荐的⽅式来实现测试⽤例的前置和后置操作, fixture 提供了更灵活的控制和更强⼤的功能.(后面讲)
5.1 setup_method 和 teardown_method
setup_method 方法, 可以在某个类的测试方法执行前执行, 而 teardown_method 方法, 可以在某个类的测试方法执行后执行.
注意: setup_method 和 teardown_method 只能在类中定义, 并且它们的作用范围也仅限于该类中的测试方法.

5.2 setup_class 和 teardown_class
setup_class 方法, 和 teardown_class 方法是整个测试类的前置和后置方法, 其中:
- setup_class, 在整个测试类的所有测试方法开始执行之前, 仅执行一次
- teardown_class, 在该测试类的所有测试方法全部执行完毕之后, 仅执行一次

6. 断言
使用 assert 对程序的执行结果进行断言, 如果符合预期则测试通过, 否则抛出 AssertionError (断言失败) 异常.
我们可以直接使用 Python 提供的 assert 进行断言.
语法为:assert 条件, 错误信息(可有可无)
注意:
- 条件: 必须是一个布尔表达式(True 或 False)
- 错误信息: 可有可无, 用作断言失败时的提示
6.1 普通数据类型断言
断言全部成功, 则测试通过:

若存在 assert 失败, 则测试不通过:

6.2 接口返回值断言
免费 api 练习资源: https://jsonplaceholder.typicode.com/
使用 requests 库发送 http 请求, 获取响应 body, 进行断言:
若断言成功:


若断言失败:

以上 http 请求返回的都是 json 数据, 若返回的是 html, 那么可以断言界面上的元素是否包含在返回的 html 中:

7. 参数化
在 Pytest 中, **"参数化" 是指能够让同一个测试函数使用多组不同的输入数据来重复运行.**使用参数化, 可以显著提高测试效率和代码的可维护性.
测试时, 针对同一个测试场景, 我们往往需要设计多组测试用例进行测试, 比如: 登录场景, 我们需要覆盖多组用户的登录(张三, 李四, 王五...)
如果每针对一个测试用例, 就写一个测试函数, 那么是麻烦且耗时的.
而参数化, 可以解决这个问题, 我们可以利用 pytest 提供的参数化装饰器(@pytest.mark.parametrize), 对一个测试方法的形参传入多个测试用例, 执行多组测试.
(除了 @pytest.mark.parametrize 可以进行参数化外, fixture 也可以进行参数化, 后文会讲.)
7.1 参数化单个参数
当我们对单个参数进行参数化时, 可以在 @pytest.mark.parametrize 中 传入一个值的列表, Pytest 会遍历这个列表, 并将每个元素作为参数执行一次测试.

7.2 参数化多个参数
当我们需要同时对多个参数进行参数化时, 需要传入一个列表 , 并且这个列表中的每个元素都是一个元组. 每个元组代表一组完整的参数, 用于一次测试的执行.

|----------|---------------------------------------------------------------|
| 参数数量 | 传入的数据结构 |
| 单个参数 | 一个值的列表,例如 ["value1", "value2"] |
| 多个参数 | 一个元组的列表,例如 [("val1_a", "val1_b"), ("val2_a", "val2_b")] |
7.3 参数化类
除了可以对方法进行参数化外, 也可以对类进行参数化. 对类级别使用 @pytest.mark.parametrize 时, 这个参数化设置会应用到该类中所有符合条件的测试方法上.
但是, 要想在某个测试方法中使用这些参数, 该方法的参数列表中必须包含与 @pytest.mark.parametrize 中定义的参数名完全相同的名称.

7.4 全局参数化
要对模块中的所有测试进⾏参数化, 你可以对 pytestmark(全局变量 ) 赋值.
这个全局参数化**仅对当前文件(即当前模块)有效,**而不是对项目中的所有文件都有效.

|------------------------------------|-------------|----------------------|
| 方式 | 作用域 | 描述 |
| 装饰器 (@pytest.mark.parametrize) | 函数或类级别 | 只影响它直接修饰的那个测试函数或测试类。 |
| 全局变量 (pytestmark) | 文件/模块级别 | 影响定义它的那个文件中的所有测试。 |
7.5 自定义参数化数据源
自定义参数化数据源, 就是指将一个函数的返回值作为 @pytest.mark.parametrize 的数据来源.

8. fixture
pytest 中的 fixture 是⼀种强⼤的机制, ⽤于提供测试函数所需的资源或上下⽂. 它可以⽤于设置试
环境、准备数据等...
我们可以通过**@pytest.fixture**注解, 将一个方法修饰为一个 fixture 函数, 在测试函数执行前, 会先执行传入的 fixture 函数.
8.1 基本使用
在之前的博客系统中, 如果我们要访问 '获取博客列表接口' / '展示博客详情接口', 必须要先进行 '登录.
因此, **'登录' 是 '获取博客列表接口' 和 '展示博客详情接口' 的一个测试前提.**那么在访问这两个接口前, 我们就可以先通过 fixture 来完成 '登录' 操作, 构造好测试环境.

8.2 fixture 嵌套
可以在一个 fixture 函数中, 传入另一个 fixture 函数, 嵌套使用.

注意, 要使用一个 Fixture, 你必须将该 Fixture 的函数名作为参数, 传入需要它的函数(无论是测试函数还是另一个 Fixture)的参数列表中.
当我们将一个 fixture 作为参数传入函数后, Pytest 就会将其返回值注入到这个同名参数中. 因此, 在函数体内部, 我们便可以直接使用这个参数名来获取其返回值

注意:
一旦使用 @pytest.fixture 装饰了一个函数, 就不应该再通过 函数名() 的方式去直接调用它了.
8.3 请求多个 fixture
在上文的例子中, 都是一个测试方法中使用了一个 fixture.
而 pytest 也允许在一个测试方法中使用多个 fixture:

注意:
1. 当比较两个自定义类的实例时, 需要重写 eq 方法:
assert your_fruit in my_fruit 就进行了比较操作.
如果不重写 eq, Python 默认会比较两个对象的内存地址.
在代码中, your_fruit 和 my_fruit 列表中的第一个元素虽然内容相同(都是Fruit('苹果')), 但它们是两个独立创建的对象, 内存地址不同, 因此不重写 eq 的话, 比较结果会是 False.
通过重写 eq 方法来定义类的比较规则, 我定义的是 "只要 name 属性相同, 两个 Fruit 对象就相等" 的规则, 这正是测试能通过的关键.
2. Python 实例属性 vs 类熟悉 (上图代码中定义的是实例属性)
|----------|------------------------|------------------------|
| 特性 | 实例属性 | 类属性 |
| 定义位置 | 在 init 等方法内部 | 在类中,方法之外 |
| 定义方式 | self.属性名 = 值 | 属性名 = 值 |
| 所有权 | 属于每个独立的对象/实例 | 属于类本身 ,被所有实例共享 |
| 用途 | 定义每个对象独有的特征(如名字、颜色、大小) | 定义所有对象共有的特征(如"是不是植物") |
8.4 yield
yield 可以将一个 fixture 函数分为两部分:
- yield 前, 是初始化数据部分(前置部分)
- yield 后, 释放数据部分(后置部分)
当一个测试方法使用这个 fixture 函数时, 会先执行 fixture 的前置部分, 再执行测试方法, 再执行 fixture 的后置部分:
- 执行 fixture 的前置部分(yield 前的代码)
- 执行测试函数
- 执行 fixture 的后置部分(yield 后的代码)
因此, 在 pytest 中, 我们可以使用 yield 来替代 return, 但是遇到 yield, 并不会真正释放这个 fixture 函数的函数栈帧, 因为当测试方法执行完后, 还会继续执行这个 fixture, 当后置部分也执行完后, 函数栈帧才会被释放.

上述代码中, 需要注意的点是, f_w.close() 不能通过 yield 去后置调用, 这是因为:
- 调用 f_w.write("hello NIO") 时, 数据并没有被立即写入到硬盘中, 只是放在了 I/O 缓冲区中, 只有当释放句柄后(调用 close 方法)才能真正将数据写入硬盘中.
- 因此 f_w.close() 必须在 读文件 前调用, 才能保证 f_r.read() 能从硬盘中读取写入的内容, 若 f_w.close() 是通过 yield 去调用的, 那么必须等到测试方法全部执行完毕才能执行, 那么 f_r.read() 就读不到任何东西了.
8.5 fixture 参数的使用
使用语法: @pytest.fixture(scope='', params='', autouse='', ids='', name='')
各参数含义:
- scope, 可以控制 fixture 的作用域, 可选值有:
- function(默认): 每个测试方法执行一次 fixture
- class: 每个类执行一次 fixture
- module: 每个文件执行一次 fixture
- session: 整个测试会话只执行一次 fixture (所有的测试方法, 不管是否同一个文件中, 都共享这个 fixture)
- autouse: fixture 方法是否会被自动执行
- autouse=False(默认), fixture 不会被自动执行, 需要在测试函数的参数中手动传入 fixture(上文都是)
- autouse=True, fixture 会被自动执行, 无需在测试函数的参数中手动传入 fixture
- params: 用于 fixture 参数化
- ids: 与 params 配合使⽤, 为每个参数化实例指定可读的标识符 (给参数取名字)
- name: 为 fixture 显式设置⼀个名称. 如果使⽤了 name, 则在测试函数中需要使⽤这个名称来引⽤ fixture (给 fixture 取名字)

8.5.1 scope = function
scope = function, 每个请求 fixture 的测试方法都会执行一次 fixture.

8.5.2 scope = class
当 scope = class 时, 一个测试类执行一次 fixture:

8.5.3 scope = module
当 scope = module 时, 每一个使用该 fixture 的测试模块(.py 文件), 都会共享属于自己模块的那个 fixture 实例.
这个 fixture 的 setup 部分会在该模块内 第一个请求它的测试函数运行前执行一次, 它的teardown 会在 "该模块内的最后一个测试(无论它是否使用了 fixture 执行完毕后" 运行.

8.5.3.1 conftest.py 配置文件
我们可以将 fixture 函数, 提取到 conftest.py 文件中, 就可以在不同的 .py ⽂件中使⽤同⼀个 fixture 函数.
conftest.py 的规则:
- conftest.py 是⼀个单独存放的夹具配置⽂件, 名称是固定的不能修改(必须命名为 conftest.py)
- 在不同模块的测试中需要⽤到 conftest.py 的前后置功能时, 不需要做任何的 import 导⼊操作
- 你可以在项⽬中的不同⽬录下创建多个 conftest.py ⽂件, 每个****conftest.py ⽂件都会对其所在⽬录及其⼦⽬录下的测试模块⽣效
conftest.py 和 @pytest.fixture 结合使⽤实现全局的前后置应⽤:
8.5.4 scope = session
scope = session, 当前项目中的所有的测试方法, 都共享一个 fixture.
因此这个 fixture 的 setup 部分(yield 之前)会在整个项目中 第一个 请求它的测试函数运行前执行 一次, 它的 teardown 部分(yield 之后)则会在 整个测试会话(session)结束时执行.
(在最后一个使用该 fixture 的测试运行完毕后, 可能还有其他完全不使用该 fixture 的测试需要运行/. Pytest 会等待 所有测试(无论是否使用该 fixture)都完成后, 在进程退出前, 统一执行 session 作用域的清理(teardown)工作)
8.5.5 autouse
autouse, 表示 fixture 方法是否会被自动执行.
autouse 默认是 False, 我们需要将 fixture 传入测试方法的参数中后, 才算请求了这个 fixture.

若把 autouse 设置为 True, 那么我们无需将 fixture 传入测试方法的参数中, pytest 就会自动为在其作用域(scope)内的所有测试方法请求这个 fixture.

8.5.6 params
除了 @pytest.mark.parametrize 可以实现参数化外, fixture 也可以通过传入 param 参数来实现参数化.

END