Python单元测试框架pytest实战指南:零基础入门到高效测试,避坑技巧全总结

Python单元测试框架pytest实战指南:零基础入门到高效测试,避坑技巧全总结 一

文章目录CloseOpen

从0到1上手pytest:基础操作全解析

5分钟装好pytest,搭建你的测试环境

刚开始用pytest时,我以为安装会很复杂,结果发现比装Python库还简单。你只要打开终端,输入pip install pytest,等个10秒左右就装好了。装完后记得验证一下,输入pytest version,如果显示类似pytest 7.4.0的版本号,就说明装成功了。

不过这里有个小细节要提醒你:如果你用的是虚拟环境(比如venv或conda),一定要先激活环境再安装,不然可能装到全局Python里,后面用的时候容易混乱。我之前在公司服务器上装pytest时,因为没激活虚拟环境,导致不同项目依赖的pytest版本冲突,后来养成了“先激活环境再装包”的习惯,就再也没出过这种问题。如果你是Windows系统,终端可能需要用pip install pytest user(加user参数避免权限问题),亲测这个方法对新手特别友好。

测试用例不用愁:3步搞定第一个pytest测试

装好环境后,咱们来写第一个测试用例。其实pytest的设计特别“懂开发者”,你几乎不用学新语法,跟着这3步走就行:

第一步,创建测试文件。pytest默认会识别test_开头的.py文件(比如test_math.py),所以你新建文件时直接这么命名,后面运行测试会方便很多。我刚开始总忘记加test_前缀,结果运行时pytest找不到用例,后来养成了“新建文件先输test_”的肌肉记忆,效率提升不少。

第二步,写测试函数。测试函数也要以test_开头(比如test_add()),里面用assert断言判断结果是否符合预期。举个例子,如果你要测试一个加法函数:

# math_functions.py(被测试的代码)

def add(a, b):

return a + b

test_math.py(测试文件)

from math_functions import add

def test_add():

result = add(2, 3)

assert result == 5, "2+3应该等于5" # 逗号后面是断言失败时的提示信息

这里的assert就是“断言”,你可以理解成“我断定这个结果应该是这样,如果不是就报错”。比起Python自带的unittest框架要写self.assertEqual(),pytest的assert简洁多了,新手几乎不用记语法。

第三步,运行测试。在终端进入测试文件所在的文件夹,输入pytest命令,pytest会自动找到所有test_开头的文件和函数,执行测试并输出结果。如果看到绿色的“PASSED”,就说明测试通过了;如果是红色的“FAILED”,会告诉你哪个断言失败,甚至把实际结果和预期结果列出来,特别直观。

断言、参数化、fixture:让测试效率翻倍的3个核心技巧

学会写基础用例后,咱们来解锁几个“提效神器”,这些功能能让你的测试代码更简洁、更聪明。

先说断言。pytest的assert不止能判断相等,还支持各种比较逻辑,比如判断包含关系(assert "a" in ["a", "b"])、布尔值(assert len([]) == 0)、异常(with pytest.raises(ValueError): int("abc"))。我之前测试一个用户注册接口,需要校验“密码长度小于6位时抛异常”,用pytest.raises一行代码就搞定了,比手动try-except清爽太多。

然后是参数化测试。如果你要测试多种输入情况(比如正常值、边界值、异常值),不用写多个测试函数,用@pytest.mark.parametrize装饰器就能批量跑测试。比如测试加法函数的多种情况:

import pytest

from math_functions import add

@pytest.mark.parametrize("a, b, expected", [

(2, 3, 5), # 正常情况

(0, 0, 0), # 边界值

(-1, 1, 0), # 负数情况

(2.5, 3.5, 6.0) # 浮点数情况

])

def test_add_parametrized(a, b, expected):

assert add(a, b) == expected

这样运行时,pytest会自动把参数列表里的每组数据都跑一遍,相当于同时测试4个用例。我之前写支付接口测试时,用参数化测试了10种支付金额场景,代码量比写10个函数减少了60%,后期改测试数据也只要改参数列表,特别方便。

最后是fixture,这可是pytest的“王牌功能”。你可以把fixture理解成“测试用例的管家”:帮你准备前置条件(比如连接数据库、登录获取token),用完后还能清理环境(比如关闭连接、删除测试数据)。

定义fixture很简单,用@pytest.fixture装饰器就行,比如创建一个“连接测试数据库”的fixture:

import pytest

import pymysql

@pytest.fixture(scope="module") # scope表示作用域,module代表“每个模块执行一次”

def db_connection():

# 前置操作:连接数据库

conn = pymysql.connect(host="localhost", user="test", password="test123", db="test_db")

yield conn # yield前面是前置操作,后面是后置操作

# 后置操作:关闭连接

conn.close()

测试函数直接通过参数调用fixture

def test_query_user(db_connection):

cursor = db_connection.cursor()

cursor.execute("SELECT name FROM users WHERE id=1")

result = cursor.fetchone()

assert result[0] == "test_user"

这里的scope="module"特别实用,意思是“整个模块(test_xxx.py文件)只连接一次数据库”,避免每个用例都连一次,大大提高效率。pytest官方文档(https://docs.pytest.org/en/stable/fixture.html nofollow)里提到,fixture比传统的setup/teardown更灵活,支持依赖注入——简单说就是“谁需要谁调用”,不用像setup那样所有用例都必须执行前置操作。我之前做接口测试时,用fixture封装了“登录获取token”,20个用例共用一个token,代码量直接减少了一半。

pytest实战避坑指南:解决90%的测试难题

学会基础操作后,你在实际用的时候可能会遇到各种“坑”。我整理了3个新手最常踩的坑,附带上解决方案,照着做能少走很多弯路。

测试环境“打架”?用fixture作用域隔离依赖

你有没有遇到过这种情况:多个测试用例修改了同一个全局变量,导致后面的用例结果出错?或者测试数据库里的数据越跑越多,最后影响测试结果?这其实是“测试环境隔离”没做好。

解决办法就是合理设置fixture的scope参数。除了前面提到的module,pytest的fixture还有4种作用域,我整理了一张表,你可以根据场景选择:

作用域 执行时机 适用场景
function 每个测试函数执行一次 创建临时文件、生成随机数据
class 每个测试类执行一次 类内共享的前置条件(如初始化对象)
module 每个模块(.py文件)执行一次 数据库连接、配置加载
session 整个测试会话执行一次 启动测试服务、全局配置

比如你测试接口时,“登录获取token”可以设为scope="session",整个测试过程只登录一次;而“创建测试用户”设为scope="function",每个用例结束后删除用户,避免数据残留。我之前团队里有人把数据库连接设为function级,结果100个用例连了100次数据库,测试时间从2分钟变成了15分钟,后来改成module级才恢复正常。

用例执行顺序乱了?3个方法让测试“按规矩来”

pytest默认会按文件名和函数名的ASCII码排序执行用例,比如test_b()会比test_a()先执行。这就可能出问题:如果你的用例有依赖关系(比如先注册用户,再查询用户),顺序乱了就会失败。

分享3个实用的解决办法:

  • 用fixture依赖控制顺序:让需要后执行的用例依赖前置用例的fixture。比如“查询用户”用例依赖“注册用户”的fixture,pytest会自动先执行前置fixture;
  • 用pytest-order插件指定顺序:安装插件(pip install pytest-order)后,用@pytest.mark.order(n)标记用例顺序,比如@pytest.mark.order(1)是第一个执行,@pytest.mark.order(2)是第二个;
  • 设计独立的测试用例:这是最推荐的方式——尽量让每个用例不依赖其他用例,比如测试“查询用户”时,自己创建用户、自己清理,虽然多写几行代码,但维护成本低很多。
  • 我之前帮一个朋友排查测试问题,他的用例总是失败,后来发现是test_delete_user()test_create_user()先执行了,导致删除了一个不存在的用户。用pytest-order指定顺序后,问题立刻解决。不过这里提醒一句:不到万不得已,尽量别依赖用例顺序,独立的用例才是“健壮”的用例。

    测试报告“看不懂”?3个插件让结果可视化

    写完用例跑通了,怎么给团队或领导展示测试结果呢?默认的终端输出虽然详细,但不够直观,特别是用例多的时候。推荐3个插件,帮你生成清晰的测试报告:

  • pytest-html:生成HTML格式报告,包含用例通过率、失败详情、执行时间,打开浏览器就能看。安装后运行pytest html=report.html,会在当前目录生成报告文件;
  • allure-pytest:生成交互式报告,支持图表展示(比如通过率趋势、用例执行时间分布),还能给用例打标签分类。需要先装allure工具(官网有教程),再装插件pip install allure-pytest,运行pytest alluredir=./allure-results,然后用allure serve ./allure-results打开报告;
  • pytest-metadata:在报告中添加环境信息(比如Python版本、pytest版本、测试环境),方便排查环境相关的问题。
  • 我之前给领导汇报测试进度时,用allure报告展示“核心模块测试覆盖率95%”“接口测试通过率100%”,领导一眼就看明白了。现在团队每周都会生成allure报告,发在群里同步进度,比口头汇报高效多了。

    好了,以上就是我用pytest三年 的实战经验,从基础操作到避坑技巧,覆盖了你入门到熟练的大部分场景。你现在可以挑一个你手头的小项目,试着用pytest写几个测试用例,遇到问题随时回来翻这篇指南。如果按这些方法试了,或者有其他测试难题,欢迎在评论区告诉我,我们一起讨论解决!


    测试用例失败的时候,你是不是经常盯着屏幕发呆,不知道从哪儿下手?其实pytest早就把“线索”都给你准备好了,就看你会不会抓重点。你仔细看失败时的红色提示,第一行肯定是你的断言内容,比如“assert add(2, 3) == 6”,后面可能还跟着你自己写的提示语“2+3应该等于5”,这时候不用多想,十有八九是被测试的函数算错了结果,或者你预期值写反了。再往下看,会有“实际结果”和“预期结果”的对比,比如“实际:5,预期:6”,一目了然哪里对不上。

    最关键的是错误堆栈,就是那些带着文件路径和行号的文字,比如“test_math.py:10: AssertionError”,意思就是“test_math.py文件的第10行断言失败了”。你直接点进那个文件,找到第10行,看看上下文代码,基本就能定位到问题。要是遇到接口调用或者数据库操作失败,光看断言可能不够,这时候别犹豫,在测试用例里加几行print,把中间结果输出来——比如调用接口前,print一下“请求参数:{params}”,接口返回后,print一下“接口响应:{response.json()}”,跑完测试再看终端日志,中间哪一步出了岔子,比如参数少传了个字段,或者返回的code不是200,立马就能发现。我之前测一个订单支付接口,总提示“金额格式错误”,加了print打印传的金额,才发现把“100.00”写成了“100”,少了两位小数,改完就好了,前后也就3分钟的事儿,比瞎猜快多了。

    有时候错误提示里还会有“During setup of fixture db_connection”这种字样,这说明不是测试用例本身的问题,而是前置的fixture出错了,比如数据库连不上、登录接口返回401。这时候你就重点看fixture的代码,检查连接参数对不对,或者登录的token是不是过期了。我之前就遇到过fixture里的数据库密码写成了旧密码,导致所有依赖这个fixture的用例都失败,改完密码重启测试,一下子就全过了。记住,测试失败不可怕,跟着错误提示一步步拆,就像破案一样,线索都在眼前,就看你细不细心。


    pytest和Python自带的unittest有什么区别?

    最直观的区别是使用门槛:unittest需要继承TestCase类,用self.assertEqual()等断言方法,语法相对繁琐;而pytest支持普通函数+assert断言,几乎不用学新语法,新手更容易上手。另外pytest的扩展性更强,比如通过fixture实现灵活的前置/后置操作,支持参数化测试、插件生态丰富(如生成HTML报告、控制用例顺序),这些都是unittest需要额外配置或不支持的。我之前带团队时,把unittest项目迁移到pytest后,测试代码量减少了40%,执行效率也提升了不少。

    测试用例失败时,如何快速定位问题原因?

    pytest的错误提示已经很详细了,失败时会显示断言内容、实际结果vs预期结果,以及错误堆栈。你可以重点看这几点:首先检查assert后面的提示信息(比如“2+3应该等于5”),快速定位断言逻辑;然后看堆栈中的文件和行号,找到具体哪行代码出错;如果是接口或数据库相关的失败, 在测试用例里临时加print输出中间结果(比如“打印查询到的用户数据:xxx”),跑完测试后通过日志排查。我之前遇到一个“接口返回None”的失败,加了print发现是请求参数少传了一个必填字段,3分钟就定位到问题。

    如何用pytest提高代码的测试覆盖率?

    可以用pytest-cov插件,安装后运行命令“pytest cov=被测试的模块名”,会生成覆盖率报告,显示哪些代码行没被测试到。比如“math_functions.py”的覆盖率报告里,如果add()函数的“a为负数”分支显示“未覆盖”,你就需要补充对应测试用例。另外 从这3个维度设计用例:正常输入(如常规参数)、边界值(如0、None、空字符串)、异常输入(如类型错误、长度超限)。我之前帮项目做覆盖率优化时,按这3个维度补充用例后,覆盖率从60%提升到了92%。

    fixture的作用域应该如何选择?有没有简单的判断方法?

    记住一个原则:“用例依赖越局部,作用域越小”。比如每个用例都需要独立的测试数据(如临时用户),选function级;多个用例共享一个数据库连接,选module级;整个测试会话只需要初始化一次的资源(如启动测试服务),选session级。举个例子:接口测试中,“登录获取token”可以设为session级(一次登录用到底),“创建测试订单”设为function级(每个用例后删除订单),这样既能减少重复操作,又能避免数据干扰。我团队的经验是,90%的场景用function和module级就够了,session级除非确认资源消耗大(如启动Docker容器),否则不用。

    除了文中提到的插件,还有哪些实用的pytest插件推荐?

    推荐3个我日常高频使用的:pytest-xdist(并行执行测试,用例多的时候能省50%时间,命令加“-n auto”自动分配CPU核数)、pytest-mock(简化mock操作,比如模拟接口返回、禁止真实发送邮件,比unittest.mock更易用)、pytest-rerunfailures(失败用例自动重试,命令加“reruns 2”失败后重试2次,适合偶尔不稳定的接口测试)。安装方法和文中一样,pip install插件名就行。比如我测第三方支付接口时,偶尔会因网络波动失败,用rerunfailures重试2次后,测试通过率从85%提升到了99%。

    0
    显示验证码
    没有账号?注册  忘记密码?