3、Pytest基础入门:安装配置、断言机制与用例规范

好,咱们今天聊聊Pytest。说实话,在Python的测试框架里,Pytest是我用得最顺手的一个。它不像unittest那么「规矩」,也不像nose那么「佛系」。Pytest给我的感觉就是——该有的都有,不该有的绝不啰嗦。

我刚开始带团队的时候,大家用的测试框架五花八门。后来我统一推了Pytest,原因很简单:它能让测试代码写起来像说话一样自然。你想想看,一个测试框架如果连入门都要折腾半天,那谁还愿意写测试?

3.1 Pytest安装与配置

安装Pytest,说白了就一行命令的事:

pip install pytest

嗯,就这么简单。但我建议你装在一个干净的虚拟环境里。我在项目中遇到过好几次因为全局包冲突导致测试跑不起来的情况,后来学乖了,每个项目都单独建虚拟环境。

验证安装是否成功,可以跑一下:

pytest --version

如果看到版本号,那就说明装好了。我个人习惯还会装两个插件,一个是pytest-html用来生成测试报告,另一个是pytest-cov用来统计代码覆盖率。安装命令如下:

pip install pytest-html pytest-cov
小提示:如果你用的是PyCharm,记得在Settings里把默认测试运行器改成Pytest。不然每次右键跑测试,它默认用unittest,会报一些莫名其妙的错。我曾经因为这个折腾了半小时...

配置方面,Pytest支持一个pytest.ini文件,放在项目根目录就行。我一般这样配:

[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts = -v --tb=short

这里解释一下:testpaths指定测试文件放在tests目录下;python_files告诉Pytest只找以test_开头的文件;addopts里的-v是详细输出,--tb=short是让错误信息短一点,不然刷屏刷得你眼花。

3.2 断言机制

Pytest的断言,是我最喜欢它的地方。它直接用Python原生的assert关键字,不需要像unittest那样记一堆assertEqualassertTrue之类的方法。

举个例子:

def test_add():
    result = add(1, 2)
    assert result == 3

def test_string():
    name = "pytest"
    assert "py" in name

def test_list():
    items = [1, 2, 3]
    assert len(items) == 3
    assert 2 in items

你看,是不是很直观?assert后面跟一个条件表达式,条件为真就通过,为假就失败。Pytest会自动捕获断言失败的信息,告诉你期望值和实际值分别是多少。

重点:Pytest的断言失败信息非常详细。比如assert result == 3失败了,它会告诉你result实际是多少,3又是多少。这在调试的时候特别有用。

我遇到过一种情况,就是断言浮点数相等。直接assert 0.1 + 0.2 == 0.3会失败,因为浮点数精度问题。这时候可以用pytest.approx

def test_float():
    assert 0.1 + 0.2 == pytest.approx(0.3)

嗯,这个坑我踩过,后来就养成了习惯——凡是浮点数比较,一律用approx

再来说说异常断言。有时候我们需要测试某个函数在特定输入下会不会抛出异常:

def test_divide_by_zero():
    with pytest.raises(ZeroDivisionError):
        result = 1 / 0

def test_custom_exception():
    with pytest.raises(ValueError, match="无效的输入"):
        validate_input(-1)

pytest.raises是一个上下文管理器,它捕获指定类型的异常。match参数可以匹配异常信息中的关键字,这样能更精确地判断异常是不是我们想要的那个。

3.3 测试用例编写规范

写测试用例,说白了就是写代码。但测试代码和业务代码的写法不太一样。我总结了几条我自己一直在用的规范:

  • 文件名以test_开头,比如test_user.pytest_order.py。这样Pytest会自动发现。
  • 测试类以Test开头,比如TestUserLogin。类里不能有__init__方法,不然Pytest会忽略它。
  • 测试函数以test_开头,比如test_login_success。函数名要能看出测试的是什么场景。
  • 一个测试函数只测一个功能点。别在一个函数里又测登录又测注册,那样失败了都不知道是哪一步出的问题。

举个例子,一个规范的测试文件长这样:

# test_user.py

class TestUserLogin:
    def test_login_success(self):
        # 测试登录成功
        assert login("admin", "123456") == True

    def test_login_failed_wrong_password(self):
        # 测试密码错误
        assert login("admin", "wrong") == False

    def test_login_failed_user_not_exist(self):
        # 测试用户不存在
        assert login("nobody", "123456") == False

你看,每个函数只测一种情况。这样如果某个场景失败了,一眼就能看出来是哪个分支出了问题。

注意:测试类名不要用__init__方法。如果你需要初始化数据,用setup_method或者fixture。我曾经看到有人直接在类里写__init__,结果测试用例一个都没跑...嗯,那个「有人」其实就是我。

3.4 运行测试用例的多种方式

Pytest运行测试的方式非常灵活。我平时用得最多的就这几种:

3.4.1 命令行直接运行

最基础的方式,在终端里敲:

pytest

这会自动发现当前目录及子目录下所有符合规则的测试文件并执行。如果你想指定某个文件:

pytest tests/test_user.py

或者只跑某个类里的测试:

pytest tests/test_user.py::TestUserLogin

甚至只跑某个具体的测试函数:

pytest tests/test_user.py::TestUserLogin::test_login_success

这种精确到函数级别的运行方式,在调试单个用例时特别有用。我经常在改完代码后,只跑那个相关的测试,而不是全量跑一遍。

3.4.2 按关键字筛选

有时候你想跑所有跟「login」相关的测试,可以用-k参数:

pytest -k "login"

这会匹配文件名、类名、函数名中包含「login」的所有测试。还可以用andornot组合:

pytest -k "login and not failed"

这个功能我在做回归测试的时候经常用。比如只跑「新增功能」相关的测试,或者排除「已知失败」的测试。

3.4.3 生成测试报告

如果你装了pytest-html插件,可以生成漂亮的HTML报告:

pytest --html=report.html

打开report.html,能看到每个用例的执行结果、耗时、错误信息。我每次给领导汇报测试结果,都是直接甩这个报告过去,省得自己写PPT了。

3.4.4 并行执行测试

当测试用例多起来之后,串行执行太慢了。这时候可以用pytest-xdist插件:

pip install pytest-xdist
pytest -n 4

-n 4表示用4个进程并行跑。我有个项目有2000多个测试用例,串行跑要20分钟,用了4个进程后,5分钟就跑完了。嗯,真香。

小技巧:并行执行时,如果测试之间有依赖关系,可能会出问题。我的建议是——测试用例之间不要有依赖。每个用例都应该能独立运行,不依赖其他用例的执行顺序或结果。

3.4.5 失败重跑

有些测试不稳定,偶尔会失败。可以用pytest-rerunfailures插件自动重跑:

pip install pytest-rerunfailures
pytest --reruns 3

这会让失败的用例最多重跑3次。如果3次里有一次通过了,就算通过。我在做UI自动化测试时经常用这个,因为页面加载偶尔会超时,重跑一次就好了。

3.5 总结

好了,Pytest的基础入门就聊到这儿。总结一下今天的内容:

知识点 要点
安装配置 pip安装,pytest.ini配置,推荐装html和cov插件
断言机制 原生assert,pytest.approx处理浮点数,pytest.raises处理异常
编写规范 文件/类/函数命名规则,一个函数只测一个点
运行方式 命令行、关键字筛选、HTML报告、并行执行、失败重跑

下一章咱们会深入聊聊Pytest的fixture机制,那可是Pytest最强大的功能之一。到时候我会分享一些我在项目中用fixture做数据准备和清理的实战经验。咱们下章见。