【接口自动化】06-轻量级框架封装(完):日志监控、数据驱动及断言 一、前言上一期我们知道了统一接口请求的轻量级框架的封装那么我们轻量级框架封装还未结束我们本期继续介绍轻量级框架封装即日志监控、数据驱动以及断言等等二、轻量级框架封装日志监控封装日志在开发中是很重要的同时在自动化测试中也很重要因为日志它可以记录程序在运行的时候发生的一些细节信息从而帮助我们了解程序的运行时过程进一步帮助我们判断排除程序出现的问题。在Python中进行日志记录有两种内置标准库logging【常用】第三方库loggru我们会选择内置的标准库logging因为他能够和pytest很好的集成。1、在pytest中启用日志记录如何启用呢将下述的配置内容放入到我们的pytest.ini配置文件中# 日志相关配置 # 日志文件存储路径相对项目根目录日志会输出到logs文件夹下的frame.log文件log_file./logs/frame.log# 日志输出级别低于该级别的日志不会写入文件# 级别优先级从小到大DEBUG调试 INFO信息 WARNING警告 ERROR错误 CRITICAL严重故障log_file_levelINFO# 日志打印格式模板# %(asctime)s日志打印的时间# %(levelname)s日志级别名称INFO/WARNING等# %(message)s自定义输出的日志内容log_file_format%(asctime)s %(levelname)s %(message)s解释日志格式字符串占位符标识作用说明%(asctime)s打印日志记录的时间%(levelname)s打印日志级别文字DEBUG/INFO/WARNING等%(message)s打印自定义输出的日志/报错信息主体%(levelno)s打印日志级别对应的数字编号%(pathname)s打印当前执行文件完整路径等价于sys.argv[0]%(filename)s打印当前执行的程序文件名%(funcName)s打印输出日志所在的函数名%(lineno)d打印打印日志代码所在的行号%(thread)d打印当前线程ID数字%(threadName)s打印当前线程名称%(process)d打印当前进程ID数字上述我们只是将日志给启用但是日志里面的内容我们需要一个日志对象来进行封装。2、创建日志对象logger使用logging.getLogger(名字)来创建# 导入日志模块的包importlogging# 创建日志对象loggerlogging.getLogger(__name__)# 使用__name__可以自动生成名字loggerlogging.getLogger(my_logger)# 自己指定名字3、使用logger来记录日志用法如下所示# 导入日志模块的包importlogging# 创建日志对象loggerlogging.getLogger(__name__)# 使用__name__可以自动生成名字# 开始记录日志logger.debug()# 记录一个debug级别的日志logger.info()# 记录一个info级别的日志logger.error()# 记录一个error级别的日志注意日志的内容必须是字符串如果做到字符串我们使用一个占位符然后将数据填充进去最终我们的数据也变为字符串了# 这个输出格式我们在Python语法基础中也有介绍过的data{1,2,3}logger.error(f数据的内容{data})4、给接口打上日志记录上一期我们对接口请求进行了轻量级的统一封装importrequestsclassRequestUtil:# 首先将session做为类对象供给大家共用sessrequests.session()# 创建一个参数 用于共用公共参数params{application:app,application_client_type:android,}# 创建一个方法构造请求defsend_request(self,**kwargs):# 使用不定长的关键字参数# 完善动态参数ifparamsinkwargs:# 不存在就添加存在就是修改kwargs[params].update(self.params)# 发送请求respself.sess.request(**kwargs)returnresp那么现在我们就需要给这个所封装的接口请求打上我们的日志便于我们的观察我们需要打上哪一些日志呢前置# 导入日志框架importlogging# 创建出日志对象loggerlogging.getLogger(__name__)1、请求前# 1、打印请求入参日志请求前 logger.info(f【发起HTTP请求】原始请求参数:{kwargs})2、记录请求# 2、打印请求日志logger.info(f【请求参数合并完成】最终请求params:{kwargs.get(params)})3、记录响应# 3、打印响应日志请求后 logger.info(f【接口响应结果】状态码{resp.status_code}, 响应头{dict(resp.headers)})try:# 优先打印json格式响应体resp_jsonresp.json()# 因为只有你的响应内容是json字符串那么才可以使用json()方法logger.info(f【响应JSON数据】{resp_json})exceptException:# 非json接口打印原始文本logger.info(f【响应文本数据】{resp.text})完整代码importrequests# 导入日志框架importlogging# 创建出日志对象loggerlogging.getLogger(__name__)classRequestUtil:# 首先将session做为类对象供给大家共用sessrequests.session()# 创建一个参数 用于共用公共参数params{application:app,application_client_type:android,}# 创建一个方法构造请求defsend_request(self,**kwargs):# 使用不定长的关键字参数# 1、打印请求入参日志请求前logger.info(f【发起HTTP请求】原始请求参数{kwargs})# 完善动态参数ifparamsinkwargs:# 不存在就添加存在就是修改kwargs[params].update(self.params)# 2、打印请求日志logger.info(f【请求参数合并完成】最终请求params:{kwargs.get(params)})# 发送请求respself.sess.request(**kwargs)# 3、打印响应日志请求后 logger.info(f【接口响应结果】状态码{resp.status_code}, 响应头{dict(resp.headers)})try:# 优先打印json格式响应体resp_jsonresp.json()# 因为只有你的响应内容是json字符串那么才可以使用json()方法logger.info(f【响应JSON数据】{resp_json})exceptException:# 非json接口打印原始文本logger.info(f【响应文本数据】{resp.text})returnresp结果我现在day05这个目录下运行那么我们就会在当前目录下创建一个logs文件夹打开里面有一个frame.log文件梳理逻辑上述完成日志的封装之后我们接下来就实现数据驱动测试的封装三、轻量级框架封装数据驱动封装我们需要实现使用数据文件去驱动测试用例也就是代码和数据文件的分离。具体的实现思路设计YAML数据文件格式加载YAML完成数据驱动测试1、设计YAML数据文件格式数据文件名的格式需要和测试用例名的格式一致唯一的区别就是测试用例名的test_改为ddt_什么意思呢比如你的一个测试用用例是test_tan那么现在你这个测试用例所对应的YMAL文件命名为ddt_tan这样一来你一看都是tan那么你就可以知道哪一个测试用例对应了哪一个数据驱动文件。如下那么我们在数据驱动文件中写什么呢上述我们的数据文件中定制的有两个内容一个是Allure报告的定制另一个是接口请求的内容-feature:首页模块story:首页接口title:首页接口请求成功request:method:geturl:http://116.62.63.211/shop/api.phpparams:s:index/index2、加载YAML完成数据驱动测试怎么去读取数据呢就看下面的图解你要去读取数据那么你就得让一个东西来帮助我们这个东西其实就是一个标记pytest.mark.parametrizepytest.mark.parametrize(变量名,打开文件并读取)def用例():代码如下importallureimportpytestfrom commons.request_utilimportRequestUtilfrom commons.yml_utilimportYmlUtilpytest.mark.parametrize(case_info,YmlUtil(rE:\python_project\basic-python-grammar-learning\api_framwor\day06\ddt_tan.yaml).read())deftest_tan(case_info):# 读取数据文件中的内容 featurecase_info[feature]storycase_info[story]titlecase_info[title]requestcase_info[request]# 此处定制Allure报告 allure.dynamic.feature(feature)allure.dynamic.story(story)allure.dynamic.title(title)respRequestUtil().send_request(**request)# 请求完毕之后他会有返回值那么此时我们就可以断言assertresp.json()[code]0结果打开YAML文件中的文件地址可以动态获取Path(__file__)获取当前文件的路径str(Path(__file__).parent)得到当前文件所在的目录变动如下图所示代码importallureimportpytestfrom pathlibimportPathfrom commons.request_utilimportRequestUtilfrom commons.yml_utilimportYmlUtilcase_pathstr(Path(__file__).parent)pytest.mark.parametrize(case_info,YmlUtil(case_path/ddt_tan.yaml).read())deftest_tan(case_info):# 读取数据文件中的内容 featurecase_info[feature]storycase_info[story]titlecase_info[title]requestcase_info[request]# 此处定制Allure报告 allure.dynamic.feature(feature)allure.dynamic.story(story)allure.dynamic.title(title)respRequestUtil().send_request(**request)# 请求完毕之后他会有返回值那么此时我们就可以断言assertresp.json()[code]0结果四、轻量级框架封装JSON断言的封装我们会使用一种工具通过这个工具来对JSON对象进行一些深度的比较1、先安装对应的模块pip install deepdiff2、简单演示怎么使用我们假设有两个字典(JSON在Python中叫做字典)dict1{a:1,b:2,c:{d:3}}dict2{a:1,b:4,e:5}那么有以下的代码fromdeepdiffimportDeepDiff# 从deepdiff(从小写中)中导入DeepDiff大写dict1{a:1,b:2,c:{d:3}}dict2{a:1,b:4,e:5}diffDeepDiff(dict1,dict2)assertdiff{},diff# 如果是一样的话那么就断言成功了 后面的diff是为了打印详细信息3、deepdiff的特点1深度比较递归的比较有嵌套的数据结构2检测多种差异添加或者删除内容、值的变化、类型的变化3相等断言当两个数据一致(没有差异)时候返回的是空值可以使用结果{}进行断言。五、案例实战本实战中使用的接口文档是我们之前用的微信公众平台的接口文档老铁们可参考Jmeter接口实战1、基本样式创建test_weixin.py在里面写两个方法一个是用来获取鉴权码一个是用来上传文件的fromcommons.request_utilimportRequestUtil# 导入请求工具类deftest_get_token():passdeftest_upload():pass2、构造获取鉴权码的请求要素然后发送请求进行断言提取鉴权码get_ver{}# 全局变量deftest_get_token():methodGETurlhttps://api.weixin.qq.com/cgi-bin/tokenparams{grant_type:client_credential,appid:wx9335efa56e8cbb64,secret:0750c9411423c510fd0c2a1acdc32aa4}# 发送请求先实例化再掉里面的方法发送请求resRequestUtil().send_request(methodmethod,urlurl,paramsparams)# 断言expires_inres.json()[expires_in]assertexpires_in7200# 提取鉴权码access_tokenres.json()[access_token]get_ver[access_token]access_token3、我想只执行这个方法所以我就加一个标记pytest.mark.weixin此时需要在我们的配置文件中加入一个标记测试通过4、接下来就完成上传文件的这个接口那么将上述的获取鉴权码的接口复制一下进行修改即可因为大部分都是相似的得到的代码如下fromcommons.request_utilimportRequestUtil# 导入请求工具类importpytest get_ver{}# 全局变量pytest.mark.weixindeftest_get_token():methodGETurlhttps://api.weixin.qq.com/cgi-bin/tokenparams{grant_type:client_credential,appid:wx9335efa56e8cbb64,secret:0750c9411423c510fd0c2a1acdc32aa4}# 发送请求先实例化再掉里面的方法发送请求resRequestUtil().send_request(methodmethod,urlurl,paramsparams)# 断言expires_inres.json()[expires_in]assertexpires_in7200# 提取鉴权码access_tokenres.json()[access_token]get_ver[access_token]access_tokenpytest.mark.weixindeftest_upload():methodPOSTurlhttps://api.weixin.qq.com/cgi-bin/media/uploadimgparams{access_token:get_ver[access_token],}# 参数是文件files{media:open(rE:\python_project\basic-python-grammar-learning\api_framwor\data\弹简特头像.jpg,rb)# 文件的上传必须使用二进制的方式此处的路径我们可以使用之前的相对路径}# 发送请求先实例化再掉里面的方法发送请求resRequestUtil().send_request(methodmethod,urlurl,paramsparams,filesfiles)# 断言urlres.json()[url]asserthttpinurl结果通过六、总结接口自动化轻量级封装到目前我们轻量级的封装就暂时告一段落了我们接下来就总结一下我们的所学1、接口自动化测试的流程理解需求设计用例根据用例需求查找接口文档根据接口文档编写测试代码测试代码中包含请求的参数和预期的结果2、接口自动化测试中接口关联和数据驱动怎么做的接口关联先通过正则(字符串)或者JSONPATH(JSON)从上一个接口中提取数据把数据保存到全局变量中在下一个接口中使用变量即可完成关联数据驱动测试分析接口参数中会发生变化的内容不会变的东西不要用数据驱动将变化的部分写入yaml文件中因为YAML文件更便于阅读然后我们再编写代码从YAML文件中读取数据使用pytest的参数化标记进行数据驱动3、你们断言是怎么做的文本的响应类型判断包含或者相等实际上就是字符串之间的比较JSON的响应类型方法一使用JSONPATH提取目标数据对目标数据进行断言方法二DeepDiff对响应的内容进行断言4、你们的接口测试框架是怎么封装的首先我们要对技术栈有一个了解pytestrequestjsonpathallurelogging使用pytest作为一个基础的框架集成一些第三方插件对request进行一些统一请求的封装目的是进行公共参数的管理和日志的记录为了更方便的从json响应中提取数据进行管理和断言封装jsonpath利用allure进行测试报告的定制用例分组、自定义logo等等

本月热点