基于Python语言的自动化测试框架—pytest学习笔记(持续补充中)

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>

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值