用 pytest 构建稳健的测试:fixture 注入、基于 yield 的清理、表驱动的参数化,以及大作用域带来的陷阱。
在 Kodokon 中打开本课pytest 建立在两个强有力的想法之上:按约定发现测试(test_*.py 文件、test_* 函数),以及朴素的 assert - 它的内省机制会在失败时显示两侧的实际值,再也不用背诵 assertEqual 和它那三十种变体。有两件工具值得马上认识:pytest.raises 用来检查异常确实被抛出,pytest.approx 用来比较浮点数,免得和二进制舍入较劲。
import pytest
def divide(a: float, b: float) -> float:
return a / b
def test_nominal() -> None:
assert divide(8, 4) == 2.0
def test_zero_division() -> None:
with pytest.raises(ZeroDivisionError):
divide(1, 0)
def test_floats() -> None:
assert 0.1 + 0.2 == pytest.approx(0.3)Fixture 就是 pytest 的依赖注入:只要声明一个与某个 fixture 同名的参数,pytest 就会构造它并注入进来。yield 把准备工作和清理工作分开 - 它之后的代码即使测试失败也会运行。scope 参数(默认是 function,还有 module、session)控制着对象的生命周期。把共享的 fixture 放进 conftest.py:它们就能在整个文件夹里使用,连一条 import 都不用写。
import sqlite3
import pytest
@pytest.fixture
def db():
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE users (name TEXT)")
yield conn
conn.close()
def test_insert(db) -> None:
db.execute("INSERT INTO users VALUES ('Ada')")
query = "SELECT name FROM users"
rows = db.execute(query).fetchall()
assert rows == [("Ada",)]@pytest.mark.parametrize 把一个测试变成一张用例表:每个元组都成为一个独立的测试,可以单独命名和筛选。叠加多个 parametrize 装饰器,就能得到这些参数的笛卡尔积。请通过 pytest.param(..., id='...') 给你的用例起可读的 id - 一份失败报告应该无需打开源文件就能看懂。
import pytest
def slugify(text: str) -> str:
return "-".join(text.lower().split())
@pytest.mark.parametrize(
("raw", "expected"),
[
("Hello World", "hello-world"),
(" Python rocks ", "python-rocks"),
pytest.param("", "", id="empty"),
],
)
def test_slugify(raw: str, expected: str) -> None:
assert slugify(raw) == expectedpytest -x --lf
pytest -k "slugify and not empty"
pytest -q --durations=5yield 之后的代码有什么用?pytest --lf 这条命令做什么?