3. pytest入门:命名规则、断言机制、运行模式、测试用例编写

好,咱们今天来聊聊pytest。说实话,我刚开始做自动化测试那会儿,用的还是unittest。后来有一次接手一个大型项目,用例数量上千,unittest跑起来那个慢啊……同事推荐我试试pytest,一试就回不去了。pytest给我的感觉就是——简洁、灵活、强大。你想想看,一个框架能同时搞定单元测试和端到端测试,还能跟各种插件无缝集成,这得多省心。

3.1 pytest的命名规则

pytest最方便的一点,就是它的自动发现机制。你不需要手动注册用例,它自己就能找到。但前提是——你得按它的规矩来命名。

核心规则:

  • 测试文件:以 test_ 开头,或以 _test 结尾。比如 test_login.pylogin_test.py
  • 测试类:以 Test 开头。注意首字母大写,比如 TestUserLogin
  • 测试方法/函数:以 test_ 开头。比如 test_login_success

我个人习惯把测试文件命名为 test_模块名.py,这样一眼就能看出测的是哪个模块。比如测登录功能,就叫 test_login.py。嗯,这里要注意:测试类里不要写 __init__ 方法,否则pytest会报错。我刚开始就踩过这个坑。

# test_demo.py
class TestCalculator:
    def test_add(self):
        assert 1 + 1 == 2

    def test_subtract(self):
        assert 3 - 1 == 2

3.2 断言机制

pytest的断言,说白了就是用Python原生的 assert 关键字。这一点跟unittest那种 self.assertEqual() 的写法比起来,简直不要太清爽。

为什么会这样?因为pytest在底层对 assert 做了增强。当断言失败时,它会自动输出详细的上下文信息,告诉你实际值和期望值差了多少。我在项目中遇到过好几次,一个断言失败,pytest直接打印出两个列表的差异,省了我大量调试时间。

常用断言写法:

  • assert a == b —— 判断相等
  • assert a != b —— 判断不等
  • assert a in b —— 判断包含
  • assert a is None —— 判断为空
  • assert a > 0 —— 判断大小
  • assert bool(a) is True —— 判断布尔值
def test_list_contains():
    my_list = [1, 2, 3, 4, 5]
    assert 3 in my_list  # 判断元素是否在列表中

def test_string_contains():
    text = "Hello, pytest!"
    assert "pytest" in text  # 判断子串

def test_dictionary():
    user = {"name": "Alice", "age": 30}
    assert user["age"] == 30
    assert "name" in user  # 判断键是否存在

避坑指南:我曾经在断言浮点数相等时吃过亏。两个浮点数明明看起来一样,但断言就是失败。后来才发现是精度问题。建议用 pytest.approx() 来处理浮点数比较。

def test_float():
    assert 0.1 + 0.2 == pytest.approx(0.3)  # 使用approx避免精度问题

3.3 运行模式

pytest的运行模式非常灵活。你可以跑单个文件、单个函数,甚至按标签筛选。我平时最常用的就这几种:

命令 说明 示例
pytest 运行当前目录下所有测试 直接执行,自动发现
pytest 文件名.py 运行指定文件 pytest test_login.py
pytest -k "关键字" 按名称匹配运行 pytest -k "login"
pytest -v 显示详细输出 每个用例名称都会打印
pytest -s 显示print输出 调试时特别有用
pytest --tb=short 缩短错误回溯 只看关键错误信息

举个例子,你只想跑登录相关的用例,但又不想改代码,直接用 pytest -k "login" -v 就行。我个人习惯在调试时加上 -s 参数,这样能看到print的中间结果,排查问题快很多。

3.4 测试用例编写

写测试用例,说白了就是三步:准备数据、执行操作、验证结果。pytest让这三步变得特别自然。

先看一个最简单的例子:

# test_calculator.py
def test_add():
    # 准备数据
    a, b = 3, 5
    # 执行操作
    result = a + b
    # 验证结果
    assert result == 8

def test_subtract():
    assert 10 - 4 == 6

你可能会问:就这么简单?对,就这么简单。pytest不强制你用类,直接写函数就行。当然,如果用例之间有依赖关系,或者需要共享一些数据,那就用类来组织。

用类组织用例的好处:

  • 相关用例放在一起,结构清晰
  • 可以用 setup_method 做前置准备
  • 方便用 @pytest.mark 打标签
class TestUserAPI:
    def setup_method(self):
        # 每个测试方法执行前都会运行
        self.user_data = {"name": "test_user", "age": 25}

    def test_create_user(self):
        # 模拟创建用户
        user = create_user(self.user_data)
        assert user["name"] == "test_user"
        assert user["age"] == 25

    def test_update_user(self):
        # 模拟更新用户
        updated = update_user(1, {"age": 26})
        assert updated["age"] == 26

我的经验:写测试用例时,尽量做到一个用例只测一个功能点。比如测登录,就分三个用例:成功登录、密码错误、账号不存在。这样哪个环节出问题,一眼就能定位。我曾经见过有人一个用例里写了十几个断言,失败了都不知道是哪个条件没满足……

最后,别忘了pytest的标签功能。你可以给用例打上 @pytest.mark.smoke@pytest.mark.regression 这样的标签,然后按标签运行:

import pytest

@pytest.mark.smoke
def test_login_success():
    assert login("admin", "123456") == True

@pytest.mark.regression
def test_login_failed():
    assert login("admin", "wrong") == False

运行命令:pytest -m smoke,就只跑冒烟测试的用例。这个在持续集成里特别实用。

好了,pytest入门就聊到这儿。记住:命名规则别搞错,断言用assert,运行模式按需选,用例结构要清晰。下一章咱们聊聊fixture,那才是pytest真正强大的地方。