目录
4、@allure.description @ allure.description_html
5、@allure.link、@allure.issue、@allure.testcase
7、@allure.severity(allure.severity_level.TRIVIAL)
(1)pytest_collection_modifyitems(session, config,items)
pytest命令行参数
(1)--tb
--tb=auto 有多个用例失败的时候,只打印第一个和最后一个用例的回溯信息
--tb=long 输出最详细的回溯信息
--tb=short 输入assert的一行和系统判断内容
--tb=line 使用一行显示错误信息
--tb=native 只输出python标准库的回溯信息
--tb=no 不显示回溯信息
import pytest
def test_region():
with pytest.raises(ZeroDivisionError):
10 / 10
运行:我长时间使用了--tb=no(使用数据驱动的话很烦这个信息)
(2)-n auto --dist=loadscope
作用:并行执行用例
安装包:pip install pytest-xdist
- -n auto:可以自动检测到系统的CPU核数;从测试结果来看,检测到的是逻辑处理器的数量
- --dist=loadscope:将按照同一个模块module下的函数和同一个测试类class下的方法来分组,然后将每个测试组发给可以执行的worker,确保同一个组的测试用例在同一个进程中执行
- --dist=loadfile:按照同一个文件名来分组,然后将每个测试组发给可以执行的worker,确保同一个组的测试用例在同一个进程中执行
(3)-m
作用:筛选用例
- 命令1:pytest -m "P0" (过滤mark了P0的用例)
- 命令2:pytest -m "P0 or P1" (过滤mark了P0或者P1的用例)
- 命令3:pytest -m "P0 and P1" (过滤mark了P0且P1的用例)
- 命令4:pytest -m "P0 and not P1" (过滤mark了P0且没有P1的用例)
1、allure
(1)安装allure
依赖java:Linux系统下安装jdk及环境配置(两种方法)_橙子君的博客-CSDN博客_linux配置jdk(liunx)
安装包:https://github.com/allure-framework/allure2/releases/download/2.13.8/allure-2.13.8.zip
放在那里看自己心情(liunx、windows)下
windows下:allure安装配置 - 简书
liunx下:配置软连接方式添加allure命令------ln -s /data/hanfang/.virtualenvs/shijian/allure-2.13.8/bin/allure /usr/bin/allure (第一个path是allure的解压路径)
验证allure --version
辅助包:pip install allure-pytest(实际应用中pytest安装辅助包即可不需要单独安装allure,但是nosetest需要安装allure)
异常处理:
报错提示AttributeError: module 'allure' has no attribute 'severity_level'或者下图
pip list |grep allure
pip uninstall pytest-allure-adaptor2(就是这个东西的祸端,有时候你安装的是pytest-allure-adaptor就卸载它)
这里特别说明下:pytest-allure-adaptor2 和pytest-allure-adaptor这两个包可以生成xml格式的结果(如果需要xml结果的报告时)
重点:1、和allure-pytest不能同时存在(nose使用nose-allure-plugin和pytest-allure-adaptor两个包,pytest使用allure-pytest)
2、adaptor不支持allure的很多标签,例如epic
pip install allure-pytest
(2)allure使用
第一步:将以上用例输出测试报告:pytest tmpdir_tmpdir_factory.py --alluredir=./report
第二步:将报告转化为html
allure generate ./report -o ./html --clean :将./report这个目录的报告转化到./html下生成html的报告, 源路径默认是allure-results(对应./report) ,转出地址默认值是allure-report(对应./html)
第三步:打开报告
pycharm在index.html右键打开在 浏览器
vscode中使用allure启动server方式:allure open html(html的文件夹就是上面生成的报告)
环境变量展示:allure报告环境配置(environment) - xyztank - 博客园
相关参考:Jenkins执行testNG生成美观的测试报告(Allure2)_qq_15290529的博客-CSDN博客
官网说明书:Allure Framework
好的文章:allure--下的各装饰器的翻译及自己的总结 - 你是猪啊! - 博客园
1、@allure.step()
展示步骤都干啥了,层级和入参有助于分析.
和入参关联动态变化
# python中关键字参数和位置参数获取方式
import allure
@allure.step("这样的找到确认这个p1一定有就行---模板名:{p1}")
def get_preset_detail(**kwargs):
p1 = kwargs.get("p1")
def test_preset_detail():
get_preset_detail(p1="lala")
2、@allure.title()
和step用法一样,展示的位置不同
3、allure.attach()
添加附件------图片、连接,文件等
4、@allure.description @ allure.description_html
方法描述,相当如方法下面的"""方法注释"""
5、@allure.link、@allure.issue、@allure.testcase
Pytest(16) - allure参数:@allure.link()、@allure.issue()、@allure.testcase()_KeKe_Winner的博客-CSDN博客
6、allure的标记装饰器
Pytest系列(23)- allure 打标记之 @allure.epic()、@allure.feature()、@allure.story() 的详细使用 - 小菠萝测试笔记 - 博客园
# 只运行 epic 名为 test 的测试用例
pytest --alluredir ./report/allure --allure-epics=test
# 只运行 feature 名为 模块 的测试用例
pytest --alluredir ./report/allure --allure-features=模块
# 只运行 story1、story2 的测试用例(也可以不用=号 空格就行了哦)
pytest tests.py --allure-stories story1,story2
# 指定 feature+story
pytest tests.py --allure-features feature2 --allure-stories story2
7、@allure.severity(allure.severity_level.TRIVIAL)
@allure.severity(allure.severity_level.TRIVIAL)
@allure.severity(allure.severity_level.NORMAL)
@allure.severity(allure.severity_level.NORMAL)
@allure.severity(allure.severity_level.CRITICAL)
(3)allure介入jenkins
参考文章:Jenkins Allure Report 插件的相关配置_MaggieTian77的博客-CSDN博客
(省略了jenkins安装allure插件)
1、Allure Commandline配置
2、JDK配置
Allure Commandline配置的地方一样
3、allure report插件配置
jenkins没有其他处理,就是在指定的目录运行nosetests命令后就到了allure Report
执行完了的 结果
4、html的报告
5、结果发送邮件
6、pytest+allure+jenkins
与nosetest框架不同点:
(1)allure报告结果会左右jenkins的任务的状态
可以忽略
(2)会出现pytest和Jenkins环境变量不一致情况
--metadata-from-json "{}"
(3)pytest产生html报告
安装包:pip install pytest-html
命令:--html=report.html --self-contained-html
2、conftest中的固定方法
(1)pytest_collection_modifyitems(session, config,items)
items:用例对象列表
用例运行后展示------个人认为需要用
# conftest.py文件内容
def pytest_collection_modifyitems(items):
"""
测试用例收集完成时,将收集到的item的name和nodeid的中文显示在控制台上
"""
for item in items:
item.name = item.name.encode("utf-8").decode("unicode_escape")
item._nodeid = item.nodeid.encode("utf-8").decode("unicode_escape")
if item.obj.__doc__:
item._nodeid += ":" + item.obj.__doc__.strip()
# 测试文件内容
# -*- coding:utf-8 -*-
from contextlib import contextmanager
import pytest
@contextmanager
def does_not_raise():
yield
@pytest.mark.parametrize(
"example_input,expectation",
[
pytest.param(3, does_not_raise(), id="除数是3的情况"),
pytest.param(2, does_not_raise(), id="除数是2的情况"),
pytest.param(1, does_not_raise(), id="除数是1的情况"),
pytest.param(0, pytest.raises(ZeroDivisionError), id="除数是0的情况"),
],
)
def test_division(example_input, expectation):
"""中文怎么办"""
with expectation:
assert (6 / example_input) is not None
用例执行顺序------必要性不大 (参考文章pytest文档33-Hooks函数获取用例执行结果(pytest_runtest_makereport) - 上海-悠悠 - 博客园)
# conftest.py内容
def pytest_collection_modifyitems(items):
# 将用例名拿出来存入新列表-利用用例名找到items的索引位置才能操作items这个列表
new_items = []
for item in items:
new_items.append(item.name)
# 1. 删除 test_A_002 用例
# 获取 test_A_002 在新列表的索引
index_2 = new_items.index('test_A_002')
# 在老列表中删除这个索引
del items[index_2]
del new_items[index_2] # 新列表同步删除,和老列表保持同步
# 2. 调换 1 和 3 的顺序
# 获取 1 和 3 在新列表的索引
index_1 = new_items.index('test_A_001')
index_3 = new_items.index('test_A_003')
# 根据索引在老列表中调换位置
items[index_1], items[index_3] = items[index_3], items[index_1]
# 排序的另一个方法实现,按照用例名称排序,是不是也可以根据id的顺序??给id带上序号
def pytest_collection_modifyitems(session, items):
print(type(items))
print("收集到的测试用例:%s" % items)
# sort排序,根据用例名称item.name 排序
items.sort(key=lambda x: x.name)
print("排序后的用例:%s" % items)
for item in items:
print("用例名:%s" % item.name)
#用例
def test_A_001():
pass
def test_A_002():
pass
def test_A_003():
pass
动态给用例添加mark功能
def pytest_collection_modifyitems(items):
"""动态根据方法名添加mark,酸爽啊,名字就是mark"""
for item in items:
print(item.name)
print(item.own_markers)
temp = item.name.split("_")
print(temp)
for i in temp:
temp_mark = "pytest.mark." + i
item.add_marker(eval(temp_mark))
print(item.own_markers)
(2)pytest_addoption
添加用户自定义命令行参数:我实际用处是一个用例要不要检查mysql,要不要检查redis
# conftest.py中内容
# 内容说明: 添加了两个参数--cdb检查要不要和数据库对数
# --crd检查要不要和redis对数
# pytest_addoption方法只是让命令行认识了你的参数
# 下面的方法才能得到参数的值:request获取、pytestconfig获取(推荐)
import os
import pytest
def pytest_addoption(parser):
"""定义一些命令行参数"""
parser.addoption("--check_db", action="store_const",
default=0,
const=1,
help="--check_db是否有mysql的操作")
parser.addoption("--check_redis", action="store_const",
default=0,
const=1,
help="--check_redis是否有redis的操作")
@pytest.fixture(scope="session", autouse=True)
def checkout_db(request):
"""是否操作数据库--用例执行不方便执行mysql的时候就不执行了"""
cdb_value = request.config.getoption("--check_db")
print(type(cdb_value))
os.environ['check_db'] = str(cdb_value)
print('\n --check_db参数值:', cdb_value)
return cdb_value
@pytest.fixture(scope="session", autouse=True)
def checkout_redis(request):
"""是否操作redis--用例执行不方便执行redis的时候就不执行了"""
crd_value = request.config.getoption("--check_redis")
os.environ['check_redis'] = str(crd_value)
print('\n --check_redis参数值:', crd_value)
return crd_value
测试文件
# _*_coding:utf-8_*_
import os
def test_addoption(checkout_db):
res = checkout_db
assert res == 1
def test_addoption1():
res = os.environ['check_db']
assert res == "1"
3、fixture使用
(1)作用范围
scope参数指定范围:session>module>class>function
# -*- coding:utf-8 -*-
import pytest
@pytest.fixture(scope='session')
def fix_session():
print('----- session create -----')
driver = 'Chrome'
yield driver
print('----- session close -----')
@pytest.fixture(scope='class')
def fix_class():
print('----- class create -----')
driver1 = 'class'
yield driver1
print('----- class close -----')
@pytest.fixture(scope='module')
def fix_module():
print('----- module create -----')
driver2 = 'module'
yield driver2
print('----- module close -----')
@pytest.fixture(scope='function')
def fix_function():
print('----- function create -----')
driver3 = 'function'
yield driver3
print('----- function close -----')
测试文件1
def test_1(fix_session, fix_module, fix_class, fix_function):
print("test_1")
def test_2(fix_session, fix_module, fix_class, fix_function):
print("test_2")
测试文件2
class Test1:
def test_1(fix_session, fix_module, fix_class, fix_function):
print("test_1")
def test_2(fix_session, fix_module, fix_class, fix_function):
print("test_2")
测试 命令:pytest --setup-show 文件1 文件2
(2)fixture实现setup和tearDown
yield前面是setup内容,后面是tearDown内容
# conftest内容
@pytest.fixture(params=["yes", "hello"])
def fixture_setup_teardown(request):
print("setup")
yield request.param
print("teardown")
测试文件
def test_fixture(fixture_setup_teardown):
print(fixture_setup_teardown)
print("我才是主角")
(3)fixture传递测试数据
fixture函数return返回测试数据
# conftest文件内容
@pytest.fixture(scope="session", params=["opt1", "opt2"])
def optmod(request):
return pytest.importorskip(request.param)
@pytest.fixture(scope="session")
def basemod(request):
return pytest.importorskip("subprocess")
测试文件
def test_func1(basemod, optmod):
print(basemod)
print(optmod)
因为没有module是opt1和opt2和base,所以就会跳过
如果把opt1和opt2和base换为string,re,subprocess ,就会执行通过
(3)参数化fixture
这个参数indirect=True,一定不能少,要不就会直接把 fixture_param当成测试函数的一个参数来用,加上indirect=True这个参数,才会在fixture的函数中查找
@pytest.fixture(params=[1, 2, 3])
def fixture_param(request):
print("我是fixture_param,这是第%s次打印" % request.param)
return request.param
#覆盖了fixture_param的参数
@pytest.mark.parametrize("fixture_param", ["a", "b"], indirect=True)
@pytest.mark.parametrize("a,b", [(1, 6), (2, 7), (3, 8), (4, 9)])
def test_fixture_param_and_parametrize(a, b, fixture_param):
print("我是测试函数test_fixture_param_and_parametrize,参数a是%s,b是%s" % (a, b))
@pytest.mark.parametrize("a,b", [(1, 6), (2, 7), (3, 8), (4, 9)])
def test_fixture_param_and_parametrize1(a, b, fixture_param):
print("我是测试函数test_fixture_param_and_parametrize,参数a是%s,b是%s" % (a, b))
if __name__ == '__main__':
pytest.main(["-s", 'test_fixture_params.py'])
(4)使用方式
第一:调用方式
(1)作为参数传入测试函数
(2)@pytest.mark.usefixtures("fixture名字")方式传入
区别:标签方式没有办法获取fixture的返回值
作为测试函数没有办法覆盖参数化
第二:执行顺序
原则越靠近测试函数,越先执行
import pytest
@pytest.fixture(params=[1, 2])
def fixture_1(request):
print("我是fixture_1_%s" % request.param)
return request.param
@pytest.fixture(params=[4])
def fixture_2(request):
print("我是fixture_2_%s" % request.param)
return request.param
@pytest.mark.usefixtures("fixture_2")
@pytest.mark.usefixtures("fixture_1")
def test_fixture_1():
print("我是测试函数1")
def test_fixture_2(fixture_1, fixture_2):
print(fixture_1 + fixture_2)
print("我是测试函数2")
@pytest.mark.usefixtures("fixture_1")
@pytest.mark.usefixtures("fixture_2")
def test_fixture_3():
print("我是测试函数3")
def test_fixture_4(fixture_2, fixture_1):
print(fixture_1 + fixture_2)
print("我是测试函数4")
if __name__ == '__main__':
pytest.main(["-s", 'test_fixture_params.py'])
第三:自动执行
简单点不细说
@pytest.fixture(scope="session", autouse=True)
def which_region(request):
"""区分region的命令行参数获取"""
which_region = request.config.getoption("--region")
os.environ['region'] = which_region
print('\n --region参数值:', which_region)
return which_region
4、用例参数化
(1)fixture传入测试数据(fixture中介绍过了)
(2)pytest.mark.parametrize()
对测试方法参数化
@pytest.mark.parametrize('a, b, c', [(1,2,3), (4,5,9), ('1', '2', '12')])
def test_add(a, b, c):
print(f'\na,b,c的值:{a},{b},{c}')
assert add(a, b) == c
对测试类参数化
@pytest.mark.parametrize('a, b, c', [(1,2,3), (4,5,9)])
class TestAdd():
def test_add1(self, a, b, c):
assert add(a, b) == c
def test_add2(self, a, b, c):
assert add(a, b) == c
(3)yaml文件驱动
安装yaml的包:PyYAML,举例我写的case把,不做demo了
(4)id和ids使用
from datetime import datetime, timedelta
import pytest
testdata = [
(datetime(2001, 12, 12), datetime(2001, 12, 11), timedelta(1)),
(datetime(2001, 12, 11), datetime(2001, 12, 12), timedelta(-1)),
]
# 没有ids的情况,运行展示不美观
@pytest.mark.parametrize("a,b,expected", testdata)
def test_timedistance_v0(a, b, expected):
diff = a - b
assert diff == expected
@pytest.mark.parametrize("a,b,expected", testdata, ids=["forward", "backward"])
def test_timedistance_v1(a, b, expected):
diff = a - b
assert diff == expected
# ids也可以是一个构造id的函数,这里的val就是testdata
def idfn(val):
if isinstance(val, (datetime,)):
return val.strftime("%Y%m%d")
@pytest.mark.parametrize("a,b,expected", testdata, ids=idfn)
def test_timedistance_v2(a, b, expected):
diff = a - b
@pytest.mark.parametrize(
"a,b,expected",
[
pytest.param(
datetime(2001, 12, 12), datetime(2001, 12, 11), timedelta(1), id="forward"
),
pytest.param(
datetime(2001, 12, 11), datetime(2001, 12, 12), timedelta(-1), id="backward"
),
],
)
def test_timedistance_v3(a, b, expected):
diff = a - b
assert diff == expected
assert diff == expected
(5)pytest.param使用
上面有试用了,需要提及的就是,可以对单个case做标记,我目前是用了标记,其他牛逼功能还没有使用
5、marks
感觉没啥说的呢,标记skip、skipif、xfail等
6、pytest处理mock
使用轻量的web服务框架,flask或者tornado
tornado可以同时监听多个端口,有队列处理等优点适合做为mock服务来使用,简单说下我如何使用的
# -*- coding:utf-8 -*-
import json
import os
import time
import traceback
from multiprocessing import Process
import tornado.ioloop
import tornado.web
class MockHandler(tornado.web.RequestHandler):
def initialize(self, res_message, res_status):
self.res_message = res_message
self.res_status = res_status
@tornado.gen.coroutine
def get(self):
uri = self.request.uri
# 对getTaskCallBackInfo的mock做点特殊处理,在日志文件中找对应内容返回
if uri.startswith("/getTaskCallBackInfo"):
try:
taskid = uri.split("taskid=")[1]
# pathUtil的方法可以看做是拼接日志文件的路径方法
with open(PathUtil.get_log_path(log_type="lixian", log_file=taskid), 'r') as f:
res = json.load(f)
self.write(res)
except Exception as e:
print(e)
self.write({'errorCode': -1, 'message': '没有找到对应回调文件'})
else:
self.write(self.res_message)
self.set_status(self.res_status)
self.finish()
@tornado.gen.coroutine
@lixian_backlog
@write_log
def post(self):
self.set_status(self.res_status)
self.write(self.res_message)
self.finish()
class MockDemo:
"""
创建一个mock的需要知道的4个字段不要少
:return:
"""
# 如果没有初始化端口,uri等给一个默认的
def __init__(self, **kwargs):
if not kwargs.get("in_params"):
in_params = [
{
"port_param": 5053,
"mock_uris": [
{
"uri_param": "/",
"res_status": 200,
"res_message": '{"code":200}'
}
]
}
]
else:
in_params = kwargs.get("in_params")
self.in_params = in_params
self.address = "127.0.0.1"
self.defult_port = 10081
def start_server(self):
for i in range(len(self.in_params)):
# 拿不到port就是需要默认一个port
try:
port = int(self.in_params[i]["port_param"])
except Exception as e:
print(e)
port = self.defult_port
try:
temp = self.in_params[i]["mock_uris"]
mock_list = [(temp[index]["uri_param"], MockHandler,
{"res_message": temp[index]["res_message"], "res_status": temp[index]["res_status"]}) for
index in range(len(temp))]
mock_application = tornado.web.Application(mock_list)
mock_application.listen(port, self.address)
print("tornado start:ip({}),port({}),params({})".format(self.address, port, temp))
except Exception as e:
info = traceback.print_exc()
raise Exception("tornado web start failed: %s \n %s" % (e, info))
i_o_instance = tornado.ioloop.IOLoop.instance()
i_o_instance.start()
def __call__(self):
self.start_server()
def jifei_trade_mock():
"""业务需要的特定mock"""
jifei_mock_param = [
{
"port_param": "8280",
"mock_uris": [
{
"uri_param": "/tradewer/saveOrdwwer",
"res_status": 200,
"res_message": {"status": 200, "message": None}
},
{
"uri_param": "/tradewer/notify",
"res_status": 200,
"res_message": {"status": 200, "message": "成功", "errorCode": None}
},
]
}
]
return MockDemo(in_params=jifei_mock_param)
if __name__ == '__main__':
# main里的调用方式就可以放在fixture中setup使用了
hl = Process(target=jifei_trade_mock())
hl.start()
print('module name:', __name__)
print('parent process:', os.getppid())
print('process id:', os.getpid())
for i in range(30):
print("-----", time.time())
time.sleep(2)
hl.terminate()
7、日志处理
我使用了loguru
# -*- coding:utf-8 -*-
"""
Created on 2020/4/29
@author: hanfang1
description:日志基础类,日志用装饰器方式调用
"""
from loguru import logger
from common.path_util import PathUtil
class LogUtil:
"""
日志处理
"""
def __init__(self, log_type="", log_file="running", ts_flag=True):
"""
log_type:根据不同的日志类型区分路径
:param log_type:
"""
# 这里是日志的输出路径方法,自行写一个就行了
self.path = PathUtil.get_log_path(log_type=log_type, log_file=log_file, ts_flag=ts_flag)
self.log_type = log_type
logger.add(self.path,
filter=lambda x: log_type in x['message'],
format="{time:YYYY-MM-DD HH:mm:ss} | [" + log_type + "] | {level} | {message}",
rotation="500MB",
backtrace=True,
diagnose=True,
encoding="utf-8",
enqueue=True,
retention="10 days",
)
def info(self, msg):
return logger.info("[" + self.log_type + "] " + msg)
def debug(self, msg):
return logger.debug("[" + self.log_type + "] " + msg)
def warning(self, msg):
return logger.warning("[" + self.log_type + "] " + msg)
def error(self, msg):
return logger.error("[" + self.log_type + "] " + msg)
# 用这样方式保证不同的类型日志的单例模式
RunLog = LogUtil(log_type="run", log_file="running", ts_flag=True)
LixianLog = LogUtil(log_type="lixian", log_file="running", ts_flag=True)
ZhiboLog = LogUtil(log_type="zhibo", log_file="running", ts_flag=True)
MockLog = LogUtil(log_type="mock", log_file="running", ts_flag=True)
if __name__ == '__main__':
RunLog.info("111111111111111") # 日志只会打印到running.log日志中
LixianLog.info("22222222222222") # 日志只会打印到lixian.log日志中
ZhiboLog.info("3333333333333333")
MockLog.info("44444444444444444")
8、mysql和redis的封装
# -*- coding:utf-8 -*-
import pymysql.cursors
# conf就是mysql的配置项
from conf import mysql
def operator_status(func):
"""
获取操作的状态,格式化
"""
def gen_status(*args, **kwargs):
error, result = None, None
try:
result = func(*args, **kwargs)
except Exception as e:
error = str(e)
return {'result': result, 'error': error}
return gen_status
class HandleMysql:
def __init__(self):
# 配置从config.py获取
self.dbInfo = {
"host": mysql.dbhost,
"port": mysql.dbport,
"user": mysql.dbuser,
"password": mysql.dbpswd,
"db": mysql.dbname
}
print("db read Success:%s" % self.dbInfo)
def __enter__(self):
try:
self.__conn = pymysql.connect(**self.dbInfo)
print("db connect Success")
return self
except Exception as e:
print("db connect Failur: {}".format(e))
def __exit__(self, exc_type, exc_val, exc_tb):
"""关闭数据库连接"""
try:
self.__conn.close()
print("db close Success")
except Exception as e:
print("db close Failur: {}".format(e))
@operator_status
def insert(self, table, val_obj):
"""插入数据到数据表"""
sql_top = 'INSERT INTO ' + table + ' ('
sql_tail = ') VALUES ('
try:
for key, val in val_obj.items():
sql_top += key + ','
sql_tail += '"' + str(val) + '"' + ','
sql = sql_top[:-1] + sql_tail[:-1] + ')'
# print(sql)
with self.__conn.cursor() as cursor:
cursor.execute(sql)
self.__conn.commit()
return self.__conn.insert_id()
except pymysql.Error:
self.__conn.rollback()
return False
@operator_status
def update(self, table, val_obj, range_str=""):
"""更新数据到数据表"""
sql = 'UPDATE ' + table + ' SET '
try:
for key, val in val_obj.items():
sql += key + '=' + '"' + val + '"' + ','
if range_str:
sql = sql[:-1] + ' WHERE ' + range_str
with self.__conn.cursor() as cursor:
cursor.execute(sql)
self.__conn.commit()
return cursor.rowcount
except pymysql.Error:
self.__conn.rollback()
return False
@operator_status
def delete(self, table, range_str=""):
"""删除数据在数据表中"""
if range_str:
sql = 'DELETE FROM ' + table + ' WHERE ' + range_str
else:
sql = 'DELETE FROM ' + table
try:
with self.__conn.cursor() as cursor:
cursor.execute(sql)
self.__conn.commit()
return cursor.rowcount
except pymysql.Error:
self.__conn.rollback()
return False
@operator_status
def select_one(self, table, factor_str, field='*'):
"""查询唯一数据在数据表中"""
sql = 'SELECT ' + field + ' FROM ' + table
if factor_str:
sql += ' WHERE ' + factor_str
try:
with self.__conn.cursor() as cursor:
cursor.execute(sql)
self.__conn.commit()
return cursor.fetchall()[0]
except pymysql.Error:
return False
@operator_status
def select_more(self, table, range_str, field='*'):
"""查询多条数据在数据表中"""
sql = 'SELECT ' + field + ' FROM ' + table
if range_str:
sql += ' WHERE ' + range_str
try:
with self.__conn.cursor() as cursor:
cursor.execute(sql)
self.__conn.commit()
return cursor.fetchall()
except pymysql.Error:
return False
@operator_status
def exec_sql_str(self, sql_str):
"""拼接的sql字符串执行"""
try:
with self.__conn.cursor() as cursor:
cursor.execute(sql_str)
self.__conn.commit()
return cursor.fetchall()
except pymysql.Error:
return False
if __name__ == "__main__":
with HandleMysql() as db:
print(db.exec_sql_str("select * from offline_preset limit 1;"))
# -*- coding:utf-8 -*-
import redis
# conf就是redis的配置项
from conf import redis_conf
def operator_status(func):
"""
获取操作的状态,格式化
"""
def gen_status(*args, **kwargs):
error, result = None, None
try:
result = func(*args, **kwargs)
except Exception as e:
error = str(e)
return {'result': result, 'error': error}
return gen_status
class HandleRedis(object):
def __init__(self):
if not hasattr(HandleRedis, 'pool'):
HandleRedis.create_pool()
self._connection = redis.Redis(connection_pool=HandleRedis.pool)
@staticmethod
def create_pool():
redis_info = {
"host": redis_conf.redisHost,
"port": redis_conf.redisPort,
"password": redis_conf.redisPassword,
"decode_responses": True,
"retry_on_timeout": 3,
"max_connections": 1024
}
print("redis read Success:%s" % redis_info)
HandleRedis.pool = redis.ConnectionPool(**redis_info)
@operator_status
def set_data(self, key, value):
"""set data with (key, value)
"""
return self._connection.set(key, value)
@operator_status
def get_data(self, key):
"""get data by key
"""
return self._connection.get(key)
@operator_status
def del_data(self, key):
"""delete cache by key
"""
return self._connection.delete(key)
if __name__ == '__main__':
print(HandleRedis().set_data('Testkey', "Simple Test"))
print(HandleRedis().get_data('Testkey'))
print(HandleRedis().del_data('Testkey'))
print(HandleRedis().get_data('Testkey'))
9、ssh操作远程机器
# -*- coding:utf-8 -*-
"""
description:ssh登录其他机器获取日志等操作
"""
import platform
if platform.system().lower() == 'linux':
import paramiko
class Paramiko:
def __init__(self, ip="127.0.0.1", user="root", password=None, port=22):
self.ssh = paramiko.SSHClient()
self.user = user
self.ip = ip
self.password = password
self.port = port
# 登录要测试的主机(linux主机)
def ssh_login(self):
self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
self.ssh.connect(self.ip, self.port, self.user, self.password)
def ssh_logout(self):
self.ssh.close()
# 执行linux命令
def exec_commands(self, cmd):
self.ssh_login()
stdin, stdout, stderr = self.ssh.exec_command(cmd)
results = stdout.read().decode("utf-8")
self.ssh_logout()
return results
# 批量顺序执行
def CMD(self, cmd_list=None):
if cmd_list is None:
cmd_list = []
self.ssh_login()
CmdDict = {}
for c in cmd_list:
CmdMes = self.exec_commands(c)
print(CmdMes)
CmdDict[c] = CmdMes
self.ssh_logout()
return CmdDict
if __name__ == "__main__":
p = Paramiko("10.111.6.231", "hanfang1")
res = p.exec_commands("grep -h 73006f75e886025cc351229227dfeb90 /home/hanfang1/log/money*")
print(res)
10、分布式执行用例
重点:pytest-xdist没有内置的支持来确保会话范围的夹具仅执行一次
解决:锁方式实现(官网示例)pytest-xdist · PyPI
在conftest.py
@pytest.fixture(scope="session")
def session_data(tmp_path_factory, worker_id):
if worker_id == "master":
LixianLog.info("master--start")
else:
root_tmp_dir = tmp_path_factory.getbasetemp().parent
fn = root_tmp_dir / "data.json"
with FileLock(str(fn) + ".lock"):
if not fn.is_file():
data = "slave--start"
LixianLog.info(data)
fn.write_text(data)
yield
if worker_id == "master":
LixianLog.info("master--end")
else:
root_tmp_dir1 = tmp_path_factory.getbasetemp().parent
fn1 = root_tmp_dir1 / "data1.json"
with FileLock(str(fn1) + ".lock"):
if not fn1.is_file():
data1 = "slave--end"
LixianLog.info(data1)
fn1.write_text(data1)
测试方法:
# -*- coding:utf-8 -*-
import pytest
from common.log_util import RunLog
@pytest.mark.usefixtures("session_data")
@pytest.mark.parametrize("fff", range(10))
def test_2(fff):
RunLog.info(str(fff))
所以官网的方式只能解决前置的夹具啊
思路:修改一个进程为管理,控制整个运行过程(研究中)
11、用例重跑
https://cloud.tencent.com/developer/article/1189354 http://t.zoukankan.com/crystal1126-p-12580953.html https://blog.csdn.net/weixin_53519100/article/details/113893416 https://www.cnblogs.com/crdym/p/14967837.html pytest -s --html=D:\report2.html --lf 测试文件 重跑失败的用例 pip install pytest-rerunfailures python -m pytest -v -n auto --reruns 2 test_offline_task.py --alluredir=./report