pytest
是一个能够简化成测试系统构建、方便测试规模扩展的框架,它让测试变得更具表现力和可读性–模版代码不再是必需的。只需要几分钟的时间,就可以对你的应用开始一个简单的单元测试或者复杂的功能测试。
1. 安装pytest
pip install -U pytest
检查版本
> pytest --version
pytest 8.2.2
2. 创建第一个测试
注:
- 文件名必须以
test
开头 - 测试类必须以
Test
开头,且不能含有init
方法 - 测试方法必须以
test
开头
# test_sample.py
def func(x):return x + 1def test_answer():assert func(3) == 5
然后在该python文件的目录下执行pytest
================================================= test session starts =================================================
platform win32 -- Python 3.9.9, pytest-7.0.1, pluggy-1.0.0
rootdir: D:\Code\Pytest
collected 1 itemtest_sample.py F [100%]====================================================== FAILURES =======================================================
_____________________________________________________ test_answer _____________________________________________________def test_answer():
> assert func(3) == 5
E assert 4 == 5
E + where 4 = func(3)test_sample.py:5: AssertionError
=============================================== short test summary info ===============================================
FAILED test_sample.py::test_answer - assert 4 == 5
================================================== 1 failed in 0.12s ==================================================
PS D:\Code\Pytest>
[100%]
指的是运行所有测试用例的总体进度。完成后,pytest会显示一个报告,如上述测试,是一个失败报告,因为func(3)
不返回5。
3. 运行多个测试
pytest _*.py
或者pytest *_
可以使 pytest 运行目录下的多个符合条件的文件。
3. pytest测试用例的运行方式
主函数模式
1. 运行所有:pytest.main()
import pytestdef fun_plus(x):return x+2def test_answer():assert fun_plus(2) == 5def test_addition():assert 1+1 == 2if __name__ == '__main__':pytest.main()
执行结果
测试用例执行通过是没有的展示的,但执行失败会有具体的信息,甚至指出出错地方:
F:\SoftWare\Python\Python39\python39.exe F:/SoftWare/JetBrains/PyCharm2023.1.2/plugins/python/helpers/pycharm/_jb_pytest_runner.py --path F:\Code\PyCharmProject\Interview\pytest\test_demo01.py
Testing started at 10:09 ...
Launching pytest with arguments F:\Code\PyCharmProject\Interview\pytest\test_demo01.py --no-header --no-summary -q in F:\Code\PyCharmProject\Interview\pytest============================= test session starts =============================
collecting ... collected 2 itemstest_demo01.py::test_answer FAILED [ 50%]
test_demo01.py:5 (test_answer)
4 != 5Expected :5
Actual :4
<Click to see difference>def test_answer():
> assert fun_plus(2) == 5
E assert 4 == 5
E + where 4 = fun_plus(2)test_demo01.py:7: AssertionErrortest_demo01.py::test_addition PASSED [100%]========================= 1 failed, 1 passed in 0.03s =========================Process finished with exit code 1
2. 指定模块/文件
# test_demo01.py
import pytestdef fun_plus(x):return x+2def test_answer():assert fun_plus(2) == 5def test_addition():assert 1+1 == 2# if __name__ == '__main__':
# pytest.main()
# test_demo02.py
import pytestdef fun_minus(x):return x-2def test_answer():assert fun_minus(2) == 5def test_minus():assert 4-2 == 2# if __name__ == '__main__':
# pytest.main()
# run_suite.py
import pytestif __name__ == '__main__':pytest.main(['-vs', './test_demo01.py', './test_demo02.py'])
参数详解:
-s:表⽰输出调试信息,输出print信息
-v:显⽰更加详细的信息
-vs:两个参数可以同时使⽤注:main函数中的参数要求是列表形式的,如果有多个模块/文件,则继续以逗号分隔
多线程或者分布式运行:
安装插件 pytest-xdist
pip install pytest-xdist
# run_suite.py
import pytestif __name__ == '__main__':pytest.main(['-vs', './', '-n 2'])
# 以两个线程来运行当前目录下的所有测试用例
执行结果:
============================= test session starts =============================
platform win32 -- Python 3.9.9, pytest-8.2.2, pluggy-1.5.0 -- F:\SoftWare\Python\Python39\python39.exe
cachedir: .pytest_cache
rootdir: F:\Code\PyCharmProject\Interview\pytest
plugins: xdist-3.6.1
created: 2/2 workers
2 workers [4 items]scheduling tests via LoadSchedulingtest_demo02.py::test_answer
test_demo01.py::test_answer
[gw0] FAILED test_demo01.py::test_answer
[gw1] FAILED test_demo02.py::test_answer
test_demo02.py::test_minus
[gw1] PASSED test_demo02.py::test_minus
test_demo01.py::test_addition
[gw0] PASSED test_demo01.py::test_addition ================================== FAILURES ===================================
_________________________________ test_answer _________________________________
[gw0] win32 -- Python 3.9.9 F:\SoftWare\Python\Python39\python39.exedef test_answer():
> assert fun_plus(2) == 5
E assert 4 == 5
E + where 4 = fun_plus(2)test_demo01.py:7: AssertionError
_________________________________ test_answer _________________________________
[gw1] win32 -- Python 3.9.9 F:\SoftWare\Python\Python39\python39.exedef test_answer():
> assert fun_minus(2) == 5
E assert 0 == 5
E + where 0 = fun_minus(2)test_demo02.py:7: AssertionError
=========================== short test summary info ===========================
FAILED test_demo01.py::test_answer - assert 4 == 5
FAILED test_demo02.py::test_answer - assert 0 == 5
========================= 2 failed, 2 passed in 0.34s =========================Process finished with exit code 0
reruns 失败用例重跑
安装插件 pytest-rerunfailures
pip install pytest-rerunfailures
–reruns=num:失败用例重跑,将这个模块多执行num次,最后返回结果
# run_suite.py
import pytestif __name__ == '__main__':pytest.main(['-vs', '--reruns=2'])
执行结果:
============================= test session starts =============================
platform win32 -- Python 3.9.9, pytest-8.2.2, pluggy-1.5.0 -- F:\SoftWare\Python\Python39\python39.exe
cachedir: .pytest_cache
rootdir: F:\Code\PyCharmProject\Interview\pytest
plugins: rerunfailures-14.0, xdist-3.6.1
collecting ... collected 4 itemstest_demo01.py::test_answer RERUN
test_demo01.py::test_answer RERUN
test_demo01.py::test_answer FAILED
test_demo01.py::test_addition PASSED
test_demo02.py::test_answer RERUN
test_demo02.py::test_answer RERUN
test_demo02.py::test_answer FAILED
test_demo02.py::test_minus PASSED================================== FAILURES ===================================
_________________________________ test_answer _________________________________def test_answer():
> assert fun_plus(2) == 5
E assert 4 == 5
E + where 4 = fun_plus(2)test_demo01.py:7: AssertionError
_________________________________ test_answer _________________________________def test_answer():
> assert fun_minus(2) == 5
E assert 0 == 5
E + where 0 = fun_minus(2)test_demo02.py:7: AssertionError
=========================== short test summary info ===========================
FAILED test_demo01.py::test_answer - assert 4 == 5
FAILED test_demo02.py::test_answer - assert 0 == 5
==================== 2 failed, 2 passed, 4 rerun in 0.05s =====================Process finished with exit code 0
用例失败,测试停止
-x:只要有一个用例失败,测试就会停止
# run_suite.py
import pytestif __name__ == '__main__':pytest.main(['-vs', '-x'])
执行结果:
============================= test session starts =============================
platform win32 -- Python 3.9.9, pytest-8.2.2, pluggy-1.5.0 -- F:\SoftWare\Python\Python39\python39.exe
cachedir: .pytest_cache
rootdir: F:\Code\PyCharmProject\Interview\pytest
plugins: rerunfailures-14.0, xdist-3.6.1
collecting ... collected 4 itemstest_demo01.py::test_answer FAILED================================== FAILURES ===================================
_________________________________ test_answer _________________________________def test_answer():
> assert fun_plus(2) == 5
E assert 4 == 5
E + where 4 = fun_plus(2)test_demo01.py:7: AssertionError
=========================== short test summary info ===========================
FAILED test_demo01.py::test_answer - assert 4 == 5
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!
============================== 1 failed in 0.04s ==============================Process finished with exit code 0
最大失败测试用例
–maxfail=n:如果有n个用例失败,测试就会停止(前提:安装插件 pytest-xdist)
# run_suite.py
import pytestif __name__ == '__main__':pytest.main(['-vs', '--maxfail=1'])
执行结果:
============================= test session starts =============================
platform win32 -- Python 3.9.9, pytest-8.2.2, pluggy-1.5.0 -- F:\SoftWare\Python\Python39\python39.exe
cachedir: .pytest_cache
rootdir: F:\Code\PyCharmProject\Interview\pytest
plugins: rerunfailures-14.0, xdist-3.6.1
collecting ... collected 4 itemstest_demo01.py::test_answer FAILED================================== FAILURES ===================================
_________________________________ test_answer _________________________________def test_answer():
> assert fun_plus(2) == 5
E assert 4 == 5
E + where 4 = fun_plus(2)test_demo01.py:7: AssertionError
=========================== short test summary info ===========================
FAILED test_demo01.py::test_answer - assert 4 == 5
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!
============================== 1 failed in 0.04s ==============================Process finished with exit code 0
通过读取 pytest.ini 配置文件运行
# pytest.ini[pytest]
# 命令行参数,用空格分隔
addopts = -vs# 测试用例文件夹
testpaths = testcase
# run_suite.py
import pytestif __name__ == '__main__':pytest.main(['--maxfail=1'])
已经将测试用例转移到 testcase 文件夹下,看看执行结果:
============================= test session starts =============================
platform win32 -- Python 3.9.9, pytest-8.2.2, pluggy-1.5.0 -- F:\SoftWare\Python\Python39\python39.exe
cachedir: .pytest_cache
rootdir: F:\Code\PyCharmProject\Interview\pytest
configfile: pytest.ini
testpaths: testcase
plugins: rerunfailures-14.0, xdist-3.6.1
collecting ... collected 4 itemstestcase/test_demo01.py::test_answer FAILED================================== FAILURES ===================================
_________________________________ test_answer _________________________________def test_answer():
> assert fun_plus(2) == 5
E assert 4 == 5
E + where 4 = fun_plus(2)testcase\test_demo01.py:7: AssertionError
=========================== short test summary info ===========================
FAILED testcase/test_demo01.py::test_answer - assert 4 == 5
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!
============================== 1 failed in 0.04s ==============================Process finished with exit code 0
更多的配置还有:
# 配置测试搜索的模块文件名称
python_files = test_*.py# 配置测试搜索的测试类名
python_classes = Test*# 配置测试搜索的函数名
python_functions = test*
4. pytest执行测试用例顺序
unittest默认的执行顺序:
按照ASCII大小执行的,即在写测试用例时将测试名按大小顺序或加上数字顺序大小即可使其按顺序执行。
pytest默认的执行顺序:
按照用例的先后顺序执行,即哪个用例写在前面就先执行哪个用例
当然我们也可以通过插件的方式自定义其执行顺序
安装 pytest-ordering
插件
pip install pytest-ordering
使用方法
@pytest.mark.run(order=1)
import pytestdef fun_plus(x):return x+2def test_01_answer():assert fun_plus(2) == 5@pytest.mark.run(order=1)
def test_02_addition():assert 1+1 == 2if __name__ == '__main__':pytest.main()
5. 以安静报告模式执行测试用例
pytest -q test_sysexit.py
注:-q与--quiet
的效果一样。
6. 分组执行测试用例
一旦开发了多个测试,你可能需要对测试进行分组,我们可以利用配置文件中的配置为测试进行分组执行:
@pytest.mark.组名
# pytest.ini
[pytest]
# 命令行参数,用空格分隔
addopts = -vs# 测试用例文件夹
testpaths = ./testcase# 分组
markers =smoke:冒烟用例group1:第一组测试用例
# test_demo02.py
import pytestdef fun_minus(x):return x-2def test_answer():assert fun_minus(2) == 5@pytest.mark.smoke
def test_minus():assert 4-2 == 2if __name__ == '__main__':pytest.main(['./test_demo02.py', '-m', 'smoke'])
测试结果:
============================= test session starts =============================
platform win32 -- Python 3.9.9, pytest-8.2.2, pluggy-1.5.0 -- F:\SoftWare\Python\Python39\python39.exe
cachedir: .pytest_cache
rootdir: F:\Code\PyCharmProject\Interview\pytest
configfile: pytest.ini
plugins: ordering-0.6, rerunfailures-14.0, xdist-3.6.1
collecting ... collected 2 items / 1 deselected / 1 selectedtest_demo02.py::test_minus PASSED======================= 1 passed, 1 deselected in 0.02s =======================Process finished with exit code 0
记一个PyCharm中可能遇到的问题
在单个test_xxx.py文件中,在main中传入了参数,但不管怎么运行都没有生效,在cmd窗口又是正常生效的,甚至在run_suite.py中的main也是正常的。这是因为程序自动识别到了pytest框架,默认以pytest运行,要main主函数运行。
解决方法:
1、修改Python解释器(但需要对每个test文件都这样,很麻烦)
Edit Configurations,新增解释器,将目标文件作为单独的python文件运行
2、修改Python integrated Tools(可以一劳永逸)
进入到File->Settings->Tools->Python integrated Tools页面,找到 Testing - Default test runner,将其修改为Unittest