
从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个实用的解决办法:
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=report.html
,会在当前目录生成报告文件; pip install allure-pytest
,运行pytest alluredir=./allure-results
,然后用allure serve ./allure-results
打开报告; 我之前给领导汇报测试进度时,用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%。