Pytest--安装与入门

pytest是一个能够简化成测试系统构建、方便测试规模扩展的框架,它让测试变得更具表现力和可读性--模版代码不再是必需的。只需要几分钟的时间,就可以对你的应用开始一个简单的单元测试或者复杂的功能测试。

1. 安装pytest

python 复制代码
pip install -U pytest

检查版本

python 复制代码
> pytest --version
pytest 8.2.2

2. 创建第一个测试

注:

  • 文件名必须以 test 开头
  • 测试类必须以 Test 开头,且不能含有 init 方法
  • 测试方法必须以 test 开头
python 复制代码
# test_sample.py
def func(x):
    return x + 1

def test_answer():
    assert func(3) == 5

然后在该python文件的目录下执行pytest

python 复制代码
================================================= test session starts =================================================
platform win32 -- Python 3.9.9, pytest-7.0.1, pluggy-1.0.0
rootdir: D:\Code\Pytest
collected 1 item

test_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()
python 复制代码
import pytest

def fun_plus(x):
    return x+2

def test_answer():
    assert fun_plus(2) == 5

def test_addition():
    assert 1+1 == 2


if __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 items

test_demo01.py::test_answer FAILED                                       [ 50%]
test_demo01.py:5 (test_answer)
4 != 5

Expected :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: AssertionError

test_demo01.py::test_addition PASSED                                     [100%]

========================= 1 failed, 1 passed in 0.03s =========================

Process finished with exit code 1
2. 指定模块/文件
python 复制代码
# test_demo01.py
import pytest

def fun_plus(x):
    return x+2

def test_answer():
    assert fun_plus(2) == 5

def test_addition():
    assert 1+1 == 2


# if __name__ == '__main__':
#     pytest.main()
python 复制代码
# test_demo02.py
import pytest

def fun_minus(x):
    return x-2

def test_answer():
    assert fun_minus(2) == 5

def test_minus():
    assert 4-2 == 2


# if __name__ == '__main__':
#     pytest.main()
python 复制代码
# run_suite.py
import pytest

if __name__ == '__main__':
    pytest.main(['-vs', './test_demo01.py', './test_demo02.py'])
参数详解:

-s:表⽰输出调试信息,输出print信息

-v:显⽰更加详细的信息

-vs:两个参数可以同时使⽤

注:main函数中的参数要求是列表形式的,如果有多个模块/文件,则继续以逗号分隔

多线程或者分布式运行:

安装插件 pytest-xdist

复制代码
pip install pytest-xdist
python 复制代码
# run_suite.py
import pytest

if __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 LoadScheduling

test_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.exe

    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 _________________________________
[gw1] win32 -- Python 3.9.9 F:\SoftWare\Python\Python39\python39.exe

    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 in 0.34s =========================

Process finished with exit code 0
reruns 失败用例重跑

安装插件 pytest-rerunfailures

复制代码
pip install pytest-rerunfailures

--reruns=num:失败用例重跑,将这个模块多执行num次,最后返回结果

python 复制代码
# run_suite.py
import pytest

if __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 items

test_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:只要有一个用例失败,测试就会停止

python 复制代码
# run_suite.py
import pytest

if __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 items

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)

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)

python 复制代码
# run_suite.py
import pytest

if __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 items

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)

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 配置文件运行
ini 复制代码
# pytest.ini

[pytest]
# 命令行参数,用空格分隔
addopts = -vs

# 测试用例文件夹
testpaths = testcase
python 复制代码
# run_suite.py
import pytest

if __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 items

testcase/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

更多的配置还有:

ini 复制代码
# 配置测试搜索的模块文件名称
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)

python 复制代码
import pytest

def fun_plus(x):
    return x+2

def test_01_answer():
    assert fun_plus(2) == 5

@pytest.mark.run(order=1)
def test_02_addition():
    assert 1+1 == 2


if __name__ == '__main__':
    pytest.main()

5. 以安静报告模式执行测试用例

python 复制代码
pytest -q test_sysexit.py

注: -q与--quiet的效果一样。

6. 分组执行测试用例

一旦开发了多个测试,你可能需要对测试进行分组,我们可以利用配置文件中的配置为测试进行分组执行:

@pytest.mark.组名

ini 复制代码
# pytest.ini
[pytest]
# 命令行参数,用空格分隔
addopts = -vs

# 测试用例文件夹
testpaths = ./testcase

# 分组
markers =
    smoke:冒烟用例
    group1:第一组测试用例
python 复制代码
# test_demo02.py
import pytest

def fun_minus(x):
    return x-2


def test_answer():
    assert fun_minus(2) == 5

@pytest.mark.smoke
def test_minus():
    assert 4-2 == 2


if __name__ == '__main__':
    pytest.main(['./test_demo02.py', '-m', 'smoke'])

测试结果:

shell 复制代码
============================= 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 selected

test_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

相关推荐
Draina19 分钟前
CBC填充预言攻击-CBC Padding Oracle Crypto Attack
python·安全·web安全·网络安全·密码学·安全性测试
Angel Q.1 小时前
特征提取 | DINO 到 DINOv3
图像处理·python
不如语冰1 小时前
AI大模型入门-pytorch-张量2 tensor创建
python
NPE~1 小时前
[AI]Agent开发——ADK框架使用
人工智能·python·ai·教程·adk·agent开发
矮个史蒂芬1 小时前
统计verilog .v文件中output 的bit数
python
gwf2162 小时前
磨损均衡算法(Wear Leveling)——SSD如何让每块闪存“公平退休“?
运维·数据库·人工智能·python·嵌入式硬件·算法·智能硬件
爱吃提升2 小时前
python 分布式爬虫、爬虫合规与综合实战项目
分布式·爬虫·python
审小匠OpenCPAi2 小时前
银行流水核查怎么自动化?单边匹配、双向勾稽与图聚类异常检测的工程对比
java·前端·人工智能·python·审计
元Y亨H3 小时前
Pandas 解析 Excel 导致的内存溢出(MemoryError)
python·excel
金銀銅鐵4 小时前
[Python] 用 turtle 来绘制瑞典国旗
python