zoukankan      html  css  js  c++  java
  • Python+Pytest+Allure+Git+Jenkins接口自动化框架(纯干货)

     一、接口基础

    接口测试是对系统和组件之间的接口进行测试,主要是效验数据的交换,传递和控制管理过程,以及相互逻辑依赖关系。其中接口协议分为HTTP,RPC,Webservice,Dubbo,RESTful等类型。

    接口测试流程

    1、需求评审,熟悉业务和需求

    2、开发提供接口文档

    3、编写接口测试用例

    4、用例评审

    5、提测后开始测试

    6、提交测试报告

    两种常见的 HTTP 请求方法:GET 和 POST

    二、项目说明

    本框架是一套基于Python+Pytest+Requests+Allure+Jenkins而设计的数据驱动接口自动化测试的框架。

    技术栈

    Python、Pytest、Requests、Pactverity、Excel、Json、Mysql、Allure、Logbook、Git、Jenkins

    三、接口测试框架结构图

    四、项目功能

    Python+Pytest+Allure+Jenkins接口自动化框架,实现Excel或Json维护测试用例,支持数据库操作,利用封装的请求基类调取相应的测试用例接口,获取配置文件中的环境地址与环境变量,

    结合Pytest进行单元测试,使用LogBook进行记录日志,并生成allure测试报告,最后进行Jenkins集成项目实现集成部署,并发送测试报告邮件。

    五、代码设计与功能说明

    1、工具类封装

    1.1、log日志

    项目中的log日志是logbook进行日志记录的,方便测试开发调试时进行排错纠正或修复优化。日志可选择是否打印在屏幕上即运行时是否在终端输出打印。日志格式输出可调整。

    handle_log.py部分源码

    def log_type(record, handler):
     2     log = "[{date}] [{level}] [{filename}] [{func_name}] [{lineno}] {msg}".format(
     3         date=record.time,  # 日志时间
     4         level=record.level_name,  # 日志等级
     5         filename=os.path.split(record.filename)[-1],  # 文件名
     6         func_name=record.func_name,  # 函数名
     7         lineno=record.lineno,  # 行号
     8         msg=record.message  # 日志内容
     9     )
    10     return log
    11 # 日志存放路径
    12 LOG_DIR = BasePath + '/log'
    13 print(LOG_DIR)
    14 if not os.path.exists(LOG_DIR):
    15     os.makedirs(LOG_DIR)
    16 # 日志打印到屏幕
    17 log_std = ColorizedStderrHandler(bubble=True)
    18 log_std.formatter = log_type
    19 # 日志打印到文件
    20 log_file = TimedRotatingFileHandler(
    21     os.path.join(LOG_DIR, '%s.log' % 'log'), date_format='%Y-%m-%d', bubble=True, encoding='utf-8')
    22 log_file.formatter = log_type
    23 
    24 # 脚本日志
    25 run_log = Logger("global_log")
    26 def init_logger():
    27     logbook.set_datetime_format("local")
    28     run_log.handlers = []
    29     run_log.handlers.append(log_file)
    30     run_log.handlers.append(log_std)
    31     return ""
    

    打印在终端的日志,如下图所示。

    同时运行项目后,会在项目文件log中自动生成一个以当天日期命名的log文件。点击log日志文件可查看日志详情即项目运行时所记录的日志或报错日志。如下图所示。

    1.2、配置文件

    项目中涉及到一些配置文件如username、password或环境变量时,我们可通过配置文件来获取配置值。通过配置文件中key与value的定义来确定获取配置文件的值。

    handle_init.py部分源码

     class HandleInit:
     2     # 读取配置文件
     3     def load_ini(self):
     4         file_path = BasePath + "/config/config.ini"
     5         cf = configparser.ConfigParser()
     6         cf.read(file_path, encoding='UTF-8')
     7         return cf
     8 
     9     # 获取ini里面对应key的value
    10     def get_value(self, key, node=None):
    11         if node == None:
    12             node = 'Test'
    13         cf = self.load_ini()
    14         try:
    15             data = cf.get(node, key)
    16             logger.info('获取配置文件的值,node:{},key:{}, data:{}'.format(node, key, data))
    17         except Exception:
    18             logger.exception('没有获取到对应的值,node:{},key:{}'.format(node, key))
    19             data = None
    20         return data
    

    获取配置文件中的值日志如下图所示。

    1.3、Api接口请求

    获取相关测试用例及接口用例配置,记录请求相关参数的日志,定义Allure测试报告的步骤。

    handle_apirequest.py部分代码

    class ApiRequest:
     2     def api_request(self, base_url, test_case_data, case_data):
     3         get_name = None
     4         get_url = None
     5         get_method = None
     6         get_headers = None
     7         get_cookies = None
     8         get_case_name = None
     9         get_case_params = None
    10         response_data = None
    11         try:
    12             get_name = test_case_data['config']['name']
    13             get_url = base_url + test_case_data['config']['url']
    14             get_method = test_case_data['config']['method']
    15             get_headers = test_case_data['config']['headers']
    16             get_cookies = test_case_data['config']['cookies']
    17         except Exception as e:
    18             logger.exception('获取用例基本信息失败,{}'.format(e))
    19         try:
    20             get_case_name = case_data['name']
    21             get_case_params = case_data['params']
    22         except Exception as e:
    23             logger.exception('获取测试用例信息失败,{}'.format(e))
    24         with allure.step("请求接口:%s,请求地址:%s,请求方法:%s,请求头:%s,请求Cookies:%s" % (
    25                 get_name, get_url, get_method, get_headers, get_cookies)):
    26             allure.attach("接口用例描述:", "{0}".format(get_case_name))
    27             allure.attach("接口用例请求参数:", "{0}".format(get_case_params))
    28         logger.info(
    29             '请求接口名:%r,请求地址:%r,请求方法:%r,请求头:%r,请求Cookies:%r' % (get_name, get_url, get_method, get_headers, get_cookies))
    30         logger.info('请求接口名:%r,请求接口用例名:%r,接口用例请求参数:%r' % (get_name, get_case_name, get_case_params))
    31         try:
    32             response_data = baseRequest.run_main(get_method, get_url, get_case_params, get_headers)
    33         except Exception as e:
    34             logger.exception('用例请求返回失败,{}'.format(e))
    35         logger.info('请求接口名:%r,请求接口用例名:%r,返回参数:%r' % (get_name, get_case_name, response_data.json()))
    36         return response_data
    

    1.4、Excel数据处理

    1.4.1、Excel测试用例

    测试用例中维护在Excel文件中,类中定义如何获取Excel中的相关数据(如获取某个单元格的内容,获取单元格的行数,以及将数据写入Excel中等操作)。

    handle_exceldata.py部分源码

    class OperationExcel:
     2     def __init__(self, file_name=None, sheet_id=None):
     3         if file_name:
     4             self.file_name = file_name
     5             self.sheet_id = sheet_id
     6         else:
     7             self.file_name = ''
     8             self.sheet_id = 0
     9         self.data = self.get_data()
    10 
    11     # 获取sheets的内容
    12     def get_data(self):
    13         data = xlrd.open_workbook(self.file_name)
    14         tables = data.sheets()[self.sheet_id]
    15         return tables
    16 
    17     # 获取单元格的行数
    18     def get_lines(self):
    19         tables = self.data
    20         return tables.nrows
    21 
    22     # 获取某一个单元格的内容
    23     def get_cell_value(self, row, col):
    24         return self.data.cell_value(row, col)
    

    1.5、Json数据处理

    1.5.1、Json测试用例

    {
     2     "config":{
     3         "name":"post接口名",
     4         "url":"/langdetect",
     5         "method":"POST",
     6         "headers":{
     7             "Content-Type":"application/json"
     8         },
     9         "cookies":{
    10 
    11         }
    12     },
    13     "testcase":[
    14         {
    15             "name":"测试用例1",
    16             "params":{
    17                 "query":"测试"
    18             },
    19             "validate":[
    20                 {
    21                     "check":"status_code",
    22                     "comparator":"eq",
    23                     "expect":"200"
    24                 }
    25             ]
    26         },
    27         {
    28             "name":"测试用例2",
    29             "params":{
    30                 "query":"python"
    31             },
    32             "validate":[
    33                 {
    34                     "check":"msg",
    35                     "comparator":"eq",
    36                     "expect":"success"
    37                 }
    38             ]
    39         }
    40     ]
    41 }
    

    1.5.2、Json用例处理

    获取Json文件中里具体字段的值。

    handle.json.py部分源码

     1 class HandleJson:
     2     # 读取json文件
     3     def load_json(self, file_name):
     4         if file_name == None:
     5             file_path = ""
     6         else:
     7             file_path = file_name
     8         try:
     9             with open(file_path, encoding='UTF-8') as f:
    10                 data = json.load(f)
    11             return data
    12         except Exception:
    13             print("未找到json文件")
    14             return {}
    15 
    16     # 读取json文件里具体的字段值
    17     def getJson_value(self, key, file_name):
    18         if file_name == None:
    19             return ""
    20         jsonData = self.load_json(file_name)
    21         if key == None:
    22             getJsonValue = ""
    23         else:
    24             getJsonValue = jsonData.get(key)
    25         return getJsonValue
    

    2、基类封装

    2.1、请求基类封装

    接口支持Get、Post请求,调用requests请求来实现接口的调用与返回。接口参数包括,接口地址、接口请求参数、cookie参数、header参数。

    1 class BaseRequest:
     2 
     3     def send_get(self, url, data, header=None, cookie=None):
     4         """
     5         Requests发送Get请求
     6         :param url:请求地址
     7         :param data:Get请求参数
     8         :param cookie:cookie参数
     9         :param header:header参数
    10         """
    11         response = requests.get(url=url, params=data, cookies=cookie, headers=header)
    12         return response
    13 
    14     def send_post(self, url, data, header=None, cookie=None):
    15         """
    16         Requests发送Post请求
    17         :param url:请求地址
    18         :param data:Post请求参数
    19         :param data:Post请求参数
    20         :param cookie:cookie参数
    21         :param header:header参数
    22         """
    23         response = requests.post(url=url, json=data, cookies=cookie, headers=header)
    24         return response
    25 
    26         # 主函数调用
    27 
    28     def run_main(self, method, url, data, header, cookie=None):
    29         try:
    30             result = ''
    31             if method.upper() == 'GET':
    32                 result = self.send_get(url, data, header, cookie)
    33             elif method.upper() == 'POST':
    34                 result = self.send_post(url, data, header, cookie)
    35             return result
    36         except Exception as e:
    37             logger.exception('请求主函数调用失败:{}'.format(e))
    

    点赞关注~~加入我们,了解更多。642830685。群内免费领取最新软件测试大厂面试资料和Python自动化、接口、框架搭建学习资料!技术大牛解惑答疑,同行一起交流。

    3、接口测试用例编写

    3.1、接口测试用例

    引用Pytest来进行接口的单元测试,通过JSON中多个测试用例来做为参数化数据驱动。结合Allure制定相应接口的测试报告。在接口返回断言之前,我们先进行该接口的契约测试,

    我们采用的是Pactverity的全量契约校验测试。当契约测试通过时,我们再进行返回参数的相关校验测试。

    test_getRequestJson.py部分源码

     @allure.feature('测试GET请求模块')
     2 class TestRequestOne():
     3     @allure.title('测试标题')
     4     @allure.testcase('测试地址:https://www.imooc.com')
     5     @pytest.mark.parametrize('case_data', testCaseData['testcase'])
     6     def test_requestOne(self, case_data):
     7         try:
     8             api_response = apiRequest.api_request(baseurl, testCaseData, case_data)
     9             api_response_data = api_response.json()
    10             # pactverity——全量契约校验
    11             config_contract_format = Like({
    12                 "msg": "成功",
    13                 "result": 0,
    14                 "data": EachLike({
    15                     "word": Like("testng")
    16                 })
    17             })
    18             mPactVerify = PactVerify(config_contract_format)
    19             try:
    20                 mPactVerify.verify(api_response_data)
    21                 logger.info(
    22                     'verify_result:{},verify_info:{}'.format(mPactVerify.verify_result, mPactVerify.verify_info))
    23                 assert mPactVerify.verify_result == True
    24             except Exception:
    25                 err_msg = '契约校验错误'
    26                 logger.exception('测试用例契约校验失败,verify_result:{},verify_info:{}'.format(mPactVerify.verify_result,
    27                                                                                      mPactVerify.verify_info))
    28             try:
    29                 for case_validate in case_data['validate']:
    30                     logger.info('断言期望相关参数:check:{},comparator:{},expect:{}'.format(case_validate['check'],
    31                                                                                    case_validate['comparator'],
    32                                                                                    case_validate['expect']))
    33                     comparatorsTest.comparators_Assert(api_response, case_validate['check'],
    34                                                        case_validate['comparator'], case_validate['expect'])
    35                     logger.info('测试用例断言成功')
    36             except Exception as e:
    37                 logger.exception('测试用例断言失败')
    38         except Exception as e:
    39             logger.exception('测试用例请求失败,原因:{}'.format(e))
    

    3.2、主运行

    运用Pytest和Allure的特性,命令行运行测试用例文件夹,并生成对应的allure测试报告。

     if __name__ == "__main__":
    2     pytest.main(['-s', '-v', 'test_case/testRequest/', '-q', '--alluredir', 'reports'])
    

    4、Allure2测试报告

    当我们运行主函数时,并生成对应的测试用例报告时,我们可以看到在该文件夹中会生成对应的json文件的测试报告。将json文件的测试报告转换成html形式的。命令如下

    reports是json格式测试报告存放的目录位置,allure_reports是html测试报告文件生成的目录位置。allure命令如下。

    1 allure generate reports -o allure_result/
    

    项目根目录下的allure_reports文件,存放的是allure生成的测试报告。可看出文件下有一个HTML文件,可通过Python的编辑器Pycharm来打开该HTML文件(测试报告),

    或可通过allure命令来打开该HTML,展示HTML测试报告。如下所示。

    测试报告文件,HTML测试报告如下。

     

    allure命令打开HTML测试报告。命令如下所示。

    1 allure open allure_result/
    

    如下图所示。  

     打开生成的HTML测试报告如下图所示。

    5、Jenkins集成

    Allure+Jenkins的分享,我之前在Pytest+Allure+Jenkins的博客中已经分享过了。这块可以出门左转看看。前期的准备就不在这里重复说明了。我们就直接来上手创建Item进行相关配置。

    General中GitHub项目地址的配置,将自己项目的Git复制至项目URL处。如下图所示。

     源码管理设置。勾选Git,填写相应的项目Git地址,Git项目权限所有者,以及对应的拉取代码的分支。如下图所示。

     配置构建命令。选择“执行windows批处理命令”,用python运行主函数运行脚本,命令如下图所示。

    当我们在Jenkins里面成功安装Allure插件后,直接可以在构建后操作中配置Allure的相关配置。在Pytest+Allure+Jenkins中已经说明过的。

    Results应与项目运行时设置的Allure生成的Json格式报告的路径一致,Report path为Allure html报告结果生成文件存放的路径。

     排除万难之后,我们就可以用Jenkins来运行项目了。如下图所示。

    测试报告详情页,如下图所示。

    六、后期优化

    1、接口测试用例之间的数据依赖

    2、测试报告邮件的发送

    。。。。。。

    七、感想

    该框架是在涉及python的知识点比较多,将接口测试与契约测试结合起来。该框架是在工作之余学习多篇文章,实战上手逐步入门开始的,适合新手入门接口自动化实战练习,仅供参考学习。框架中有不少可优化点与不足点,希望大家多多提建议或想法。

    点赞关注~~加入我们,了解更多。642830685。群内免费领取最新软件测试大厂面试资料和Python自动化、接口、框架搭建学习资料!技术大牛解惑答疑,同行一起交流。

      

      

      

      

      

      

      

      

      

  • 相关阅读:
    HDU 2433 Travel (最短路,BFS,变形)
    HDU 2544 最短路 (最短路,spfa)
    HDU 2063 过山车 (最大匹配,匈牙利算法)
    HDU 1150 Machine Schedule (最小覆盖,匈牙利算法)
    290 Word Pattern 单词模式
    289 Game of Life 生命的游戏
    287 Find the Duplicate Number 寻找重复数
    283 Move Zeroes 移动零
    282 Expression Add Operators 给表达式添加运算符
    279 Perfect Squares 完美平方数
  • 原文地址:https://www.cnblogs.com/www642830685/p/13502955.html
Copyright © 2011-2022 走看看