Files
2026-07-13 12:29:33 +08:00

199 lines
4.7 KiB
Markdown

# Pytest 核心概念指南
## 1. 断言详解 (Assertions)
pytest 直接使用 Python 原生的 `assert` 关键字,不需要引入额外的断言库。
```python
# 1. 基本形式
assert condition
# 2. 带自定义错误信息
assert condition, "Error message if assertion fails"
```
> **注意**:断言主要用于开发和测试阶段,不要在生产代码的关键业务逻辑中仅依赖断言(因为 python -O 运行时会忽略断言)。
---
## 2. 发现规则 (Discovery Rules)
pytest 默认按照以下规则搜索测试:
### 文件名规则
- `test_*.py` (以 `test_` 开头的 Python 文件)
- `*_test.py` (以 `_test` 结尾的 Python 文件)
### 函数与类名规则
```python
def test_something(): # 以 test_ 开头的函数
assert 1 + 1 == 2
class TestSomething: # 以 Test 开头的类
def test_method(self): # 以 test_ 开头的方法
assert True
```
### 运行方式
```bash
# 1. 自动搜索:在当前目录及其子目录中搜索
pytest
# 2. 指定目录
pytest tests/
pytest src/
# 3. 指定特定文件
pytest main.py
# 4. 运行特定测试函数 (文件::函数)
pytest tests/test_basic.py::test_addition
```
---
## 3. 测试函数 (Test Functions)
测试函数是 pytest 的最基本单元。
### 基本特征
- 函数名必须以 `test_` 开头。
- 通常不接受参数(除非使用 `fixture``parametrize`)。
- 使用 `assert` 语句进行断言。
- 通过返回值或异常来表示测试结果。
### 断言与异常处理
#### A. 普通断言 (期望成功)
验证代码逻辑是否符合预期。
```python
def test_basic():
assert 1 == 1 # 如果条件为 False,测试失败
```
#### B. 异常断言 (期望抛出错误)
验证代码在错误输入下是否正确抛出了异常(使用 `pytest.raises`)。
```python
import pytest
def divide(a, b):
if b == 0:
raise ValueError("b cannot be 0")
return a / b
def test_divide_zero():
# 断言:执行 divide(1, 0) 时应该抛出 ValueError
with pytest.raises(ValueError):
divide(1, 0)
```
---
## 4. Fixture 夹具 (Fixtures)
Fixture 用于处理测试前置准备(Setup)和后置清理(Teardown),以及数据共享。
### 基本用法
```python
import pytest
@pytest.fixture
def sample_data():
"""返回测试数据"""
print('>>> Setup')
# yield 之前的代码相当于 Setup
yield {"name": "Alice", "age": 30, "city": "New York"}
# yield 之后的代码相当于 Teardown
print('<<< Teardown')
def test_user_name(sample_data):
# sample_data 作为参数传入,获取 yield 返回的值
assert sample_data["name"] == "Alice"
```
> **提示**:要看到 fixture 中的 print 输出,运行时需要加 `-s` 参数:`pytest -s`
### Fixture 作用域 (Scope)
通过 `@pytest.fixture(scope="...")` 指定共享范围:
| 作用域 | 说明 | 典型应用场景 |
| :--- | :--- | :--- |
| **function** (默认) | 每个测试函数运行一次 | 独立的数据、清理数据库记录 |
| **class** | 每个测试类运行一次 | 测试类共享的配置 |
| **module** | 每个模块 (`.py`文件) 运行一次 | 模块级别的连接 |
| **session** | 整个测试会话只运行一次 | 全局配置、浏览器实例、数据库连接池 |
---
## 5. Mark 标记 (Marks)
使用 Mark 给测试分类,便于筛选运行。
### 定义标记
```python
import pytest
@pytest.mark.p1
def test_feature_a():
assert True
@pytest.mark.slow
def test_network_op():
pass
@pytest.mark.p1
@pytest.mark.slow
def test_complex():
pass
```
### 运行指定标记
使用 `-m` 参数支持逻辑表达式:
```bash
# 运行标记为 p1 的测试
pytest -m "p1"
# 运行标记为 p1 或 slow 的测试
pytest -m "p1 or slow"
# 运行同时标记为 p1 和 slow 的测试
pytest -m "p1 and slow"
# 运行不包含 p1 标记的测试
pytest -m "not p1"
```
---
## 6. 参数化 (Parametrize)
使用 `@pytest.mark.parametrize` 实现数据驱动测试,用多组数据测试同一个逻辑。
```python
import pytest
# 定义参数名和参数值列表
@pytest.mark.parametrize("x, y, expected", [
(1, 2, 3),
(4, 5, 9),
(10, 20, 30),
])
def test_addition(x, y, expected):
assert x + y == expected
```
---
## 7. 常用命令行参数
| 命令 | 作用 | 示例 |
| :--- | :--- | :--- |
| `pytest -v` | **Verbose**,显示详细输出 | 查看每个测试函数的执行结果 |
| `pytest -s` | **Standard Output**,捕获/显示标准输出 | 查看 print 语句的输出 |
| `pytest -m` | **Marker**,根据标记运行 | `pytest -m "smoke"` |
| `pytest -k` | **Keyword**,根据名称关键字运行 | `pytest -k "user or login"` |
| `pytest -n` | **Num processes**,多进程并行运行 | `pytest -n 4` (需要 `pytest-xdist` 插件) |