1.安装(-U是upgrade,表示已经安装就升级为最新版本)
pip install -U pytest
2.查看版本
pytest --version
3.项目结构
common存放公共方法封装
config存放配置文件
log存放日志文件
params接口文档
report测试报告
testcases测试用例
4.pytest测试用例编写规范
①测试文件以test_开头(以_test结尾也可以)
②测试类以Test开头,且不能带有init方法
③测试函数以test_开头
④断言必须使用assert
5.测试用例代码
Python中创建一个test_case01.py文件,存放在cese目录下
class Test_demo1:
def test_a1(self):
print('\n这是用例a1')
assert 1 == 1
def test_a2(self):
print('\n这是用例a2')
assert 2 == 2
def test_a3(self):
print('\n这是用例a3')
assert 3 == 2
class Test_demo2:
def test_b1(self):
print('\n这是用例b1')
assert 11 == 1
def test_b2(self):
print('\n这是用例b2')
assert 22 == 22
6.运行测试
直接打开cmd命令行窗口,进入到case目录下,执行命令pytest即可,结果如下:
PS D:\pythonProject\case> pytest
======================================================= test session starts ========================================================
platform win32 -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\pythonProject\case
plugins: html-3.1.1, metadata-1.11.0
collected 5 items
test_case01.py ..FF. [100%]
============================================================= FAILURES =============================================================
________________________________________________________ Test_demo1.test_a3 ________________________________________________________
self = <test_case01.Test_demo1 object at 0x0000024FDCC55EA0>
def test_a3(self):
print('\n这是用例a3')
> assert 3 == 2
E assert 3 == 2
test_case01.py:10: AssertionError
------------------------------------------------------- Captured stdout call -------------------------------------------------------
这是用例a3
________________________________________________________ Test_demo2.test_b1 ________________________________________________________
self = <test_case01.Test_demo2 object at 0x0000024FDCC55990>
def test_b1(self):
print('\n这是用例b1')
> assert 11 == 1
E assert 11 == 1
test_case01.py:14: AssertionError
------------------------------------------------------- Captured stdout call -------------------------------------------------------
这是用例b1
===================================================== short test summary info ======================================================
FAILED test_case01.py::Test_demo1::test_a3 - assert 3 == 2
FAILED test_case01.py::Test_demo2::test_b1 - assert 11 == 1
=================================================== 2 failed, 3 passed in 0.20s ====================================================
PS D:\pythonProject\case>
或者指定模块里面的类执行,模块名和类名之间用::隔开
PS D:\pythonProject> pytest case\test_case01.py::Test_demo2
======================================================= test session starts ========================================================
platform win32 -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\pythonProject
plugins: html-3.1.1, metadata-1.11.0
collected 2 items
case\test_case01.py F. [100%]
============================================================= FAILURES =============================================================
________________________________________________________ Test_demo2.test_b1 ________________________________________________________
self = <test_case01.Test_demo2 object at 0x0000016578483EB0>
def test_b1(self):
print('\n这是用例b1')
> assert 11 == 1
E assert 11 == 1
case\test_case01.py:14: AssertionError
------------------------------------------------------- Captured stdout call -------------------------------------------------------
这是用例b1
===================================================== short test summary info ======================================================
FAILED case/test_case01.py::Test_demo2::test_b1 - assert 11 == 1
=================================================== 1 failed, 1 passed in 0.13s ====================================================
PS D:\pythonProject>
或者指定某个模块里的某个类里的某个用例执行
PS D:\pythonProject> pytest case\test_case01.py::Test_demo2::test_b2 -s
================================================= test session starts =================================================
platform win32 -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\pythonProject\case, configfile: pytest.ini
plugins: forked-1.4.0, html-3.1.1, metadata-1.11.0, ordering-0.6, xdist-2.5.0
collected 1 item
case\test_case01.py
这是用例b2
.
================================================== 1 passed in 0.03s ==================================================
PS D:\pythonProject>
7.初始化清除
①setup_module/teardown_module 模块级别
在模块始末调用,为该模块中所有的测试用例做公共的初始化和清除工作
def setup_module():
print('\n *** 模块初始化 ***')
def teardown_module():
print('\n *** 模块清除 ***')
class Test_demo1:
def test_a1(self):
print('\n这是用例a1')
assert 1 == 1
def test_a2(self):
print('\n这是用例a2')
assert 3 == 2
class Test_demo2:
def test_b1(self):
print('\n这是用例b1')
assert 1 == 2
def test_b2(self):
print('\n这是用例b2')
assert 4==4
运行结果:
PS D:\pythonProject\case> pytest -s
======================================================= test session starts ========================================================
platform win32 -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\pythonProject\case
plugins: html-3.1.1, metadata-1.11.0
collected 4 items
test_case01.py
*** 模块初始化 ***
这是用例a1
.
这是用例a2
F
这是用例b1
F
这是用例b2
.
*** 模块清除 ***
============================================================= FAILURES
=============================================================
......
②setup_function/teardown_function 函数级别
在函数始末调用,在类的外部执行
def setup_function():
print('\n *** 函数级别初始化 ***')
def teardown_function():
print('\n *** 函数级别清除 ***')
def test_b1():
print('\n这是用例b1')
assert 1 == 2
def test_b2():
print('\n这是用例b2')
assert 4==4
class Test_demo1:
def test_a1(self):
print('\n这是用例a1')
assert 1 == 1
def test_a2(self):
print('\n这是用例a2')
assert 3 == 2
运行结果:
PS D:\pythonProject\case> pytest -s
======================================================= test session starts ========================================================
platform win32 -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\pythonProject\case
plugins: html-3.1.1, metadata-1.11.0
collected 4 items
test_case01.py
*** 函数级别初始化 ***
这是用例b1
F
*** 函数级别清除 ***
*** 函数级别初始化 ***
这是用例b2
.
*** 函数级别清除 ***
这是用例a1
.
这是用例a2
F
============================================================= FAILURES
=============================================================
......
③setup_class/teardown_class 类级别
在每个类里面执行前后分别执行
class Test_demo1:
def setup_class(self):
print('\n *** 类级别初始化 ***')
def teardown_class(self):
print('\n *** 类级别清除 ***')
def test_a1(self):
print('\n这是用例a1')
assert 1 == 1
def test_a2(self):
print('\n这是用例a2')
assert 3 == 2
class Test_demo2:
def test_b1(self):
print('\n这是用例b1')
assert 1 == 2
def test_b2(self):
print('\n这是用例b2')
assert 4==4
运行结果:
PS D:\pythonProject\case> pytest -s
======================================================= test session starts ========================================================
platform win32 -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\pythonProject\case
plugins: html-3.1.1, metadata-1.11.0
collected 4 items
test_case01.py
*** 类级别初始化 ***
这是用例a1
.
这是用例a2
F
*** 类级别清除 ***
这是用例b1
F
这是用例b2
.
============================================================= FAILURES =============================================================
......
④setup_method/teardown_method 方法级别
在方法始末调用,在类的内部执行,对类的每个测试方法都要做初始化和清除工作
class Test_demo1:
def setup_method(self):
print('\n *** 方法别初始化 ***')
def teardown_method(self):
print('\n *** 方法级别清除 ***')
def test_a1(self):
print('\n这是用例a1')
assert 1 == 1
def test_a2(self):
print('\n这是用例a2')
assert 3 == 2
class Test_demo2:
def test_b1(self):
print('\n这是用例b1')
assert 1 == 2
def test_b2(self):
print('\n这是用例b2')
assert 4==4
运行结果:
PS D:\pythonProject\case> pytest -s
======================================================= test session starts ========================================================
platform win32 -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\pythonProject\case
plugins: html-3.1.1, metadata-1.11.0
collected 4 items
test_case01.py
*** 方法别初始化 ***
这是用例a1
.
*** 方法级别清除 ***
*** 方法别初始化 ***
这是用例a2
F
*** 方法级别清除 ***
这是用例b1
F
这是用例b2
.
============================================================= FAILURES =============================================================
.........
注:还可以通过装饰器@pytest.fixture()定义用例的前置和后置操作 (具体的可以先上网查一下,我还在研究~~~后续补充)
8.运行参数
①无参数时,读取路径下符合条件的所有类、函数、方法全部执行
pytest
②-v 显示详细运行信息,包括每个测试类、测试函数的名字
pytest -v
PS D:\pythonProject\case> pytest -v
================================================= test session starts =================================================
platform win32 -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 -- D:\python\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.10.0', 'Platform': 'Windows-10-10.0.19042-SP0', 'Packages': {'pytest': '6.2.5', 'py': '1.10.0', 'pluggy': '1.0.0'}, 'Plugins': {'html': '3.1.1', 'metadata': '1.11.0'}, 'JAVA_HOME': 'D:\\Java\\jdk1.8.0_73'}
rootdir: D:\pythonProject\case
plugins: html-3.1.1, metadata-1.11.0
collected 3 items
test_case01.py::Test_demo1::test_a1 PASSED [ 33%]
test_case01.py::Test_demo1::test_a2 PASSED [ 66%]
test_case01.py::Test_demo1::test_a3 FAILED [100%]
====================================================== FAILURES =======================================================
_________________________________________________ Test_demo1.test_a3 __________________________________________________
self = <test_case01.Test_demo1 object at 0x000001CA6D0792D0>
def test_a3(self):
print('\n这是用例a3')
> assert 3 == 2
E assert 3 == 2
E +3
E -2
test_case01.py:10: AssertionError
------------------------------------------------ Captured stdout call -------------------------------------------------
这是用例a3
=============================================== short test summary info ===============================================
FAILED test_case01.py::Test_demo1::test_a3 - assert 3 == 2
============================================= 1 failed, 2 passed in 0.17s =============================================
PS D:\pythonProject\case>
③-s 显示代码中print的内容
pytest -s
PS D:\pythonProject\case> pytest -s
======================================================= test session starts ========================================================
platform win32 -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\pythonProject\case
plugins: html-3.1.1, metadata-1.11.0
collected 3 items
test_case01.py
这是用例a1
.
这是用例a2
.
这是用例a3
F
============================================================= FAILURES =============================================================
________________________________________________________ Test_demo1.test_a3 ________________________________________________________
self = <test_case01.Test_demo1 object at 0x000001A5C901CCD0>
def test_a3(self):
print('\n这是用例a3')
> assert 3 == 2
E assert 3 == 2
test_case01.py:10: AssertionError
===================================================== short test summary info ======================================================
FAILED test_case01.py::Test_demo1::test_a3 - assert 3 == 2
=================================================== 1 failed, 2 passed in 0.14s ====================================================
PS D:\pythonProject\case>
④-k "名字" 挑选匹配"名字"的用例执行
-k后面的名字可以是测试函数的名字、类的名字、模块文件的名字
pytest -k test_case01 #执行名为test_case01的用例
pytest -k "not a1" #执行所有用例,但不包括有a1的用例
pytest -k "a1 and b1" #执行包含a1和b1关键字的用例
pytest -k "a1 or b1" #执行包括a1或者包括b1关键字的用例执行
⑤-x 遇到执行失败的用例,立即停止运行
pytest -x
PS D:\pythonProject\case> pytest -x
======================================================= test session starts ========================================================
platform win32 -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\pythonProject\case
plugins: html-3.1.1, metadata-1.11.0
collected 4 items
test_case01.py F
============================================================= FAILURES =============================================================
________________________________________________________ Test_demo1.test_a1 ________________________________________________________
self = <test_case01.Test_demo1 object at 0x000001C810C9B010>
def test_a1(self):
print('\n这是用例a1')
> assert 1 == 2
E assert 1 == 2
test_case01.py:4: AssertionError
------------------------------------------------------- Captured stdout call -------------------------------------------------------
这是用例a1
===================================================== short test summary info ======================================================
FAILED test_case01.py::Test_demo1::test_a1 - assert 1 == 2
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
======================================================== 1 failed in 0.18s =========================================================
PS D:\pythonProject\case>
⑥--maxfail=num 用例失败总数等于num 时停止运行
pytest --maxfail=1
PS D:\pythonProject\case> pytest --maxfail=1
================================================= test session starts =================================================
platform win32 -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\pythonProject\case
plugins: html-3.1.1, metadata-1.11.0
collected 5 items
test_case01.py .F
====================================================== FAILURES =======================================================
_________________________________________________ Test_demo1.test_a2 __________________________________________________
self = <test_case01.Test_demo1 object at 0x000002C9016AA020>
def test_a2(self):
print('\n这是用例a2')
> assert 2 == 3
E assert 2 == 3
test_case01.py:7: AssertionError
------------------------------------------------ Captured stdout call -------------------------------------------------
这是用例a2
=============================================== short test summary info ===============================================
FAILED test_case01.py::Test_demo1::test_a2 - assert 2 == 3
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
============================================= 1 failed, 1 passed in 0.14s =============================================
PS D:\pythonProject\case>
⑦-m 标记名 运行含有@pytest.mark.[标记名] 标记的用例
标签可以加在方法上也可以加在类上;也可以同时添加多个标签
pytest -m <标签名> -s
import pytest
class Test_demo1:
@pytest.mark.qiafanfan #函数上加标签
def test_a1(self):
print('\n这是用例a1')
assert 1 == 1
def test_a2(self):
print('\n这是用例a2')
assert 2 == 3
@pytest.mark.恰饭饭 #类上加标签
class Test_demo2:
def test_b1(self):
print('\n这是用例b1')
assert 11 == 1
def test_b2(self):
print('\n这是用例b2')
assert 22 == 22
但是执行测试用例,后面会有一个警告warnings summary
PS D:\pythonProject\case> pytest -m qiafanfan -s
================================================= test session starts =================================================
platform win32 -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\pythonProject\case, configfile: pytest.ini
plugins: html-3.1.1, metadata-1.11.0
collected 4 items / 3 deselected / 1 selected
test_case01.py
这是用例a1
.
================================================== warnings summary ===================================================
test_case01.py:3
D:\pythonProject\case\test_case01.py:3: PytestUnknownMarkWarning: Unknown pytest.mark.qiafanfan - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/mark.html
@pytest.mark.qiafanfan
-- Docs: https://docs.pytest.org/en/stable/warnings.html
===================================== 1 passed, 3 deselected, 1 warning in 0.04s ======================================
PS D:\pythonProject\case>
解决warnings summary的方法:
在目录下创建一个pytest.ini的配置文件,添加以下内容
[pytest]
markers=qiafanfan:函数上的标签
恰饭饭: 类上的标签
注:配置文件里多个标签要换行,一行一个标签且第二行开始的标签要缩进,标签后面是备注
然后再运行就不会有警告了,结果如下:
PS D:\pythonProject\case> pytest -m qiafanfan -s
================================================= test session starts =================================================
platform win32 -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\pythonProject\case, configfile: pytest.ini
plugins: html-3.1.1, metadata-1.11.0
collected 4 items / 3 deselected / 1 selected
test_case01.py
这是用例a1
.
=========================================== 1 passed, 3 deselected in 0.04s ===========================================
PS D:\pythonProject\case>
关于pytest还有很多其他运行参数,可以通过pytest --help获取全部运行参数选项
9.pytest常用插件
①生成报告插件 ——pytest-html
安装插件:
pip install pytest-html
运行:
pytest --html=report.html
注:生成报告后一般会通过邮件发送测试报告给相关人员,附上发邮件的代码~~
使用smtp发邮件的源码如下:
更多关于smtp发邮件的举例操作可以参考:Python SMTP发送邮件 | 菜鸟教程
# 邮件的构成部分:发送人 收件人 标题 正文 附件
import smtplib # 用来发邮件
from email.mime.text import MIMEText
from email.header import Header
from email.mime.multipart import MIMEMultipart #发送带有附件的邮件时导入模块MIMEMultipart
def sendemail(filename):
# 先写头部
sender = 'xxxxx@qq.com'#发件人邮箱
receviers = ['xxxxx@qq.com']#收件邮箱,多个人收用逗号隔开
# 写纯文本的邮件
# message = MIMEText('python自动化测试','plain','utf-8')
# 创建带附件的邮件
message = MIMEMultipart()
message['from'] = Header('pytest测试报告', 'utf-8')
message['To'] = Header('xxxxx', 'utf-8')#收件人邮箱
# 邮件的正文内容
message.attach(MIMEText('pytest测试报告','plain','utf-8'))#主题
# 添加附件,取出附件内容
fujian = MIMEText(open(filename, 'r').read(),'base64','utf-8')
# 设置附件在收件方的显示
fujian['Content-Type']='application/octet-stream'
fujian['Content-Disposition'] ='attachment;filename="xxxx.html"'#附件名字,不能写中文
message.attach(fujian)
subject = '某某某的测试报告' #邮件标题
message['Subject'] = Header(subject,'utf-8')
try:
smtp = smtplib.SMTP('smtp.qq.com') # 邮件服务器是谁,如果要用其他邮箱,需要对应的查邮箱服务器
smtp.login('xxxx@qq.com','******')# *表示授权码,在设置--账户里面可以看到
smtp.sendmail(sender,receviers,message.as_string())
print('邮件发送成功')
except Exception:
print('邮件发送失败')
或者使用更简单的yagmail发邮件,源码如下:
安装yagmail插件:
pip install yagmail
import yagmail
#链接邮箱服务器,host表示SMTP服务器域名
yag = yagmail.SMTP(user = 'xxxx@qq.com',password = '*******',host = 'smtp.qq.com')
subject = "测试报告" #邮件标题
contents = '你好呀' #邮件正文
file_path = '../testcases/xxxx.html' #附件路径
yag.send("xxxxxx@qq.com",subject,contents,file_path)
yag.close()
print("发送成功")
② 设置用例的执行顺序——pytest-ordering
安装插件:
pip installl pytest-ordering
例:不加排序的case和运行结果如下(根据case由上至下的顺序执行):
class Test_demo01:
def test_a1(self):
print('\n这是用例a1')
def test_a2(self):
print('\n这是用例a2')
def test_a3(self):
print('\n这是用例a3')
PS D:\pythonProject\case> pytest test_case02.py -v
================================================= test session starts =================================================
platform win32 -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 -- D:\python\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.10.0', 'Platform': 'Windows-10-10.0.19042-SP0', 'Packages': {'pytest': '6.2.5', 'py': '1.10.0', 'pluggy': '1.0.0'}, 'Plugins': {'html': '3.1.1', 'metadata': '1.11.0'}, 'JAVA_HOME': 'D:\\Java\\jdk1.8.0_73'}
rootdir: D:\pythonProject\case, configfile: pytest.ini
plugins: html-3.1.1, metadata-1.11.0
collected 3 items
test_case02.py::Test_demo01::test_a1 PASSED [ 33%]
test_case02.py::Test_demo01::test_a2 PASSED [ 66%]
test_case02.py::Test_demo01::test_a3 PASSED [100%]
================================================== 3 passed in 0.05s ==================================================
PS D:\pythonProject\case>
加上排序参数后的case和运行结果如下(按照order的排序执行):
import pytest
class Test_demo01:
@pytest.mark.run(order=3) #设置该用例第三个执行
def test_a1(self):
print('\n这是用例a1')
@pytest.mark.run(order=1) #设置该用例第一个执行
def test_a2(self):
print('\n这是用例a2')
@pytest.mark.run(order=2) #设置该用例第二个执行
def test_a3(self):
print('\n这是用例a3')
PS D:\pythonProject\case> pytest test_case02.py -v
================================================= test session starts =================================================
platform win32 -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 -- D:\python\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.10.0', 'Platform': 'Windows-10-10.0.19042-SP0', 'Packages': {'pytest': '6.2.5', 'py': '1.10.0', 'pluggy': '1.0.0'}, 'Plugins': {'html': '3.1.1', 'metadata': '1.11.0', 'ordering': '0.6'}, 'JAVA_HOME': 'D:\\Java\\jdk1.8.0_73'}
rootdir: D:\pythonProject\case, configfile: pytest.ini
plugins: html-3.1.1, metadata-1.11.0, ordering-0.6
collected 3 items
test_case02.py::Test_demo01::test_a2 PASSED [ 33%]
test_case02.py::Test_demo01::test_a3 PASSED [ 66%]
test_case02.py::Test_demo01::test_a1 PASSED [100%]
================================================== 3 passed in 0.04s ==================================================
PS D:\pythonProject\case>
注:也可以通过添加装饰器@pytest.mark.latest用例最后一个执行,@pytest.mark.first用例第一个执行 (同样最好在上述提到的pytest.ini文件里面加上latest和first,否则运行结果可能会出现warnings summary(不过这影响也不大,可以忽略滴~~~))
import pytest
class Test_demo01:
@pytest.mark.latest
def test_a1(self):
print('\n这是用例a1')
@pytest.mark.first
def test_a2(self):
print('\n这是用例a2')
PS D:\pythonProject\case> pytest test_case02.py -v
================================================= test session starts =================================================
platform win32 -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 -- D:\python\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.10.0', 'Platform': 'Windows-10-10.0.19042-SP0', 'Packages': {'pytest': '6.2.5', 'py': '1.10.0', 'pluggy': '1.0.0'}, 'Plugins': {'forked': '1.4.0', 'html': '3.1.1', 'metadata': '1.11.0', 'ordering': '0.6', 'xdist': '2.5.0'}, 'JAVA_HOME': 'D:\\Java\\jdk1.8.0_73'}
rootdir: D:\pythonProject\case, configfile: pytest.ini
plugins: forked-1.4.0, html-3.1.1, metadata-1.11.0, ordering-0.6, xdist-2.5.0
collected 2 items
test_case02.py::Test_demo01::test_a2 PASSED [ 50%]
test_case02.py::Test_demo01::test_a1 PASSED [100%]
================================================== 2 passed in 0.05s ==================================================
PS D:\pythonProject\case>
③分布式执行测试用例——pytest-xdist
有1个测试人员有1000条case要执行,每个case一分钟,就需要1000分钟才可以执行完,太费时间了,但是如果有20个测试人员一起同时执行这1000条case,就只需要50分钟,这是分布式的执行测试用例
安装插件:
pip install pytest-xdist
运行方式:
pytest <py文件名称> -n [num] -v #-n [num]表示进程数num个,可以指定数量num,也可以设为auto,自动分配进程
class Test_demo01:
def test_a1(self):
print('\n这是用例a1')
def test_a2(self):
print('\n这是用例a2')
def test_a3(self):
print('\n这是用例a3')
def test_a4(self):
print('\n这是用例a4')
def test_a5(self):
print('\n这是用例a5')
def test_a6(self):
print('\n这是用例a6')
PS D:\pythonProject\case> pytest test_case03.py -n 3 -v
================================================= test session starts =================================================
platform win32 -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 -- D:\python\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.10.0', 'Platform': 'Windows-10-10.0.19042-SP0', 'Packages': {'pytest': '6.2.5', 'py': '1.10.0', 'pluggy': '1.0.0'}, 'Plugins': {'forked': '1.4.0', 'html': '3.1.1', 'metadata': '1.11.0', 'ordering': '0.6', 'xdist': '2.5.0'}, 'JAVA_HOME': 'D:\\Java\\jdk1.8.0_73'}
rootdir: D:\pythonProject\case, configfile: pytest.ini
plugins: forked-1.4.0, html-3.1.1, metadata-1.11.0, ordering-0.6, xdist-2.5.0
[gw0] win32 Python 3.10.0 cwd: D:\pythonProject\case
[gw1] win32 Python 3.10.0 cwd: D:\pythonProject\case
[gw2] win32 Python 3.10.0 cwd: D:\pythonProject\case
[gw0] Python 3.10.0 (tags/v3.10.0:b494f59, Oct 4 2021, 19:00:18) [MSC v.1929 64 bit (AMD64)]
[gw1] Python 3.10.0 (tags/v3.10.0:b494f59, Oct 4 2021, 19:00:18) [MSC v.1929 64 bit (AMD64)]
[gw2] Python 3.10.0 (tags/v3.10.0:b494f59, Oct 4 2021, 19:00:18) [MSC v.1929 64 bit (AMD64)]
gw0 [6] / gw1 [6] / gw2 [6]
scheduling tests via LoadScheduling
test_case03.py::Test_demo01::test_a2
test_case03.py::Test_demo01::test_a3
test_case03.py::Test_demo01::test_a1
[gw2] [ 16%] PASSED test_case03.py::Test_demo01::test_a3
[gw1] [ 33%] PASSED test_case03.py::Test_demo01::test_a2
[gw0] [ 50%] PASSED test_case03.py::Test_demo01::test_a1
test_case03.py::Test_demo01::test_a5
test_case03.py::Test_demo01::test_a4
test_case03.py::Test_demo01::test_a6
[gw1] [ 66%] PASSED test_case03.py::Test_demo01::test_a5
[gw0] [ 83%] PASSED test_case03.py::Test_demo01::test_a4
[gw2] [100%] PASSED test_case03.py::Test_demo01::test_a6
================================================== 6 passed in 1.58s ==================================================
PS D:\pythonProject\case>
注:以上-n 3表示总共6个case,分配给3个人员测,一人2个case
④测试用例运行失败重新跑——pytest-rerunfailures
有时候跑自动化时会出现503错误或者网络延迟导致case失败,这时我们可以通过rerun重新运行该case
安装插件:
pip install pytest-rerunfailures
运行方式:
pytest <py文件名称> --reruns [num1] --reruns-delay [num2] -sv #reruns后的num1表示重新运行的次数,reruns-delay后的num2表示每次重试的间隔时间
class Test_demo1:
def test_a1(self):
print('\n这是用例a1')
assert 1 == 1
def test_a2(self):
print('\n这是用例a2')
assert 2 == 3
def test_a3(self):
print('\n这是用例a3')
assert 3 == 3
PS D:\pythonProject\case> pytest test_case01.py --reruns 3 --reruns-delay 1 -sv
================================================= test session starts =================================================
platform win32 -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 -- D:\python\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.10.0', 'Platform': 'Windows-10-10.0.19042-SP0', 'Packages': {'pytest': '6.2.5', 'py': '1.10.0', 'pluggy': '1.0.0'}, 'Plugins': {'forked': '1.4.0', 'html': '3.1.1', 'metadata': '1.11.0', 'ordering': '0.6', 'rerunfailures': '10.2', 'xdist': '2.5.0'}, 'JAVA_HOME': 'D:\\Java\\jdk1.8.0_73'}
rootdir: D:\pythonProject\case, configfile: pytest.ini
plugins: forked-1.4.0, html-3.1.1, metadata-1.11.0, ordering-0.6, rerunfailures-10.2, xdist-2.5.0
collected 3 items
test_case01.py::Test_demo1::test_a1
这是用例a1
PASSED
test_case01.py::Test_demo1::test_a2
这是用例a2
RERUN
这是用例a2
RERUN
test_case01.py::Test_demo1::test_a2
这是用例a2
RERUN
test_case01.py::Test_demo1::test_a2
这是用例a2
FAILED
test_case01.py::Test_demo1::test_a3
这是用例a3
PASSED
====================================================== FAILURES =======================================================
_________________________________________________ Test_demo1.test_a2 __________________________________________________
self = <test_case01.Test_demo1 object at 0x0000018784DB6170>
def test_a2(self):
print('\n这是用例a2')
> assert 2 == 3
E assert 2 == 3
E +2
E -3
test_case01.py:24: AssertionError
=============================================== short test summary info ===============================================
FAILED test_case01.py::Test_demo1::test_a2 - assert 2 == 3
======================================== 1 failed, 2 passed, 3 rerun in 3.20s =========================================
注:或者通过装饰器设置重跑次数和间隔时间
@pytest.mark.flaky(reruns=num1,reruns_delay=num2)#num1指重试次数,num2指间隔时间
class Test_demo1:
def test_a1(self):
print('\n这是用例a1')
assert 1 == 1
@pytest.mark.flaky(reruns=2,reruns_delay=2)
def test_a2(self):
print('\n这是用例a2')
assert 2 == 3
def test_a3(self):
print('\n这是用例a3')
assert 3 == 3
PS D:\pythonProject\case> pytest test_case01.py -sv
================================================= test session starts =================================================
platform win32 -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 -- D:\python\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.10.0', 'Platform': 'Windows-10-10.0.19042-SP0', 'Packages': {'pytest': '6.2.5', 'py': '1.10.0', 'pluggy': '1.0.0'}, 'Plugins': {'forked': '1.4.0', 'html': '3.1.1', 'metadata': '1.11.0', 'ordering': '0.6', 'rerunfailures': '10.2', 'xdist': '2.5.0'}, 'JAVA_HOME': 'D:\\Java\\jdk1.8.0_73'}
rootdir: D:\pythonProject\case, configfile: pytest.ini
plugins: forked-1.4.0, html-3.1.1, metadata-1.11.0, ordering-0.6, rerunfailures-10.2, xdist-2.5.0
collected 3 items
test_case01.py::Test_demo1::test_a1
这是用例a1
PASSED
test_case01.py::Test_demo1::test_a2
这是用例a2
RERUN
test_case01.py::Test_demo1::test_a2
这是用例a2
RERUN
test_case01.py::Test_demo1::test_a2
这是用例a2
FAILED
test_case01.py::Test_demo1::test_a3
这是用例a3
PASSED
====================================================== FAILURES =======================================================
_________________________________________________ Test_demo1.test_a2 __________________________________________________
self = <test_case01.Test_demo1 object at 0x0000027AAF469B40>
@pytest.mark.flaky(reruns=2,reruns_delay=2)
def test_a2(self):
print('\n这是用例a2')
> assert 2 == 3
E assert 2 == 3
E +2
E -3
test_case01.py:25: AssertionError
=============================================== short test summary info ===============================================
FAILED test_case01.py::Test_demo1::test_a2 - assert 2 == 3
======================================== 1 failed, 2 passed, 2 rerun in 4.23s =========================================
PS D:\pythonProject\case>
⑤多重校验——pytest-assume
当一个case有多个断言时,有一条断言失败,后面的断言就不会再执行,这时,我们可以用assume继续执行后面的断言
安装插件:
pip install pytest-assume
运行方式:
class Test_demo1:
def test_add(self):
pytest.assume(1 + 1 == 2)
print("这是第一个正确的断言")
pytest.assume(1 + 2 == 4)
print("这是第二个错误的断言")
pytest.assume(1 + 3 == 4)
print("这是第三个正确的断言")
pytest.assume(1 + 4 == 6)
print("这是第四个错误的断言")
PS D:\pythonProject\case> pytest test_case01.py -sv
================================================= test session starts =================================================
platform win32 -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 -- D:\python\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.10.0', 'Platform': 'Windows-10-10.0.19042-SP0', 'Packages': {'pytest': '6.2.5', 'py': '1.10.0', 'pluggy': '1.0.0'}, 'Plugins': {'assume': '2.4.3', 'forked': '1.4.0', 'html': '3.1.1', 'metadata': '1.11.0', 'ordering': '0.6', 'rerunfailures': '10.2', 'xdist': '2.5.0'}, 'JAVA_HOME': 'D:\\Java\\jdk1.8.0_73'}
rootdir: D:\pythonProject\case, configfile: pytest.ini
plugins: assume-2.4.3, forked-1.4.0, html-3.1.1, metadata-1.11.0, ordering-0.6, rerunfailures-10.2, xdist-2.5.0
collected 1 item
test_case01.py::Test_demo1::test_add 这是第一个正确的断言
这是第二个错误的断言
这是第三个正确的断言
这是第四个错误的断言
FAILED
====================================================== FAILURES =======================================================
_________________________________________________ Test_demo1.test_add _________________________________________________
tp = <class 'pytest_assume.plugin.FailedAssumption'>, value = None, tb = None
def reraise(tp, value, tb=None):
try:
if value is None:
value = tp()
if value.__traceback__ is not tb:
> raise value.with_traceback(tb)
E pytest_assume.plugin.FailedAssumption:
E 2 Failed Assumptions:
E
E test_case01.py:22: AssumptionFailure
E >> pytest.assume(1 + 2 == 4)
E AssertionError: assert False
E
E test_case01.py:26: AssumptionFailure
E >> pytest.assume(1 + 4 == 6)
E AssertionError: assert False
D:\python\lib\site-packages\six.py:718: FailedAssumption
=============================================== short test summary info ===============================================
FAILED test_case01.py::Test_demo1::test_add - pytest_assume.plugin.FailedAssumption:
================================================== 1 failed in 0.28s ==================================================
PS D:\pythonProject\case>
10.pytest参数化
pytest是直接使用@pytest.mark.parametrize装饰器来实现数据驱动测试的
用法:
@pytest.mark.parametrize("argnames","argvalues")
#argnames表示字符串,多个字符串用英文逗号隔开;argvalues表示参数值,多个参数值用列表或元组格式表示
①只有一个参数多个参数值时,参数值用列表形式表示
例:
class Test_demo1:
@pytest.mark.parametrize("name",["皮卡丘","打豆豆","冰墩墩"])
def test_name1(self,name):
print("第一个传递的名字是:{}".format(name))
运行结果:
PS D:\pythonProject\case> pytest test_case01.py -sv
================================================= test session starts =================================================
platform win32 -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 -- D:\python\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.10.0', 'Platform': 'Windows-10-10.0.19042-SP0', 'Packages': {'pytest': '6.2.5', 'py': '1.10.0', 'pluggy': '1.0.0'}, 'Plugins': {'assume': '2.4.3', 'forked': '1.4.0', 'html': '3.1.1', 'metadata': '1.11.0', 'ordering': '0.6', 'rerunfailures': '10.2', 'xdist': '2.5.0'}, 'JAVA_HOME': 'D:\\Java\\jdk1.8.0_73'}
rootdir: D:\pythonProject\case, configfile: pytest.ini
plugins: assume-2.4.3, forked-1.4.0, html-3.1.1, metadata-1.11.0, ordering-0.6, rerunfailures-10.2, xdist-2.5.0
collected 3 items
test_case01.py::Test_demo1::test_name1[\u76ae\u5361\u4e18] 第一个传递的名字是:皮卡丘
PASSED
test_case01.py::Test_demo1::test_name1[\u6253\u8c46\u8c46] 第一个传递的名字是:打豆豆
PASSED
test_case01.py::Test_demo1::test_name1[\u51b0\u58a9\u58a9] 第一个传递的名字是:冰墩墩
PASSED
================================================== 3 passed in 0.06s ==================================================
PS D:\pythonProject\case>
②有多个参数多个参数 值时,参数值用元组形式表示
例:
class Test_demo1:
@pytest.mark.parametrize("name,age",[("皮卡丘",100),("打豆豆",8),("冰墩墩",9)])
def test_demo1(self,name,age):
print("{}的年龄为{}".format(name,age))
运行结果:
PS D:\pythonProject\case> pytest test_case01.py -sv
================================================= test session starts =================================================
platform win32 -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 -- D:\python\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.10.0', 'Platform': 'Windows-10-10.0.19042-SP0', 'Packages': {'pytest': '6.2.5', 'py': '1.10.0', 'pluggy': '1.0.0'}, 'Plugins': {'assume': '2.4.3', 'forked': '1.4.0', 'html': '3.1.1', 'metadata': '1.11.0', 'ordering': '0.6', 'rerunfailures': '10.2', 'xdist': '2.5.0'}, 'JAVA_HOME': 'D:\\Java\\jdk1.8.0_73'}
rootdir: D:\pythonProject\case, configfile: pytest.ini
plugins: assume-2.4.3, forked-1.4.0, html-3.1.1, metadata-1.11.0, ordering-0.6, rerunfailures-10.2, xdist-2.5.0
collected 3 items
test_case01.py::Test_demo1::test_demo1[\u76ae\u5361\u4e18-100] 皮卡丘的年龄为100
PASSED
test_case01.py::Test_demo1::test_demo1[\u6253\u8c46\u8c46-8] 打豆豆的年龄为8
PASSED
test_case01.py::Test_demo1::test_demo1[\u51b0\u58a9\u58a9-9] 冰墩墩的年龄为9
PASSED
================================================== 3 passed in 0.05s ==================================================
PS D:\pythonProject\case>
③@pytest.mark.parametrize装饰测试类
此时会将参数值集合传递给类里的所有测试用例方法中
例:
@pytest.mark.parametrize("a,b,sum", [(1, 1, 2), (2, 2, 4), (3, 3, 6)])
class Test_demo1:
def test_add1(self,a,b,sum):
res = a + b
assert res == sum
print('\n测试第一组数据为',sum)
def test_add2(self, a, b, sum):
res = a + b
assert res == sum
print('\n测试第二组数据为', sum)
PS D:\pythonProject\case> pytest test_case01.py -sv
================================================= test session starts =================================================
platform win32 -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 -- D:\python\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.10.0', 'Platform': 'Windows-10-10.0.19042-SP0', 'Packages': {'pytest': '6.2.5', 'py': '1.10.0', 'pluggy': '1.0.0'}, 'Plugins': {'assume': '2.4.3', 'forked': '1.4.0', 'html': '3.1.1', 'metadata': '1.11.0', 'ordering': '0.6', 'rerunfailures': '10.2', 'xdist': '2.5.0'}, 'JAVA_HOME': 'D:\\Java\\jdk1.8.0_73'}
rootdir: D:\pythonProject\case, configfile: pytest.ini
plugins: assume-2.4.3, forked-1.4.0, html-3.1.1, metadata-1.11.0, ordering-0.6, rerunfailures-10.2, xdist-2.5.0
collected 6 items
test_case01.py::Test_demo1::test_add1[1-1-2]
测试第一组数据为 2
PASSED
test_case01.py::Test_demo1::test_add1[2-2-4]
测试第一组数据为 4
PASSED
test_case01.py::Test_demo1::test_add1[3-3-6]
测试第一组数据为 6
PASSED
test_case01.py::Test_demo1::test_add2[1-1-2]
测试第二组数据为 2
PASSED
test_case01.py::Test_demo1::test_add2[2-2-4]
测试第二组数据为 4
PASSED
test_case01.py::Test_demo1::test_add2[3-3-6]
测试第二组数据为 6
PASSED
================================================== 6 passed in 0.09s ==================================================
PS D:\pythonProject\case>
④以字典的形式表示参数值
例:
class Test_demo1:
@pytest.mark.parametrize("data",[{"name":"皮卡丘","age":100},{"name":"打豆豆","age":8},{"name":"冰墩墩","age":9}])
def test_data1(self,data):
print("小可爱们有{}".format(data))
print(f"它的名字是{data['name']},年龄是{data['age']}")
#print字符串前面加f表示格式化字符串:formatting,加f后可以在字符串里面使用大括号{ }括起来的变量和表达式
PS D:\pythonProject\case> pytest test_case01.py -sv
================================================= test session starts =================================================
platform win32 -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 -- D:\python\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.10.0', 'Platform': 'Windows-10-10.0.19042-SP0', 'Packages': {'pytest': '6.2.5', 'py': '1.10.0', 'pluggy': '1.0.0'}, 'Plugins': {'assume': '2.4.3', 'forked': '1.4.0', 'html': '3.1.1', 'metadata': '1.11.0', 'ordering': '0.6', 'rerunfailures': '10.2', 'xdist': '2.5.0'}, 'JAVA_HOME': 'D:\\Java\\jdk1.8.0_73'}
rootdir: D:\pythonProject\case, configfile: pytest.ini
plugins: assume-2.4.3, forked-1.4.0, html-3.1.1, metadata-1.11.0, ordering-0.6, rerunfailures-10.2, xdist-2.5.0
collected 3 items
test_case01.py::Test_demo1::test_data1[data0] 小可爱们有{'name': '皮卡丘', 'age': 100}
它的名字是皮卡丘,年龄是100
PASSED
test_case01.py::Test_demo1::test_data1[data1] 小可爱们有{'name': '打豆豆', 'age': 8}
它的名字是打豆豆,年龄是8
PASSED
test_case01.py::Test_demo1::test_data1[data2] 小可爱们有{'name': '冰墩墩', 'age': 9}
它的名字是冰墩墩,年龄是9
PASSED
================================================== 3 passed in 0.05s ==================================================
PS D:\pythonProject\case>
⑤笛卡尔积:多个参数化装饰器
一个函数或者一个类可以装饰多个@pytest.mark.parametrize
但是当使用多个@pytest.mark.parametrize装饰器时,最终执行的结果相当于"笛卡尔积",执行的用例数为num1*num2(num1是第一个装饰器的数据数量,num2是第二个装饰器的数据数量)
例:
class Test_demo1:
@pytest.mark.parametrize("a",[1,2]) #第一个装饰器的数据有2个
@pytest.mark.parametrize("b", ["x","y","z"]) #第二个装饰器的数据有3个
def test_add1(self,a,b):
print('\n测试数据为',a,b)
运行结果(可判断出执行的用例数量有2*3=6个):
PS D:\pythonProject\case> pytest test_case01.py -sv
================================================= test session starts =================================================
platform win32 -- Python 3.10.0, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 -- D:\python\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.10.0', 'Platform': 'Windows-10-10.0.19042-SP0', 'Packages': {'pytest': '6.2.5', 'py': '1.10.0', 'pluggy': '1.0.0'}, 'Plugins': {'assume': '2.4.3', 'forked': '1.4.0', 'html': '3.1.1', 'metadata': '1.11.0', 'ordering': '0.6', 'rerunfailures': '10.2', 'xdist': '2.5.0'}, 'JAVA_HOME': 'D:\\Java\\jdk1.8.0_73'}
rootdir: D:\pythonProject\case, configfile: pytest.ini
plugins: assume-2.4.3, forked-1.4.0, html-3.1.1, metadata-1.11.0, ordering-0.6, rerunfailures-10.2, xdist-2.5.0
collected 6 items
test_case01.py::Test_demo1::test_add1[x-1]
测试数据为 1 x
PASSED
test_case01.py::Test_demo1::test_add1[x-2]
测试数据为 2 x
PASSED
test_case01.py::Test_demo1::test_add1[y-1]
测试数据为 1 y
PASSED
test_case01.py::Test_demo1::test_add1[y-2]
测试数据为 2 y
PASSED
test_case01.py::Test_demo1::test_add1[z-1]
测试数据为 1 z
PASSED
test_case01.py::Test_demo1::test_add1[z-2]
测试数据为 2 z
PASSED
================================================== 6 passed in 0.08s ==================================================
PS D:\pythonProject\case>