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

相关推荐
蓝天扶光14 分钟前
小白学python(第四天)顺序与分支篇
python
立秋678917 分钟前
使用Python绘制极坐标图
开发语言·python
信息科技云课堂29 分钟前
在 Python 中将字典内容保存到 Excel 文件
开发语言·python·excel
文 丰31 分钟前
python基础_类
开发语言·python
用哲学编程33 分钟前
每日一题——Python实现PAT乙级1090 危险品装箱(举一反三+思想解读+逐步优化)4千字好文
开发语言·python·算法·职场和发展
易迟1 小时前
来自工业界的知识库 RAG 服务(五),模块化知识库 GoMate 实现方案详解
人工智能·python·gpt
eclipsercp1 小时前
《每天5分钟用Flask搭建一个管理系统》第9章:API设计
后端·python·flask
东方佑1 小时前
python教程---网络爬虫
开发语言·爬虫·python
爱C++的小笨1 小时前
python工程打包成exe
python·python3.11
天下·第二2 小时前
使用【docker】简单部署打包构建好的镜像并运行python项目
python·docker·eureka