跳到主要内容 Pytest 自动化测试框架基础 | 极客日志
Python
Pytest 自动化测试框架基础 Pytest 是 Python 第三方自动化测试框架,相比官方 unittest 更灵活且兼容性好。支持通过标记(mark)控制用例运行,如跳过、失败预期等。固件(Fixture)用于预处理和后处理,支持作用域(function/class/module/session)及自动执行(autouse)。参数化测试允许单用例多数据输入。学习自动化测试可节省重复工作,优化代码结构并提高覆盖率。
苹果系统 发布于 2025/2/7 更新于 2026/4/19 1 浏览Pytest 和 Unittest 测试框架的区别?
如何区分这两者,很简单:unittest 作为官方的测试框架,在测试方面更加基础,并且可以在此基础上进行二次开发,同时在用法上格式会更加复杂;而 pytest 框架作为第三方框架,方便的地方就在于使用更加灵活,并且能够对原有 unittest 风格的测试用例有很好的兼容性,同时在扩展上更加丰富,可通过扩展的插件增加使用的场景,比如一些并发测试等。
Pytest 安装
pip 安装:
pip install pytest
测试安装成功:
pytest --help
py.test --help
检查安装版本:
pytest --version
Pytest 示例
Pytest 编写规则:
测试文件以 test_开头(以_test为结尾)
测试的类以 Test 开头;
测试的方法以 test_开头
断言使用基本的 assert
test_example.py
def count_num (a: list ) -> int :
return len (a)
def test_count ():
assert count_num([1 , 2 , 3 ]) != 3
执行测试:
pytest test_example.py
执行结果:
platform win32 -- Python 3.6.8, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 -- <python_path >
cachedir: .pytest_cache
rootdir: <project_path >
plugins: Faker-8.11.0
collected 1 item
test_ example.py::test_count FAILED [100%]
====================================================================== FAILURES =======================================================================
_ test _
test example.py::test
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 curl 转代码 解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
____
____
____
____
____
____
____
____
____
____
____
____
____
____
____
____
____
_count ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ _
def test_count():
> assert count_num([1, 2, 3]) != 3
E assert 3 != 3
E + where 3 = count_num([1, 2, 3])
_example.py:11: AssertionError
=============================================================== short test summary info ===============================================================
FAILED test_
_count - assert 3 != 3
================================================================== 1 failed in 0.16s ==================================================================
.代表测试通过,F 代表测试失败;
-v 显示详细的测试信息,-h 显示 pytest 命令详细的帮助信息;
标记
默认情况下,pytest 会在当前目录下寻找以 test_为开头(以_test 结尾)的测试文件,并且执行文件内所有以 test_为开头(以_test 结尾)的所有函数和方法;
指定运行测试用例,可以通过::显示标记(文件名::类名::方法名)(文件名::函数名)
pytest test_example3.py::test_odd
通过 pytest.mark.skip()或者 pytest.mark.skipif()条件表达式,跳过指定的测试用例
import pytest
import random
test_flag = False
@pytest.mark.skip()
def test_odd ():
num = random.randint(0 , 100 )
assert num % 2 == 1
@pytest.mark.skipif(test_flag is False , reason="test_flag is False" )
def test_even ():
num = random.randint(0 , 1000 )
assert num % 2 == 0
通过 pytest.raises()捕获测试用例可能抛出的异常
def test_zero ():
num = 0
with pytest.raises(ZeroDivisionError) as e:
num = 1 /0
exc_msg = e.value.args[0 ]
print (exc_msg)
assert num == 0
预先知道测试用例会失败,但是不想跳过,需要显示提示信息,使用 pytest.mark.xfail()
import random
@pytest.mark.xfail()
def test_sum ():
random_list = [random.randint(0 , 100 ) for x in range (10 )]
num = sum (random_list)
assert num < 20
对测试用例进行多组数据测试,每组参数都能够独立执行一次(可以避免测试用例内部执行单组数据测试不通过后停止测试)
@pytest.mark.parametrize('num,num2' , [(1 ,2 ),(3 ,4 )] )
def test_many_odd (num: int , num2: int ):
assert num % 2 == 1
assert num2 % 2 == 0
固件(Fixture)
固件就是一些预处理的函数,pytest 会在执行测试函数前(或者执行后)加载运行这些固件,常见的应用场景就有数据库的连接和关闭(设备连接和关闭)
import pytest
@pytest.fixture()
def postcode ():
return "hello"
def test_count (postcode ):
assert postcode == "hello"
按照官方的解释就是当运行测试函数,会首先检测运行函数的参数,搜索与参数同名的 fixture,一旦 pytest 找到,就会运行这些固件,获取这些固件的返回值(如果有),并将这些返回值作为参数传递给测试函数;
预处理和后处理 import pytest
@pytest.fixture()
def connect_db ():
print ("Connect Database in ......." )
yield
print ("Close Database out ......." )
def read_database (key: str ):
p_info = {
"name" : "zhangsan" ,
"address" : "China Guangzhou" ,
"age" : 99
}
return p_info[key]
def test_count (connect_db ):
assert read_database("name" ) == "zhangsan"
============================= test session starts =============================
platform win32 -- Python 3.6.8, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 -- <python_path>
cachedir: .pytest_cache
rootdir: <project_path>
plugins: Faker-8.11.0
collecting ... collected 1 item
test_example.py::test_count Connect Database in .......
PASSED [100%] Close Database out .......
============================== 1 passed in 0.07s ==============================
首先从结果上看验证了官方的解释,pytest 执行测试函数前会寻找同名的固件加载运行;
connect_db 固件中有 yield,这里 pytest 默认会判断 yield 关键词之前的代码属于预处理,会在测试前执行,yield 之后的代码则是属于后处理,将在测试后执行;
作用域
从前面大致了解了固件的作用,抽离出一些重复的工作方便复用,同时 pytest 框架中为了更加精细化控制固件,会使用作用域来进行指定固件的使用范围,(比如在这一模块中的测试函数执行一次即可,不需要模块中的函数重复执行)更加具体的例子就是数据库的连接,这一连接的操作可能是耗时的,我只需要在这一模块的测试函数运行一次即可,不需要每次都运行。
而定义固件是,一般通过 scop 参数来声明作用,常用的有:
function: 函数级,每个测试函数都会执行一次固件;
class: 类级别,每个测试类执行一次,所有方法都可以使用;
module: 模块级,每个模块执行一次,模块内函数和方法都可使用;
session: 会话级,一次测试只执行一次,所有被找到的函数和方法都可用。
import pytest
@pytest.fixture(scope="function" )
def func_scope ():
print ("func_scope" )
@pytest.fixture(scope="module" )
def mod_scope ():
print ("mod_scope" )
@pytest.fixture(scope="session" )
def sess_scope ():
print ("session_scope" )
def test_scope (sess_scope, mod_scope, func_scope ):
pass
def test_scope2 (sess_scope, mod_scope, func_scope ):
pass
============================= test session starts =============================
platform win32 -- Python 3.6.8, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 -- <python_path>
cachedir: .pytest_cache
rootdir: <project_path>
plugins: Faker-8.11.0
collecting ... collected 2 items
test_example2.py::test_scope session_scope
mod_scope
func_scope
PASSED [ 50%]
test_example2.py::test_scope2 func_scope
PASSED [100%]
============================== 2 passed in 0.07s ==============================
从这里可以看出 module,session 作用域的固件只执行了一次,可以验证官方的使用介绍
自动执行
有人可能会说,这样子怎么那么麻烦,unittest 框架中直接定义 setUp 就能自动执行预处理,同样的 pytest 框架也有类似的自动执行; pytest 框架中固件一般通过参数 autouse 控制自动运行。
import pytest
@pytest.fixture(scope='session' , autouse=True )
def connect_db ():
print ("Connect Database in ......." )
yield
print ("Close Database out ......." )
def test1 ():
print ("test1" )
def test2 ():
print ("test" )
============================= test session starts =============================
platform win32 -- Python 3.6.8, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 -- <python_path>
cachedir: .pytest_cache
rootdir: <project_path>
plugins: Faker-8.11.0
collecting ... collected 2 items
test_example.py::test1 Connect Database in .......
PASSED [ 50%] test1
test_example.py::test2 PASSED [100%] test
Close Database out .......
============================== 2 passed in 0.07s ==============================
从结果看到,测试函数运行前后自动执行了 connect_db 固件;
参数化
前面简单的提到过了@pytest.mark.parametrize 通过参数化测试,而关于固件传入参数时则需要通过 pytest 框架中内置的固件 request,并且通过 request.param 获取参数
import pytest
@pytest.fixture(params=[
('redis' , '6379' ),
('elasticsearch' , '9200' )
] )
def param (request ):
return request.param
@pytest.fixture(autouse=True )
def db (param ):
print ('\nSucceed to connect %s:%s' % param)
yield
print ('\nSucceed to close %s:%s' % param)
def test_api ():
assert 1 == 1
============================= test session starts =============================
platform win32 -- Python 3.6.8, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 -- <python_path>
cachedir: .pytest_cache
rootdir: <project_path>
plugins: Faker-8.11.0
collecting ... collected 2 items
test_example.py::test_api[param0]
Succeed to connect redis:6379
PASSED [ 50%]
Succeed to close redis:6379
test_example.py::test_api[param1]
Succeed to connect elasticsearch:9200
PASSED [100%]
Succeed to close elasticsearch:9200
============================== 2 passed in 0.07s ==============================
这里模拟连接 redis 和 elasticsearch,加载固件自动执行连接然后执行测试函数再断开连接。
总结
对于开发来说为什么也要学习自动化测试这一块,很重要的一点就是通过自动化测试节省一些重复工作的时间,同时对于优化代码结构,提高代码覆盖率,以及后续项目重构都是有着很重要的意义,同时理解 pytest 和 unittest 在基础上有何区别有助于不同的业务场景中选择适合自己的测试工具。
这篇文章只是简单的介绍了 pytest 的基本使用,有兴趣的可以去看看官方文档,官方文档中还提到了如内置固件的使用,常用测试的场景等等。