pytest使用

编写规范:
• 测试⽂件以 test_ 开头(以 _test 结尾也可以)!
• 测试类以 Test 开头,并且不能带有 __init__ ⽅法!
• 测试函数以 test_ 开头

 1)创建python文件

def add(x, y):
    return x + y

def test_add():
    print("---add1----")
    assert add(1, 2) == 3


def test_add2():
    print("---add2----")
    assert add(1.2, 3.1) == 42.3

2)控制台运行

pytest test_p02.py

D:javaideaworkSpaces	estpythonWebsimple	est03>pytest test_p02.py
====================================================================== test session starts =======================================================================
platform win32 -- Python 3.8.1, pytest-6.2.1, py-1.10.0, pluggy-0.13.1
rootdir: D:javaideaworkSpaces	estpythonWebsimple	est03
plugins: html-3.1.1, metadata-1.11.0, remotedata-0.3.2
collected 2 items                                                                                                                                                 

test_p02.py .F                                                                                                                                              [100%]

============================================================================ FAILURES ============================================================================
___________________________________________________________________________ test_add2 ____________________________________________________________________________

    def test_add2():
        print("---add2----")
>       assert add(1.2, 3.1) == 42.3
E       assert 4.3 == 42.3
E        +  where 4.3 = add(1.2, 3.1)

test_p02.py:11: AssertionError
---------------------------------------------------------------------- Captured stdout call ----------------------------------------------------------------------
---add2----
==================================================================== short test summary info =====================================================================
FAILED test_p02.py::test_add2 - assert 4.3 == 42.3
================================================================== 1 failed, 1 passed in 0.63s ===================================================================

2、参数化

import pytest

@pytest.mark.parametrize("x,y",[
    (3+3,8),
    (9-1,8),
    (2*4,8),
    (32/4,8)
])
def test_math(x,y):
    assert x==y

运行   pytest test_p03.py

D:javaideaworkSpaces	estpythonWebsimple	est03>pytest test_p03.py
====================================================================== test session starts =======================================================================
platform win32 -- Python 3.8.1, pytest-6.2.1, py-1.10.0, pluggy-0.13.1
rootdir: D:javaideaworkSpaces	estpythonWebsimple	est03
plugins: html-3.1.1, metadata-1.11.0, remotedata-0.3.2
collected 4 items                                                                                                                                                 

test_p03.py F...                                                                                                                                            [100%]

============================================================================ FAILURES ============================================================================
_________________________________________________________________________ test_math[6-8] _________________________________________________________________________

x = 6, y = 8

    @pytest.mark.parametrize("x,y",[
        (3+3,8),
        (9-1,8),
        (2*4,8),
        (32/4,8)
    ])
    def test_math(x,y):
>       assert x==y
E       assert 6 == 8

test_p03.py:10: AssertionError
==================================================================== short test summary info =====================================================================
FAILED test_p03.py::test_math[6-8] - assert 6 == 8
================================================================== 1 failed, 3 passed in 0.60s ===================================================================

单个参数,随机数

import pytest
import random

@pytest.mark.parametrize("x",[(1),(3),(5),(7)])
def test_random(x):
    ran = random.randrange(1,8)
    assert x == ran

运行

 参数化方式二:

import pytest


test_user_data=['zs','123456']


@pytest.fixture(scope="module")
def login_r(request):
    user = request.param+'___00加工00'
    print('
登录用户%s'%user)
    return user

#indirect=True,把login_r当做函数来执行
@pytest.mark.parametrize("login_r",test_user_data,indirect=True)
def test_login(login_r):
    a=login_r
    print('测试用例返回:%s'%a)
    assert a != ""

执行原理:将参数test_user_data传入方法login_r()进行加工,加工完成后,返回到test_login

结果

参数化--字典格式数据

import pytest


test_user_data=[{'user':'zs','pwd':'123456'},
                {'user':'ls','pwd':'456'},
                {'user':'ww','pwd':'16'},
                {'user':'sa','pwd':''}
                ]


@pytest.fixture(scope="module")
def login_r(request):
    user = request.param['user']
    pwd = request.param['pwd']
    print('
登录用户:%s,pwd:%s'%(user,pwd))
    if pwd:
        return True
    else:
        return False


#indirect=True 把login_r当做函数执行
@pytest.mark.parametrize("login_r",test_user_data,indirect=True)
def test_login(login_r):
    a=login_r
    print('测试用例返回:%s'%a)
    assert a ,'登录失败,密码为空'

结果

collecting ... 
登录用户:zs,pwd:123456
测试用例返回:True

 test_p02.py ✓                                                                                                                                       25% ██▌
   
登录用户:ls,pwd:456
测试用例返回:True
 test_p02.py ✓✓                                                                                                                                      50% █████
     
登录用户:ww,pwd:16
测试用例返回:True
 test_p02.py ✓✓✓                                                                                                                                     75% █████
██▌  
登录用户:sa,pwd:
测试用例返回:False


―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― test_login[login_r3]
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――

login_r = False

    @pytest.mark.parametrize("login_r",test_user_data,indirect=True)
    def test_login(login_r):
        a=login_r
        print('测试用例返回:%s'%a)
>       assert a ,'登录失败,密码为空'
E       AssertionError: 登录失败,密码为空
E       assert False

test_p02.py:27: AssertionError

 test_p02.py ⨯                                                                                                                                      100% █████
█████
==================================================================== short test summary info =====================================================================
FAILED test_p02.py::test_login[login_r3] - AssertionError: 登录失败,密码为空

Results (0.60s):
       3 passed
       1 failed
         - test_p02.py:23 test_login[login_r3]
View Code

参数化---多个字典

import pytest


test_user_data=[{'user':'zs','pwd':'123456'},
                {'user':'ls','pwd':'456'},
                {'user':'ww','pwd':'16'}
                ]

test_user_data2 = [{"q": "中国平安", "count": 3, "page": 1},
                   {"q": "阿里巴巴", "count": 2, "page": 2},
                   {"q": "pdd", "count": 3, "page": 1}]

@pytest.fixture(scope="module")
def login_r(request):
    #数据加工
    user = request.param['user']
    pwd = request.param['pwd']
    print('
登录用户:%s,pwd:%s'%(user,pwd))
    return request.param

@pytest.fixture(scope="module")
def query_param(request):
    q = request.param['q']
    count = request.param['count']
    page = request.param['page']
    print("查询的搜索词:%s" % q)
    return request.param


# 这是pytest的参数化数据驱动,indeirect=True 是把login_r当作函数去执行
# 从下往上执行
# 两个数据进行组合测试有3*3个测试用例执行(test_user_data1的个数*test_user_data2的个数)
@pytest.mark.parametrize("query_param",test_user_data2,indirect=True)
@pytest.mark.parametrize("login_r",test_user_data,indirect=True)
def test_login(login_r,query_param):
    values = login_r.values()
    print('用户名 %s ,密码:%s'%(list(values)[0],list(values)[1]))
    u_values =  query_param.values()
    print('名称:%s,总共:%s页,当前:%s页' %(list(u_values)[0],list(u_values)[1],list(u_values)[2]))

结果

D:javaideaworkSpaces	estpythonWebpytest_simple	est06>pytest -s  test_p03.py
Test session starts (platform: win32, Python 3.8.1, pytest 6.2.1, pytest-sugar 0.9.4)
rootdir: D:javaideaworkSpaces	estpythonWebpytest_simple	est06
plugins: assume-2.4.2, html-3.1.1, metadata-1.11.0, ordering-0.6, remotedata-0.3.2, rerunfailures-9.1.1, sugar-0.9.4
collecting ... 
登录用户:zs,pwd:123456
查询的搜索词:中国平安
用户名 zs ,密码:123456
名称:中国平安,总共:3页,当前:1页

 test_p03.py ✓                                                                                                                                       11% █▎
  查询的搜索词:阿里巴巴
用户名 zs ,密码:123456
名称:阿里巴巴,总共:2页,当前:2页
 test_p03.py ✓✓                                                                                                                                      22% ██▎
   
登录用户:ls,pwd:456
用户名 ls ,密码:456
名称:阿里巴巴,总共:2页,当前:2页
 test_p03.py ✓✓✓                                                                                                                                     33% ███▍
    查询的搜索词:中国平安
用户名 ls ,密码:456
名称:中国平安,总共:3页,当前:1页
 test_p03.py ✓✓✓✓                                                                                                                                    44% ████▌
     查询的搜索词:pdd
用户名 ls ,密码:456
名称:pdd,总共:3页,当前:1页
 test_p03.py ✓✓✓✓✓                                                                                                                                   56% █████
▋    
登录用户:zs,pwd:123456
用户名 zs ,密码:123456
名称:pdd,总共:3页,当前:1页
 test_p03.py ✓✓✓✓✓✓                                                                                                                                  67% █████
█▋   
登录用户:ww,pwd:16
用户名 ww ,密码:16
名称:pdd,总共:3页,当前:1页
 test_p03.py ✓✓✓✓✓✓✓                                                                                                                                 78% █████
██▊  查询的搜索词:阿里巴巴
用户名 ww ,密码:16
名称:阿里巴巴,总共:2页,当前:2页
 test_p03.py ✓✓✓✓✓✓✓✓                                                                                                                                89% █████
███▉ 查询的搜索词:中国平安
用户名 ww ,密码:16
名称:中国平安,总共:3页,当前:1页
 test_p03.py ✓✓✓✓✓✓✓✓✓                                                                                                                              100% █████
█████

Results (0.23s):
       9 passed
View Code

3、运行进度条

安装 :  pip install pytest-sugar

 4、assume

pip install assume

assert断言失败之后,后面的断言也不会执行,包括正常的代码

assume即使断言失败,后面的断言还是会继续执行,这有助于我们分析和查看到底一共有哪些断言是失败的,直接用assert更高效

import pytest

def test_assume():
    pytest.assume(1 == 2)
    pytest.assume(2 == 2)
    pytest.assume(3==2)

 5、rerunfailures

在web、APP⾃动化测试中, 经常出现超时导致测试失败,所以需要重新运行

pip install pytest-rerunfailures

测试代码

import random
def add(x,y):
    return x+y

def test_add():
    ran=random.randint(1,10)
    assert add(1,3) == ran

运行结果

pytest --reruns 8 test_p07.py

设置最大重新运行测试为8。

rerun  5次,总共运行6次,第六次时测试通过,不再运行。

 6、Pytest-ordering

使得测试方法按照指定顺序运行

pip install pytest-ordering

代码

import pytest

value = 0
@pytest.mark.run(order=2)
def test_add():
    print("---add        [order=2]       ---")
    assert value == 10

@pytest.mark.run(order=1)
def test_sub():
    print("--sub     [order=1]---")
    global value
    value = 10
    assert value == 10

7、冒泡排序

def bubbleSort(arr):
    n = len(arr)
    # 遍历所有数组元素
    for i in range(n):
        for j in range(0, n-i-1):
            if arr[j] > arr[j+1] :
                arr[j], arr[j+1] = arr[j+1], arr[j]

arr = [64, 34, 25, 12, 22, 11, 90]
bubbleSort(arr)
print ("排序后的数组:")
for i in range(len(arr)):
    print ("%d" %arr[i])

8、函数级别

 代码

import pytest


def setup_module():
    print('整个模块.py开始')


def teardown_module():
    print('整个模块.py结束')


def setup_function():
    print('不在类中的函数前')


def teardown_function():
    print('不在类中的函数后')


def test_no_01():
    print('不在类中的方法1')


def test_no_02():
    print('不在类中的方法2')


class TestClass:
    def setup_class(self) -> None:
        print('类前面')

    def teardown_class(self):
        print('类之后')

    def setup_method(self):
        print('方法前')

    def teardown_method(self):
        print('方法后')

    def test_one(self):
        print('one')


    def test_two(self):
        print('two')



if __name__ == '__main__':
    pytest.main(["-s", "test_p01.py"])

结果

模块(setup_module) --》不在类中的方法(setup_function)--》具体方法,不再在中--》类(setup_class)--》类中的方法(setup_method)--》具体方法

 9、运行方式

 10.pytest-fixture

 类似于setup,在一个方法之前运行

在加入购物车、购买之前先登录,scope="function"

实现方式一:直接将fixture的引用写在方法的参数,如  test_shoppingChart(login):

import pytest


@pytest.fixture()
def login():
    print('登录')


def test_select():
    print('搜索')


def test_shoppingChart(login):
    print('加入购物车')


def test_pay(login):
    print('购买')


if __name__ == '__main__':
    pytest.main(['-s', 'test_p02.py'])

结果

 实现方式二@pytest.mark.usefixtures("login")

import pytest


@pytest.fixture()
def login():
    print('登录')


def test_select():
    print('搜索')


@pytest.mark.usefixtures("login")
def test_shoppingChart():
    print('加入购物车')


@pytest.mark.usefixtures("login")
def test_pay():
    print('购买')

将公共模块放入共享文件

 查找顺序:先在本文件中查找,如果没找到,在从conftest.py中查找

例如:刚才的例子,将login放进conftest.py中,进行数据共享

 

 运行

 11、yield

使用场景:在执行方法前要执行依赖的模块,在执行方法后要销毁清除数据,范围模块级别  scope="module"

import pytest


@pytest.fixture(scope="module")
def openBrower():
    print('打开浏览器')

    yield
    print('关闭浏览器')


def test_search(openBrower):
    print('搜索:shell')


def test_look(openBrower):
    print('查看浏览器返回结果')


def test_searchAagin(openBrower):
    print('搜索:bash')

结果

如上述代码如果每个方法的调用都通过参数指定,会比较麻烦而且不利于扩展,可以选择

使用fixture中参数autouse=True,替换掉方法参数


import pytest


@pytest.fixture(scope="module",autouse=True)
def openBrower():
print('打开浏览器')

yield
print('关闭浏览器')


def test_search():
print('搜索:shell')


def test_look():
print('查看浏览器返回结果')


def test_searchAagin():
print('搜索:bash')

综合:@pytest.fixture(scope="module", autouse=True),@pytest.mark.usefixtures("login")一起使用

import pytest


@pytest.fixture(scope="module",autouse=True)
def openBrower():
    print('打开浏览器')
    print('进入网页版淘宝')
    yield
    print('关闭浏览器')


@pytest.fixture(scope="function")
def login():
    print('登录')


#搜索:不用登陆
def test_search():
    print('搜索:包包')


#加入购物车之前,先登录
@pytest.mark.usefixtures("login")
def test_chart():
    print('加入购物车')


#下单之前,先登录
@pytest.mark.usefixtures("login")
def test_pay():
    print('下单')

结果

 12、skip与xfail

 

 代码

import pytest
import sys
import time
# scope=function
'''
skip
'''

def test_soso(login):
    print('case1: 登录后执行搜索')
    assert 1 == 1
    assert {'name': 'linda', 'age': 18} == {'name': 'linda', 'age': 188}
    a = 'hello'
    age = 35
    assert a in 'hello world'
    assert 20 < age < 80


def f():
    return 3


@pytest.mark.skip
def test_cakan():
    print('case2:不登陆,就看是否执行')
    assert f() == 4


environment = 'android'


@pytest.mark.skipif('environment=="android"', reason='android平台没有这个功能')
def test_cart_3(login):
    print('case3,登陆,点击苹果图标')


@pytest.mark.skipif(sys.platform =='win32', reason='不在windows下运行')
@pytest.mark.skipif(sys.version_info < (3,6), reason='3.6版本行,您需要更高版本')
def test_cart(login):
    print('case3,登陆,点击苹果图标,3.6以下版本无法执行')


@pytest.mark.xfail
def test_xfail():
    print(broken_fixture())


def broken_fixture():
    raise Exception("Sorry, it's 中断异常.")

运行

 

 运行时指定平台

 代码

import pytest


@pytest.mark.webtest
def test_send_http():
    pass


@pytest.mark.apptest
def test_devide():
    pass


@pytest.mark.android
def test_search():
    pass


@pytest.mark.ios
def test_add():
    pass


def test_plus():
    pass


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

运行

pytest -s test_p05.py -m "ios"

 13、通过文件名、类名、方法名及组合调动部分用例执行

 pytest-s -v 文件名::类名::方法名

pytest -k "类名 and  方法名"

 

14、用例出错时停止

 15、多线程并行与分布式执行

16、pytest-html生成测试报告

pip install pytest-html

pytest -v -s --html=report.html -- self-contained-html

17、allure生成测试报告

1)下载

https://github.com/allure-framework/allure2/releases

2)安装

解压到任意⽬录下,把 allure-2.7.0in, 加⼊到环境path⾥⾯

命令⾏下执⾏: pip install allure-pytest

命令⾏下执⾏:allure --version  如果出现版本号就代表安装成功

3)使用

方式一:

在测试期间收集结果

pytest -s -q --alluredir=./result/

从结果生成报告,这是一个启动Tomcat的服务,只生成报告.clean用于覆盖路径

allure generate ./result/ -o ./report/ --clean

打开报告

allure open -h 127.0.0.1 -p 8883 ./report/

方式二

在测试期间收集结果

pytest -s -q --alluredir=./result/

测试完成后查看实际报告,在线看报告

allure serve ./result/

 18、在测试报告中增加‘测试功能、子功能/场景、测试步骤、附加信息’等信息

利用@Feature,story,step,@attach

步骤:

1)import allure

2)在功能上加@allure.feature('功能名称')

3)在子功能上加@allure.story('子功能名称')

4)在步骤上加@allure.step('步骤细节)

5)需要附加信息,可以是数据、文本、图片、网页;在要附加的地方加@allure.attach('具体文本信息')

6)如果只测试购物车功能可以限制顾虑:pytest 文件名 --allure_features='购物车功能' --allure_stories='加入购物车' 

 

 19、按照重要级别进行一定范围测试

lure.severity("critical")               # 优先级,包含blocker, critical, 
def test_case_19688(para_one, para_two):
if __name__ == '__main__':
    # 执行,指定执行测试模块_demo1, 测试模块_demo2两个模块,同时指定执行的用例优先级为critical,blocker
    pytest.main(['--allure_feature=测试功能_demo1', '--allure_stories=测试模块_demo2', '--allure_severities=critical, blocker'])

完整代码

import pytest
import allure
import logging

'''
test_allure_all.py
'''
# 测试函数
@allure.step("测试步骤1:字符串相加:{0},{1}")     # 测试步骤,可通过format机制自动获取函数参数
def str_add(str1, str2):
    print("hello",str1,str2)
    if not isinstance(str1, str):
        return "%s 不是字符串" % str1
    if not isinstance(str2, str):
        return "%s 不是字符串" % str2
    return str1 + str2


@allure.description("测试相加的各种情况")
@allure.severity("critical")               # 优先级,包含blocker, critical, normal, minor, trivial 几个不同的等级
@allure.feature("测试功能_demo1")           # 功能块,feature功能分块时比story大,即同时存在feature和story时,feature为父节点
@allure.story("测试模块_demo2")             # 功能块,具有相同feature或story的用例将规整到相同模块下,执行时可用于筛选
# @allure.issue("BUG号:123")                 # BUG编号,关联标识已有的问题,可为一个url链接地址
@allure.issue("http://www.jira.com/id=19688")   # BUG编号,关联标识已有的问题,可为一个url链接地址
# @allure.testcase("用例名:测试字符串相等")      # 用例标识,关联标识用例,可为一个url链接地址
@allure.testcase("http://www.testlink.com/id=19688")
@pytest.mark.parametrize("para_one, para_two",              # 用例参数
                         [("hello world", "hello world"),   # 用例参数的参数化数据
                          ('4', '54'),
                          ("我不是超人", "我是超人"),
                          ("888", "我是超人")],
                         ids=["letter",          # 对应用例参数化数据的用例名
                              "decimal123",
                              "unicode",
                              "mix"])
def test_case_19688(para_one, para_two):
    """用例描述:测试字符串相等
    :param para_one: 参数1
    :param para_two: 参数2
    """
    logging.info("这是测试的信息,在log中输出")
    # 获取参数
    paras = vars()
    # 关联的资料信息, 可在报告中记录保存必要的相关信息
    allure.attach("用例参数", "{0}".format(paras))
    # 调用测试函数
    res = str_add(para_one, para_two)
    # 对必要的测试中间结果数据做备份
    allure.attach("str_add返回结果", "{0}".format(res))
    # 测试步骤,对必要的测试过程加以说明
    with allure.step("测试步骤2,结果校验 {0} == {1}".format(res, para_one+para_two)):
        allure.attach('<html><head></head><body> 附加网页看效果 </body></html>', '这是错误页的结果信息', allure.attachment_type.HTML)
        assert res == para_one+para_two, res


if __name__ == '__main__':
    # 执行,指定执行测试模块_demo1, 测试模块_demo2两个模块,同时指定执行的用例优先级为critical,blocker
    pytest.main(['--allure_feature=测试功能_demo1', '--allure_stories=测试模块_demo2', '--allure_severities=critical, blocker'])
View Code

 20、前端自动化测试截图

 完整代码

import allure
from selenium import webdriver
import time
import pytest

'''
test_sele_allure.py
'''

@allure.testcase("https://www.baidu.com的搜索功能")
@pytest.mark.parametrize('test_data1', ['allure', 'pytest', 'unittest'])
def test_steps_demo(test_data1):
    with allure.step('step one:打开浏览器输入百度网址'):
        driver = webdriver.Chrome()
        driver.get('https://www.baidu.com')
    with allure.step('step two:在搜索栏输入allure,并点击百度一下'):

        driver.find_element_by_id('kw').send_keys(test_data1)
        time.sleep(1)
        driver.find_element_by_id('su').click()
        time.sleep(1)
    with allure.step('step three:截图保存到项目中'):

        driver.save_screenshot("./result/b.png")
        # f = open('./result/b.png', 'rb').read()
        allure.attach.file("./result/b.png", attachment_type=allure.attachment_type.PNG)
        allure.attach('<head></head><body> 首页</body>', 'Attach with HTML type',
                      allure.attachment_type.HTML)

    with allure.step('step four:关闭浏览器,退出'):
        driver.quit()
View Code

结果

分布式测试插件:https://github.com/pytest-dev/pytest-xdist

 https://github.com/linda883/
原文地址:https://www.cnblogs.com/ychun/p/14372834.html