pytest

生成项目结构

1
2
3
4
─src
│ └─pytest_demo
│ calculator.py
│ __init__.py

生成calculator.py类用于后续学习pytest,以上树状结构使用tree /f生成

__init__.py 文件的作用

__init__.py 文件的主要作用是告诉Python解释器这个目录是一个Python包(package)。当Python看到一个包含 __init__.py 文件的目录时,就会将其识别为一个可导入的包。

1
2
3
4
5
6
7
"""
pytest_demo package
"""

from .calculator import Calculator

__all__ = ['Calculator']

相对导入 : from .calculator import Calculator

  • .calculator 表示从当前包内的 calculator.py 模块导入 Calculator 类
  • 点号 . 表示相对导入,指向当前包

__all__ 列表 : __all__ = ['Calculator']

  • 定义了当使用 from pytest_demo import * 时会导入哪些对象
  • 这是一个显式的公共API声明

pytest 的自动发现规则

pytest 会自动查找以下内容:

  • 文件名:test_*.py*_test.py
  • 函数名:test_*()
  • 类名:Test*(类中方法也需以 test_ 开头)

测试文件

1
2
└─tests
│ test_calculator.py
1
2
3
4
5
6
7
8
9
10
from pytest_demo.calculator import Calculator

def test_add() -> None:
calc = Calculator()
assert calc.add(2, 1) == 3


def test_div() -> None:
calc = Calculator()
assert calc.divide(2, 1) == 2

运行 uv run pytest

image-20251018194456963

vscode支持pytest的可视化页面

ctrl+shift+p搜索

image-20251018194539467
image-20251018194723165

使用pytest.raises验证异常

1
2
3
4
5
6
import pytest

def test_divide_by_zero() -> None:
calc = Calculator()
with pytest.raises(ZeroDivisionError):
calc.divide(2, 0)

当你测试的函数应该在特定条件下抛出异常(比如传入非法参数、除零错误等),你可以用 pytest.raises 来验证:

“这段代码是否如预期那样,抛出了我们想要的异常?”

如果:

  • ✅ 抛出了指定类型的异常 → 测试通过
  • ❌ 没有抛出异常 → 测试失败
  • ❌ 抛出了其他类型的异常 → 测试失败

@pytest.fixture提供测试参数

@pytest.fixturepytest 中最核心、最强大的功能之一,它的作用是:

为测试函数提供可复用的、隔离的“测试依赖”(如对象、数据、资源、环境等),并管理它们的生命周期。

1
2
3
4
5
6
7
#工厂函数
@pytest.fixture
def calc() -> Calculator:
return Calculator()

def test_add(calc: Calculator) -> None:
assert calc.add(2, 1) == 3

在 pytest 中,当测试函数(或另一个 fixture)的参数名与某个 fixture 的名称相同时,pytest 会自动调用该 fixture,并将其返回值传入测试函数

这是 pytest 依赖注入机制的核心,也是 fixture 能“自动生效”的原因。

工厂函数(Factory Function) 是一种返回对象(通常是类的实例或其他函数)的函数,它的名字来源于“工厂模式”——就像工厂生产产品一样,这个函数“生产”对象。

scope 参数

1
2
3
4
5
6
7
8
9
10
@pytest.fixture(scope="session")  # 整个测试会话只启动一次
def browser():
driver = webdriver.Chrome()
yield driver
driver.quit()

def test_login(browser):
browser.get("/login")
def test_profile(browser):
browser.get("/profile")

好处:避免创建多个实例

支持的作用域:

  • function(默认):每个测试函数
  • class:每个测试类
  • module:每个 .py 文件
  • session:整个测试运行

自动管理资源生命周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pytest.fixture
def temp_file():
# setup: 创建临时文件
path = "temp.txt"
with open(path, "w") as f:
f.write("hello")

yield path # 测试函数在此处获得 path

# teardown: 清理
os.remove(path)

def test_read_file(temp_file):
with open(temp_file) as f:
assert f.read() == "hello"