目录
Pytest介绍
pytest安装
pytest运行方式
1、pytest主函数运行方式
2、pytest命令行运行方式
pytest初始化和结束方法
常用断言方式
pytest配置文件
pytest测试报告插件
pytest控制测试方法执行顺序优先级插件
pytest失败重试插件
fixture工厂函数
pytest是python的⼀种单元测试框架,同⾃带的Unittest测试框架类似, 相⽐于Unittest框架使⽤起来更简洁,效率更⾼。
pytest特点:
- ⾮常容易上⼿,⼊⻔简单,⽂档丰富,⽂档中有很多实例可以参考
- ⽀持简单的单元测试和复杂的功能测试
- ⽀持参数化
- 执⾏测试过程中可以将某些测试跳过,或者对某些预期失败的Case标记成失败
- ⽀持重复执⾏失败的Case
- ⽀持运⾏由Nose , Unittest编写的测试Case
- 具有很多第三⽅插件,并且可以⾃定义扩展
- ⽅便的和持续集成⼯具集成
安装:
win:pip install pytest
安装指定版本:pip install pytest==版本号
安装后验证:
pytest --version
如果可以查看版本,那么说明pytest已经变为命令行命令
pytest.main( [ " 需要执行文件名字 " ] )
★ 主函数运⾏仅限于debug代码调试使用
import pytest def test_001(): print('test_001') assert True # 断言方式:assert 条件表达式,条件表达式结果true或false def test_002(): print('test_002') assert False if __name__ == '__main__': pytest.main(['test_main.py'])
pytest 路径 + 文件名
默认不输出打印内容,需添加-s参数才能输出打印内容
pytest可以不指定文件名字,默认搜索:
当前路径下所有以test_开头的文件
搜索文件中以Test开头的类
搜索类中以test开头的方法
★ 命令行运行方式是项目唯一方式,方便持续集成及项目管理
# 3、模块级别setup_module
# 1 只能作⽤于测试类外部
# 2 只会在整个⽂件中最开始运⾏⼀次
def setup_module():
print('\n ---模块级别setup_module')
# 4、功能级别setup_function,仅限于类外面的测试方法
def setup_function():
print('\n ---功能级别setup_function')
class Test:
# 1、类级别setup_class和teardown_class
def setup_class(self):
print('\n ---类级别setup_class')
def teardown_class(self):
print('\n ---类级别teardown_class')
# 2、方法级别setup 和 teardown
def setup(self):
print('\n ---方法级别setup')
def teardown(self):
print('\n ---方法级别teardown')
def test_001(self):
print('测试方法test_001')
assert True
def test_002(self):
print('测试方法test_002')
assert True
def test_001():
print('测试类外的测试方法test_001')
assert True
def test_002():
print('测试类外的测试方法test_002')
assert True
命令行运行结果:
(venv) D:\untitled\pytestDemo\scripts>pytest 2_pytest初始化和结束方法.py -s
=========================================================== test session starts ============================================================
platform win32 -- Python 3.5.4, pytest-5.3.2, py-1.8.1, pluggy-0.13.1
rootdir: D:\untitled\pytestDemo\scripts
plugins: html-1.22.1, metadata-1.8.0, ordering-0.6, rerunfailures-8.0
collected 4 items
2_pytest初始化和结束方法.py
---模块级别setup_module
---类级别setup_class
---方法级别setup
测试方法test_001
.
---方法级别teardown
---方法级别setup
测试方法test_002
.
---方法级别teardown
---类级别teardown_class
---功能级别setup_function
测试类外的测试方法test_001
.
---功能级别setup_function
测试类外的测试方法test_002
.
============================================================ 4 passed in 0.06s =============================================================
(venv) D:\untitled\pytestDemo\scripts>
1、捕获断言
2、列表断言
import time
from util import get_driver
class TestSetLocation:
def setup_class(self):
# 声明我们的driver对象
self.driver = get_driver()
def teardown_class(self):
"""退出driver"""
self.driver.quit()
def test_loction(self):
"""测试方法"""
# 存储
save = self.driver.find_element_by_xpath("//*[contains(@text,'存储')]")
# WLAN
wlan = self.driver.find_element_by_xpath("//*[contains(@text,'WLAN')]")
# 存储 -> WLAN
self.driver.drag_and_drop(save, wlan)
# 点击位置信息
self.driver.find_element_by_xpath("//*[contains(@text,'位置信息')]").click()
# 点击模式
self.driver.find_element_by_xpath("//*[contains(@text,'模式')]").click()
time.sleep(1)
# 选择仅限设备
self.driver.find_element_by_xpath("//*[contains(@text,'GPS确定位置')]").click()
time.sleep(1)
# 点击返回按钮
self.driver.find_element_by_class_name("android.widget.ImageButton").click()
# 断言结果
"""方式一:捕获断言"""
# try:
# # 找预期结果
# self.driver.find_element_by_xpath("//*[contains(@text,'仅限设备')]")
# # 找到 断言成功
# assert True
# except:
# # 没找到 断言失败
# assert False
"""方式二:列表断言"""
# 获取所有文本信息
results = self.driver.find_elements_by_id("android:id/summary")
# 断言 仅限设备 在结果列表中
assert "仅限设备" in [i.text for i in results]
util.py:
from appium import webdriver
def get_driver():
desired_caps = {'platformName': 'android',
'platformVersionName': '5.1',
'deviceName': '192.168.192.101:5555',
'appPackage': 'com.android.settings',
'appActivity': '.Settings',
'unicodeKeyboard': True}
return webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
配置放在项目根目录下,当命令行输入pytest时会自动加载配置文件中的内容
配置文件名字:
pytest.ini
tox.ini
setup.cfg
配置文件首行:[pytest]
★ 配置文件中不允许出现任何的中文字符
命令行参数:addopts = 参数 # 多个参数间空格隔开
搜索测试目录:testpaths = 某个目录 # 相对路径
搜索测试文件:python_files = 文件名字/通配符 # 可采用通配符进行匹配,*:代表任意内容
搜索测试类:python_classes = 类名/类通配符
搜索测试方法:python_functions = 方法名/方法通配符
★ 类和方法名可以自己指定,要求所有人遵循规则
配置文件搜索执行方法流程:
搜索目录 > 搜索文件 > 搜索测试类名 > 搜索类中方法名
插件:pytest-html
安装:pip install pytest-html
使用方式:
命令行:--html=路径/xx.html # 等号两侧不允许出现空格
命令行:--junitxml=路径/xx.xml # 等号两侧不允许出现空格
[pytest] # not use chinese addopts = -s --html=./report/report.html --junitxml=./report/report.xml testpaths = ./scripts python_files = test_fixture.py python_classes = Test* python_functions = test*
插件:pytest-ordering
安装:pip install pytest-ordering
使用方式:
装饰器装饰测试方法
@pytest.mark.run(order=None)
import pytest # ★pytest失败重试插件不建议和setup_class方法使用,如果一定要使用,要么测试方法之间没有前后依赖关系 pip install pytest-rerunsfailures # pytest测试方法优先级插件:pip install pytest-ordering,使用方式:注解的形式 # pytest测试报告插件:pip install pytest-html(可生成xml/html报告) class TestFile: # 全为整数或负数时越小优先级越高,有正有负时,正数优先级高于负数 # 负数优先级低于未标记的测试方法,正数优先级高于未被标记的测试方法 # 0最高 @pytest.mark.run(order=2) def test_001(self): print('\n登录') assert True @pytest.mark.run(order=1) def test_002(self): print('\n查询订单') assert False
插件:pytest-rerunfailures
安装:pip install pytest-rerunfailures
使用方式:
命令⾏: --reruns n # n:重试次数 建议最⼤为2
★ 重试插件不建议和setup_class⽅法⼀起使⽤,如果⼀定要使⽤,那么测试⽅法之间没 有前后依赖关系
import pytest class TestFile: def setup_class(self): print("\n声明手机驱动对象") def test_001(self): print('\n登录') assert True def test_002(self): print('\n查询订单') assert False pytest.ini: [pytest] # not use chinese addopts = -s --html=./report/report.html --junitxml=./report/report.xml --reruns 2 testpaths = ./scripts python_files = test_fixture.py python_classes = Test* python_functions = test*
执行结果:
(venv) D:\untitled\pytestDemo\scripts>pytest 4_pytest测试方法优先级插件.py ======================================================================== test session starts ======================================================================== platform win32 -- Python 3.5.4, pytest-5.3.2, py-1.8.1, pluggy-0.13.1 rootdir: D:\untitled\pytestDemo, inifile: pytest.ini plugins: html-1.22.1, metadata-1.8.0, ordering-0.6, rerunfailures-8.0 collected 2 items 4_pytest测试方法优先级插件.py 声明手机驱动对象 登录 . 查询订单 R 声明手机驱动对象 查询订单 R 声明手机驱动对象 查询订单 F ============================================================================= FAILURES ============================================================================== _________________________________________________________________________ TestFile.test_002 _________________________________________________________________________ self = <4_pytest测试方法优先级插件.TestFile object at 0x00000263696C1320> def test_002(self): print('\n查询订单') > assert False E assert False 4_pytest测试方法优先级插件.py:24: AssertionError ========================================================================= warnings summary ========================================================================== d:\program files\python35\lib\site-packages\_pytest\junitxml.py:436 d:\program files\python35\lib\site-packages\_pytest\junitxml.py:436: PytestDeprecationWarning: The 'junit_family' default value will change to 'xunit2' in pytest 6. 0. Add 'junit_family=legacy' to your pytest.ini file to silence this warning and make your suite compatible. _issue_warning_captured(deprecated.JUNIT_XML_DEFAULT_FAMILY, config.hook, 2) -- Docs: https://docs.pytest.org/en/latest/warnings.html ----------------------------------------------- generated xml file: D:\untitled\pytestDemo\scripts\report\report.xml ------------------------------------------------ ------------------------------------------- generated html file: file://D:\untitled\pytestDemo\scripts\report\report.html ------------------------------------------- ========================================================== 1 failed, 1 passed, 1 warning, 2 rerun in 0.15s ========================================================== (venv) D:\untitled\pytestDemo\scripts>
fixture装饰器来标记固定的⼯⼚函数,在其他函数,模块,类或整个⼯程调⽤它时会被激活 并优先执⾏,通常会被⽤于完成预置处理和重复操作
Api:
fixture(scope="function", params=None, autouse=False, name=None)
常⽤参数:
# scope:被标记⽅法的作⽤域
# "function" (default):作⽤于每个测试⽅法,每个test都运⾏⼀次
# "class":作⽤于整个类,每个class的所有test只运⾏⼀次
# "module":作⽤于整个模块,每个module的所有test只运⾏⼀次
# "session":作⽤于整个session(慎⽤),每个session只运⾏⼀次
# params:(list类型)提供基础参数数据,供调⽤标记⽅法使⽤
# autouse:是否⾃动运⾏,默认为False不运⾏,设置为True⾃动运⾏
# name: ⼯⼚函数名字
使⽤⽅式:
@pytest.fixture()
⽅法()
import pytest
@pytest.fixture(name='outx')
def before_out():
print('\nbefore_out')
class TestFixture:
@pytest.fixture(name='inx') # 工厂函数被调用时是优先运行的,默认方法名作为工厂函数名字,当指定name残烛时,那么函数名不再作为工厂函数的名字
def before_in(self):
print('\nbefore_in')
# 方式一:参数引用
def test_query_all(self, outx, inx): # 工厂函数按参数先后顺序执行
print('\n查询员工')
assert True
运行结果:
(venv) D:\untitled\pytestDemo\scripts>pytest 5_工厂函数fixture.py
======================================================================== test session starts ========================================================================
platform win32 -- Python 3.5.4, pytest-5.3.2, py-1.8.1, pluggy-0.13.1
rootdir: D:\untitled\pytestDemo, inifile: pytest.ini
plugins: html-1.22.1, metadata-1.8.0, ordering-0.6, rerunfailures-8.0
collected 1 item
5_工厂函数fixture.py
before_out
before_in
查询员工
.
========================================================================= warnings summary ==========================================================================
d:\program files\python35\lib\site-packages\_pytest\junitxml.py:436
d:\program files\python35\lib\site-packages\_pytest\junitxml.py:436: PytestDeprecationWarning: The 'junit_family' default value will change to 'xunit2' in pytest 6.
0.
Add 'junit_family=legacy' to your pytest.ini file to silence this warning and make your suite compatible.
_issue_warning_captured(deprecated.JUNIT_XML_DEFAULT_FAMILY, config.hook, 2)
-- Docs: https://docs.pytest.org/en/latest/warnings.html
----------------------------------------------- generated xml file: D:\untitled\pytestDemo\scripts\report\report.xml ------------------------------------------------
------------------------------------------- generated html file: file://D:\untitled\pytestDemo\scripts\report\report.html -------------------------------------------
=================================================================== 1 passed, 1 warning in 0.11s ====================================================================
(venv) D:\untitled\pytestDemo\scripts>
import pytest
@pytest.fixture(name='outx')
def before_out():
print('\nbefore_out')
class TestFixture:
@pytest.fixture(name='inx') # 工厂函数被调用时是优先运行的,默认方法名作为工厂函数名字,当指定name残烛时,那么函数名不再作为工厂函数的名字
def before_in(self):
print('\nbefore_in')
# 方式二:函数引用,类可以使用函数引用方法引用工厂函数,函数引用和类引用同时存在时,最先调用的时函数引用方法
@pytest.mark.usefixtures('outx', 'inx') # 按参数先后顺序执行
def test_query(self):
print('查询员工')
assert True
运行结果:
(venv) D:\untitled\pytestDemo\scripts>pytest 5_工厂函数fixture.py
======================================================================== test session starts ========================================================================
platform win32 -- Python 3.5.4, pytest-5.3.2, py-1.8.1, pluggy-0.13.1
rootdir: D:\untitled\pytestDemo, inifile: pytest.ini
plugins: html-1.22.1, metadata-1.8.0, ordering-0.6, rerunfailures-8.0
collected 1 item
5_工厂函数fixture.py
before_out
before_in
查询员工
.
========================================================================= warnings summary ==========================================================================
d:\program files\python35\lib\site-packages\_pytest\junitxml.py:436
d:\program files\python35\lib\site-packages\_pytest\junitxml.py:436: PytestDeprecationWarning: The 'junit_family' default value will change to 'xunit2' in pytest 6.
0.
Add 'junit_family=legacy' to your pytest.ini file to silence this warning and make your suite compatible.
_issue_warning_captured(deprecated.JUNIT_XML_DEFAULT_FAMILY, config.hook, 2)
-- Docs: https://docs.pytest.org/en/latest/warnings.html
----------------------------------------------- generated xml file: D:\untitled\pytestDemo\scripts\report\report.xml ------------------------------------------------
------------------------------------------- generated html file: file://D:\untitled\pytestDemo\scripts\report\report.html -------------------------------------------
=================================================================== 1 passed, 1 warning in 0.78s ====================================================================
(venv) D:\untitled\pytestDemo\scripts>
类可以使⽤函数引⽤⽅法引⽤⼯⼚函数作用于类中每一个方法:
★ 函数引⽤和类引⽤⼯⼚函数同时存在时,函数引⽤优先级⾼于类引⽤优先级
import pytest
@pytest.fixture(name='outx')
def before_out():
print('\nbefore_out')
@pytest.mark.usefixtures('outx') # 类引用可以引用类内部和类外部的工厂函数,作用于类中的每一个测试方法
class TestFixture:
@pytest.fixture(name='inx') # 工厂函数被调用时是优先运行的,默认方法名作为工厂函数名字,当指定name残烛时,那么函数名不再作为工厂函数的名字
def before_in(self):
print('\nbefore_in')
# 函数引⽤和类引⽤⼯⼚函数同时存在时,函数引⽤优先级⾼于类引⽤优先级
# ★ 前提是函数引用和类引用采用的都是方式二,若测试方法采用的是方式一参数引用先执行的是类引用而并非函数引用!!!
@pytest.mark.usefixtures('inx')
def test_query_all(self):
print('\n查询员工')
assert True
执行结果:
(venv) D:\untitled\pytestDemo\scripts>pytest 5_工厂函数fixture.py
======================================================================== test session starts ========================================================================
platform win32 -- Python 3.5.4, pytest-5.3.2, py-1.8.1, pluggy-0.13.1
rootdir: D:\untitled\pytestDemo, inifile: pytest.ini
plugins: html-1.22.1, metadata-1.8.0, ordering-0.6, rerunfailures-8.0
collected 1 item
5_工厂函数fixture.py
before_in
before_out
查询员工
.
========================================================================= warnings summary ==========================================================================
d:\program files\python35\lib\site-packages\_pytest\junitxml.py:436
d:\program files\python35\lib\site-packages\_pytest\junitxml.py:436: PytestDeprecationWarning: The 'junit_family' default value will change to 'xunit2' in pytest 6.
0.
Add 'junit_family=legacy' to your pytest.ini file to silence this warning and make your suite compatible.
_issue_warning_captured(deprecated.JUNIT_XML_DEFAULT_FAMILY, config.hook, 2)
-- Docs: https://docs.pytest.org/en/latest/warnings.html
----------------------------------------------- generated xml file: D:\untitled\pytestDemo\scripts\report\report.xml ------------------------------------------------
------------------------------------------- generated html file: file://D:\untitled\pytestDemo\scripts\report\report.html -------------------------------------------
=================================================================== 1 passed, 1 warning in 0.10s ====================================================================
(venv) D:\untitled\pytestDemo\scripts>
import pytest
class Test:
def setup(self): # 工厂函数优先级高于setup
print("\n函数初始化方法setup")
def setup_class(self): # 工厂函数优先级低于setup_class
print("\n----类初始化方法setup_class")
@pytest.mark.usefixtures("inx")
def test_001(self):
print("\n---测试方法")
assert True
@pytest.fixture(name="inx")
def before(self):
print("\n-----工厂方法before")
执行结果:
(venv) D:\untitled\pytestDemo\scripts>pytest 6_工厂函数和初始化函数比较.py
======================================================================== test session starts ========================================================================
platform win32 -- Python 3.5.4, pytest-5.3.2, py-1.8.1, pluggy-0.13.1
rootdir: D:\untitled\pytestDemo, inifile: pytest.ini
plugins: html-1.22.1, metadata-1.8.0, ordering-0.6, rerunfailures-8.0
collected 1 item
6_工厂函数和初始化函数比较.py
----类初始化方法setup_class
-----工厂方法before
函数初始化方法setup
---测试方法
.
========================================================================= warnings summary ==========================================================================
d:\program files\python35\lib\site-packages\_pytest\junitxml.py:436
d:\program files\python35\lib\site-packages\_pytest\junitxml.py:436: PytestDeprecationWarning: The 'junit_family' default value will change to 'xunit2' in pytest 6.
0.
Add 'junit_family=legacy' to your pytest.ini file to silence this warning and make your suite compatible.
_issue_warning_captured(deprecated.JUNIT_XML_DEFAULT_FAMILY, config.hook, 2)
-- Docs: https://docs.pytest.org/en/latest/warnings.html
----------------------------------------------- generated xml file: D:\untitled\pytestDemo\scripts\report\report.xml ------------------------------------------------
------------------------------------------- generated html file: file://D:\untitled\pytestDemo\scripts\report\report.html -------------------------------------------
=================================================================== 1 passed, 1 warning in 0.08s ====================================================================
(venv) D:\untitled\pytestDemo\scripts>
"""工厂函数之autouse"""
import pytest
@pytest.fixture(autouse=True) # autouse=True时,login工厂函数不需要调用,会在每个测试方法执行前自动执行工厂函数
def login():
print("\n 登录")
class Test_001:
def test_001(self):
print("\n test001")
def test_002(self):
print("\n test002")
"""工厂函数之scope类级别"""
import pytest
# scope默认function,值可以是function class module session(慎用,每个session只运行一次),
# 后两个用于编写插件使用,我们主要使用前两个
@pytest.fixture(scope='class', autouse=True)
def login():
print("\n 登录")
class Test_002:
def test_001(self):
print("\n 进入个人中心")
def test_002(self):
print("\n 查看订单")
import pytest
# 注意:类级别的工厂函数 如果在类内部,运行结果为整个类的setup_class之后优先运行;
# 如果在类外部,运行结果为在整个类开始运行工厂函数
@pytest.fixture(scope='class', autouse=True)
def login():
print("\n 登录")
class Test_003:
def setup_class(self):
print('\n setup_class')
def test_001(self):
print("\n 进入个人中心")
def test_002(self):
print("\n 查看订单")
import pytest
@pytest.fixture(params=[1, 2, 3, 4]) # 可以为元组,但不推荐
def data(request): # 固定参数:request 等价于这个列表
return request.param # 固定写法,返回列表中的某个值
class Test_004:
def test_004(self, data): # 只有参数引用才可以使用工厂函数的返回值
assert data != 2
import pytest
class Test5:
@pytest.mark.skipif(True, reason='跳过方法') # 跳过
def test_01(self):
print('\n test01')
@pytest.mark.skipif(False, reason='跳过方法') # 不满足条件,不跳过
def test_02(self):
print('\n test02')
@pytest.mark.skipif(reason='跳过方法') # 跳过
def test_03(self):
print('\n test03')
@pytest.mark.skipif() # 跳过
def test_04(self):
print('\n test04')
@pytest.mark.skipif(True) # 报错
def test_05(self):
print('\n test05')
@pytest.mark.skipif(True) # 报错
def test_06(self):
print('\n test06')
import pytest
class Test6:
# 标记预期失败,但还会执行一次测试方法
@pytest.mark.xfail(True, reason='预期失败') # xpassed
def test_01(self):
print('\n test01')
assert True # 断言通过,结果为xpass; 断言失败,结果为xfail,这种情况更合理
@pytest.mark.xfail(True, reason='预期失败') # xfailed
def test_02(self):
print('\n test02')
assert False
@pytest.mark.xfail(reason='预期失败') # xfailed
def test_03(self):
print('\n test03')
assert False
@pytest.mark.xfail() # xfailed
def test_04(self):
print('\n test04')
assert False
@pytest.mark.xfail(True) # errors
def test_05(self):
print('\n test05')
assert False
@pytest.mark.xfail(False) # errors
def test_06(self):
print('\n test06')
assert False
import pytest
class Test_ParamOne:
@pytest.mark.parametrize('num', [1, 2, 3, 4]) # 上面申明,下面可以不使用 但不能不传递
def test_001(self, num): # 参数名必须要和申明的参数一致
assert num != 2
def data():
return [(1, 2, 3), (4, 4, 8)]
class Test_ParamMore:
@pytest.mark.parametrize('a, b, c', data())
def test_001(self, a, b, c):
assert a + b == c