1、简单代码示例
pytest捕获异常
with pytest.raise(异常类型):
cal.div(1,0)
#测试类
class Calculator:
def add(self,a,b):
return a+b
def div(self,a,b):
return a/b
from pytestdemo.calculator import Calculator
import pytest
class TestCal:
def setup(self):
print("开始计算")
def teardown(self):
print("结束计算")
@pytest.mark.parametrize(("a", "b", "expect"), [(1, 1, 2), (5, 4, 9)])
def test_add_int(self, a, b, expect):
cal = Calculator()
assert expect == cal.add(a, b)
@pytest.mark.parametrize(("a", "b", "expect"), [(0.1, 0.1, 0.2), (0.1, 0.2, 0.3)], ids=["float1", "float2"])
def test_add_float(self, a, b, expect):
cal = Calculator()
assert expect == round(cal.add(a, b),2)
def test_div(self):
cal = Calculator()
# try:
# cal.div(1,0)
# except ZeroDivisionError as e:
# print("除数为0")
with pytest.raises(ZeroDivisionError):
cal.div(1,0)
2、pytest框架结构
模块、函数、类、类方法共4种,第四跟第五是同一种
- 模块级(setup_module/teardown_module)模块始末,全局优先级最高
- 函数级(setup_function/teardown_function)只对函数用例生效,不在类中的函数
- 类级(setup_class/teardown_class)只在类中,前后调用一次
- 方法级(setup_method/teardown_method)在方法的前后,在类中的方法
- 类中(setup/teardown)运行在调用方法前后
3、pytest fixture的作用
fixture是运行在测试函数前后,由pytest执行的外壳函数,可以定制代码,满足多种测试需求,功能包括:
- 定义传入测试中的数据集
- 配置测试前系统的初始状态
- 为批量测试提供数据
fixture是pytest用于测试前后进行准备,清理工作代码,分离核心测试逻辑的机制
(1)类似于setup/teardown功能,但是更加灵活
(2)直接通过函数名字调用或使用装饰器@pytest.mark,usefixture('test1')
tips:使用这种方式,无法获取test1方法的返回值
(3)可使用多个fixture
(4)使用autouse自动应用,会自动在所有测试用例上执行,使用返回值,需要传fixture函数名
@pytest.fixture(autouse=True) def login(): print("登录") token="xxaascxa" return token
(5)作用域 session>module>class>function
session是整个项目的域
module是模板即测试文件
class即测试类
function即测试方法
@pytest.fixture(scope="module") def login(): print("登录") token="xxaascxa" return token
说明:这里使用了模块级别的作用域,那么这个登录操作只会在整个模块前执行一次
代码示例:
import pytest
@pytest.fixture
def login():
print("登录")
token="xxaascxa"
return token
def test_search():
print("搜索")
def test_cart():
print("购物车")
# def test_order(login):
# print("下单")
# print(login)
@pytest.mark.usefixtures('login')
def test_order():
print("下单")
print(login)
测试结果只显示了测试前置的fixture操作,怎么显示fixture测试后置的操作结果呢?
小tips:
(1)--setup-show 回溯fixture的执行过程
例子:pytest test_demo.py --setup-show
(2)fixture中可以使用yield激活teardown操作
4、命令行运行
- 运行当前目录下的所有测试文件 pytest
- 运行指定的测试文件 pytest 文件名
- 运行指定文件中的指定类或方法 pytest 文件名::测试类名::测试方法名
- 查看执行过程中的详细信息和打印信息 pytest -vs
- 只收集测试用例不运行 pytest --collect-only
- 生成执行结果文件 pytest --junitxml=./result.xml
5、场景一:前端自动化应用-按需操作
场景:测试用例执行,假设有的用例需要登录,有的用例不需要,setup/teardown不满足
可以使用fixture,默认scope 范围是function
步骤:
- 导入pytest
- 在登录函数上加装饰器@pytest.fixture()
- 在需要登录操作的测试方法中传入 登录函数名称
- 不需要登录操作的测试方法就直接执行
6、场景二:前端自动化应用 conftest.py 共享文件
场景:多人共同测试开发,公共资源要让大家都能访问到
解决方案:使用conftest.py文件进行资源共享,并且在不同位置起着不同范围的共享作用
前提条件:conftest.py文件名不能更换,放在项目下是全局数据共享的地方,全局的配置和前期工作都可以写在里面,假设放在某个包下,就是这个包数据共享的地方
执行:假设系统执行时,需要参数login,先从本文件查找是否有此名称的fixture方法,找不到会去conftest.py查找
步骤:需要先将登录模块带@pytest.fixture写入conftest.py
conftest.py用法
- 文件名称不能更换
- conftest.py与执行的测试用例要在同一个package下,并且具有__init__.py文件
- 不需要import导入,pytest会自动查找
查找的规律是就近原则,先在同级目录下查找conftest.py,没有就回去父目录查找
- 所有同目录测试文件执行前,都会先运行conftest.py文件(就近原则)
- 全局配置,前期准备工作都可以写入conftest.py
代码示例
yaml文件写法
add_int:
datas:
- [1,1,2]
- [100,100,200]
add_float:
datas:
- [0.1,0.1,0.2]
- [0.2,0.3,0.5]
ids:
- 'float1'
- 'float2'
from pytestdemo.calculator import Calculator
import pytest
@pytest.fixture()
def cal():
print("开始计算")
cal = Calculator()
yield cal
print("结束计算")
class Calculator:
def add(self,a,b):
return a+b
def div(self,a,b):
return a/b
import pytest
import yaml
def get_datas():
datas = yaml.safe_load((open('data.yaml')))
return datas
class TestCal:
@pytest.mark.parametrize(("a", "b", "expect"), get_datas()['add_int']['datas'])
def test_add_int(self, cal, a, b, expect):
assert expect == cal.add(a, b)
@pytest.mark.parametrize("a, b, expect", get_datas()['add_float']['datas'],
ids=get_datas()['add_float']['ids'])
def test_add_float(self, cal, a, b, expect):
assert expect == round(cal.add(a, b), 2)
def test_div(self, cal):
# try:
# cal.div(1,0)
# except ZeroDivisionError as e:
# print("除数为0")
with pytest.raises(ZeroDivisionError):
cal.div(1, 0)
7、pytest常用插件
pip install pytest-ordering 控制用例的执行顺序
pip install pytest-dependency 控制用例的依赖关系
pip install pytest-xdist 分布式并发执行用例
pip install pytest-rerunfailures 失败重跑
pip install pytest-assume 多重校验
pip install pytest-random-order 随机执行用例
pip install pytest-html 测试报告
8、pytest-ordering举例
GitHub源码的顺序映射
import pytest
@pytest.mark.run(order=2)
def test_foo():
assert True
@pytest.mark.last
def test_last():
assert True
@pytest.mark.first
def test_first():
assert True
@pytest.mark.run(order=3)
def test_bar():
assert True
9、pytest-dependency
控制用例之间的依赖关系
结果:
a失败
b成功
c失败
d成功
e失败
import pytest
@pytest.mark.dependency()
@pytest.mark.xfail(reason="deliberate fail")
def test_a():
assert False
@pytest.mark.dependency()
def test_b():
pass
@pytest.mark.dependency(depends=["test_a"])
# c依赖于a
def test_c():
pass
@pytest.mark.dependency(depends=["test_b"])
# d依赖于b
def test_d():
pass
@pytest.mark.dependency(depends=["test_b", "test_c"])
# e依赖于b c
def test_e():
pass
10、pytest插件开发
插件加载方式
外部插件:pip install 安装的第三方插件
本地插件:pytest自动模块发现机制,在conftest.py文件中定义的fixture
内置插件:_pytest目录加载
什么是hook
钩子,理解为挂钩,在某一时刻将自定义改写的hook函数挂载到程序中
D:\a_study_env\env2\venv\Lib\site-packages\_pytest\hookspec.py
pytest hook的执行顺序(帖子获取)
11、pytest扩展插件-实现测试用例中文
conftest.py
# 含有中文的测试用例名称,改写编码格式
def pytest_collection_modifyitems(session, config, items):
for item in items:
item.name = item.name.encode('utf-8').decode('unicode-escape')
item._nodeid = item.nodeid.encode('utf-8').decode('unicode-escape')
test_extend1.py
import pytest
@pytest.mark.parametrize('name',['哈利','赫敏'])
def test_extend1(name):
print(name)
12、pytest扩展插件-执行路径含有login的测试用例
conftest.py
# 含有中文的测试用例名称,改写编码格式
import pytest
def pytest_collection_modifyitems(session, config, items):
for item in items:
#测试用例的名字
item.name = item.name.encode('utf-8').decode('unicode-escape')
#测试用例的路径
item._nodeid = item.nodeid.encode('utf-8').decode('unicode-escape')
#如果路径存在login 那么就加上login的标签
if 'login' in item.nodeid:
item.add_marker(pytest.mark.login)
items.reverse()
test_extend1.py
import pytest
@pytest.mark.parametrize('name',['哈利','赫敏'])
def test_extend1(name):
print(name)
def test_login():
print('login')
pass
def test_login_fail():
print('login')
assert False
def test_search():
print('search')
执行命令,只执行带有login的测试用例
pytest -m login -vs
13、pytest扩展插件-添加命令行参数
conftest.py
# 含有中文的测试用例名称,改写编码格式
import pytest
import yaml
def pytest_collection_modifyitems(session, config, items):
for item in items:
# 测试用例的名字
item.name = item.name.encode('utf-8').decode('unicode-escape')
# 测试用例的路径
item._nodeid = item.nodeid.encode('utf-8').decode('unicode-escape')
# 如果路径存在login 那么就加上login的标签
if 'login' in item.nodeid:
item.add_marker(pytest.mark.login)
items.reverse()
# 添加命令行参数
def pytest_addoption(parser):
# group将下面所有的option都展示在这个group下
mygroup = parser.getgroup('testaaa')
# 注册一个命令行选项
mygroup.addoption("--env",
default='test', # 参数默认值
dest='env', # 存储的变量
help='set your run env' # 帮助提示 参数的描述信息
)
@pytest.fixture(scope='session')
def cmdoption(request):
env = request.config.getoption('--env', default='test')
if env == 'test':
print("测试环境")
dataspath='./test/datas/datas.yaml'
elif env == 'dev':
print("开发环境")
dataspath='./dev/datas/datas.yaml'
datas=yaml.safe_load(open(dataspath))
return env,datas
test_extend1.py
import pytest
@pytest.mark.parametrize('name',['哈利','赫敏'])
def test_extend1(name):
print(name)
def test_login():
print('login')
pass
def test_login_fail():
print('login')
assert False
def test_search():
print('search')
def test_env(cmdoption):
print(cmdoption)
env,datas=cmdoption
ip=datas[0]['ip']
port=datas[0]['port']
url=ip+":"+port
print(url)
14、发布插件
Packaging Python Projects — Python Packaging User Guide
pypi官网打包步骤
(1)准备好必须文件:setup.py 包
setup.py示例
from setuptools import setup setup( name='pytest_encode', url='http://github.com/xxx/pytest-encode', version='1.0', author='moon', author_email='784994407@qq.com', description='set your encoding and logger', long_description='Show Chinese for your mark.parametrize(). Define logger variable ', classifiers=[ 'Framework :: Pytest', 'Programming Language :: Python', 'Topic :: Software Development :: Testing', 'Programming Language :: Python ::3.8', ], license='proprietary', packages=['pytest_encode'], keywords=['pytest', 'py.test', 'pytest_encode', ], # 需要安装的依赖 install_requires=['pytest'], # 入口函数 entry_points={ 'pytest11': ['pytest-encode = pytest_encode', ] }, zip_safe=False )
参考pytest-ordering插件目录
(2)安装打包工具:wheel、setuptools(自带)
(3)在setup.py的文件下执行打包命令:python setup.py sdist bdist_wheel
(4)打包完成后的目录示例
dist目录:上面是源码包,下面是pip命令安装
pip安装:pip install 文件绝对路径
tips:当依赖多时,可使用命令导出
pip freeze >requirements.txt
15、常用的日志模块
import logging
logging.basicConfig(
level=logging.INFO,
# 日志格式
# 时间、代码所在文件名、代码行号、日志级别名字、日志信息
format='%(asctime)s %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s',
# 打印日志的时间
datefmt='%a, %d %b %Y %H:%M:%S',
# 日志文件存放的目录及文件名
filename='report.log',
filemode='w'
)
logger = logging.getLogger(__name__)
使用方式
import logging
import pytest
@pytest.mark.parametrize('name',['哈利','赫敏'])
def test_extend1(name):
logging.info("测试")
print(name)