浅谈HttpRunner 做Http接口自动化测试

眉间皱痕 提交于 2019-11-29 06:34:51

HttpRunner 框架

简介

HttpRunner 是一款面向 HTTP(S) 协议的通用测试框架,只需编写维护一份 YAML/JSON 脚本,即可实现自动化测试、性能测试、线上监控、持续集成等多种测试需求 【引用作者简述

相关链接
框架对比
框架 最新版本 开发语言 支持语言 持续集成 拓展难度 性能测试 数据分离 推广门槛 其它特性
Robot Framework 3.1.2 python python/java 不支持 支持 自带wx的GUI,可支持界面化或命令操作,可支持web UI自动化seleniumLibrary
HttpRunner 2.0 python python 支持 支持 脚本化、有完善易阅读报告输出
Jmeter 5.1.1 java java 支持 支持 更偏向于接口性能;做功能测试,用例维护管理难

HttpRunner 模块化架构


关于HttpRunner框架详情,在此不做过多介绍,本次内容主要以实战为主

HttpRunner 环境安装

因 python2.7版本已停止更新,不在维护,大部分相关开源项目与库已不再对 python2.x 版本的支持,所以此处用 Python3.6 + HttpRunner 1.5.15 搭建环境

起步:

  • pip install -r requirements.txt

requirements.txt

HttpRunner == 1.5.15 Jinja2 == 2.10 PyMySQL == 0.9.3   # 非必需安装,因个人项目中涉及到数据库操作 SQLalchemy == 1.3.4  # 非必需安装,因个人项目中涉及到数据库ORM操作 

HttpRunner环境搭建验证

  • hrun -v 【使用CLI验证】
  • pip list 【通过pip list查看】

CLI 命令 hrun用法

  • hrun --startporject projectName 创建工程
  • hrun testcase/demo.yml 运行case
  • 其它详见 hrun -h

HttpRunner 常用关键字

  • name:用例名称
  • variables :定义变量
  • extract :提取返回结果
  • validate: 结果效验
  • content 返回结果
  • eq 效验
  • setup_hooks() 钩子函数,类似于unnitest 的setUp() 执行用例前环境准备
  • teardown_hooks() 钩子函数,类似于unnitest的teardown() 用例执行后环境初始化操作

实战

具体以 当前使用的项目为例

用HttpRunner 搭建接口自动化框架概况

工程结构

Api模板注册 Basic.yml
# 登陆 login - api:     def: get_token($password, $sign, $timestamp, $userAccount)     request:         url: /test/login         method: POST         json:             password: $password             timestamp: $time_sign             userAccount: $userAccount             sign: $sign  # 用户信息获取 - api:     def: get_userInfo($sign, $timestamp, $token)     request:         url: /test/userInfo         method: POST         json:             sign: $sign             timestamp: $time_sign             token: $token  
用例编写 test.yml
 - config:      name: 验证用户信息获取接口      request:          base_url: $server          headers: $m_headers      variables:        userAccount: '15989556891'        password: ${get_pwd(123456)}        childIds: '11027897,11029010'      validate:          - eq: [status_code, 200]  - test:      name: 获取 token      api: get_token($password,$sign,$timestamp,$userAccount)   - test:      name: case 01 验证token错误时,返回是否正确      variables:        token: 'sfsd12'      api: get_userInfo($sign,$timestamp,$token)      extract:        - code: content.code        - msg: content.errorMsg      validate:        - eq: ['$code', '00600010006']        - eq: ['$msg', '无效的token']   - test:      name: case 02 验证token过期,返回是否正确      variables:        token: 616383b06cbf8ce4d392ff4523670058000050900      api: get_userInfo($sign,$timestamp,$token)      extract:        - code: content.code        - msg: content.errorMsg      validate:        - eq: ['$code', '00200010006']        - eq: ['$msg', 'token已过有效期']  - test:      name: case 03 token有效,正常获取用户信息,返回是否正确      api: get_userInfo($sign,$timestamp,$token)      extract:        - code: content.code        - data: content.data      validate:        - eq: ['$code', '000']        - eq: ['$data', '返回数据内容'] 
HttpRunner 结果报告 Report.html
报告拓展 -> 邮件报告
报告拓展 -> 钉钉机器人提醒

拓展 源码:邮件 + 钉钉

#!/usr/bin/env python # -*- coding: UTF-8 -*- # Created by Hank on 2019-07-15. import time import json from setting import SERVER_ID   class HtmlTemplate:     """     Email content  HtmlTemplate     Attributes:     """      def __init__(self, summary):         self.summary = summary      def _get_summary(self):         """         get all case run detail         :return:         """         detail = self.summary['details']         time_lst, name_lst, case_detail = [], [], []         if detail:             for i in range(len(detail)):                 for j in range(len(detail[i]['records'])):                     case_name = detail[i]['records'][j]['name']                     case_status = detail[i]['records'][j]['status']                     case_api = detail[i]['records'][j]['meta_data']['request']['url']                     if case_api != 'N/A':                         case_code = detail[i]['records'][j]['meta_data']['response']['status_code']                         res_time = detail[i]['records'][j]['meta_data']['response']['response_time_ms']                         if case_code != 200:                             res_text = json.dumps({'code': 'HTTP ' + str(case_code)})                         else:                             res_text = detail[i]['records'][j]['meta_data']['response']['text']                         case_response = [case_status, case_name, case_code, case_api, res_text]                         time_lst.append(res_time)                         name_lst.append(case_name)                         case_detail.append(case_response)             return {                 'detail': detail,                 'time_lst': time_lst,                 'name_lst': name_lst,                 'case_detail': case_detail             }      def _get_time_analy(self):         rsp_data = self._get_summary()         time_lst = rsp_data['time_lst']         name_lst = rsp_data['name_lst']         aly_time, aly_name = [], []         if time_lst:             for i in range(len(time_lst)):                 if int(time_lst[i]) >= 2000:                     aly_time.append(time_lst[i])                     aly_name.append(name_lst[i])             return {                 'aly_time': aly_time,                 'aly_name': aly_name             }      def _get_case_analy(self):         rsp_data = self._get_summary()['case_detail']         rlt = []         if rsp_data:             for i in range(len(rsp_data)):                 if rsp_data[i][0] != 'success':                     rsp_body = json.loads(rsp_data[i][4])                     rsp_data[i][3] = rsp_data[i][3][len(SERVER_ID):]                     if rsp_body['code'] == '001':                         rsp_data[i][4] = 'code:001,data与预期不一致'                     rlt.append(rsp_data[i])              return rlt      def __table_total(self):         """         case执行数统计 +  请求耗时统计 模块         :return: 返回 html模板   type(str)         """         rsp_data = self._get_summary()         time_lst = rsp_data['time_lst']         name_lst = rsp_data['name_lst']         duration = round(self.summary['time']['duration'], 3)         sum = 0         for k in range(len(time_lst)):             sum += time_lst[k]         avg_time = round(sum / len(time_lst), 3)         # 耗时最多的 api 详情         max_index = time_lst.index(max(time_lst))         max_case_name = name_lst[max_index]         # Server path         server_path = SERVER_ID[len('https://'):]         case_result = """                 <h4>Api check Report</h4>                 <th>""" + '详见附件...' + """</th> <br/>                 <td>""" + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + """</td>                 <table width="800" border="1" bordercolor="#FFFFFF" style="border-collapse:collapse;" cellspacing="1" cellpadding="4" bgcolor="#66CCFF" class="tabtop13"  font-size="16px" padding-left="15px">                     <tr>                         <td colspan="5" align="center" font-size="24"><strong>""" + 'API CheckReport' + """</strong></td>                     </tr>                     <tr>                         <td>""" + 'START AT' + """</td>                         <td colspan="4" align="center">""" + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + """</td>                     </tr>                     <tr>                         <td>""" + 'DURATION' + """</td>                         <td colspan="4" align="center">""" + str(duration) + 'seconds' + """</td>                     </tr>                     <tr>                         <td>""" + 'SERVER' + """</td>                         <td colspan="4" align="center">""" + server_path + """</td>                     </tr>                     <tr>                         <td width="160">""" + 'TOTAL' + """</td>                         <td width="160">""" + 'SUCCESS' + """</td>                         <td width="160">""" + 'FAILED' + """</td>                         <td width="160">""" + 'ERROR' + """</td>                         <td width="160">""" + 'SKIPPED' + """</td>                     </tr>                     <tr>                         <td bgcolor="#C8EDFF">""" + str(self.summary['stat']['testsRun']) + """</td>                         <td bgcolor="#C8EDFF">""" + str(self.summary['stat']['successes']) + """</td>                         <td bgcolor="#C8EDFF">""" + str(self.summary['stat']['failures']) + """</td>                         <td bgcolor="#C8EDFF">""" + str(self.summary['stat']['errors']) + """</td>                         <td bgcolor="#C8EDFF">""" + str(self.summary['stat']['skipped']) + """</td>                     </tr>                 </table>                 <p> </p>                 <table width="800" border="1" bordercolor="#FFFFFF" style="border-collapse:collapse;" cellspacing="1" cellpadding="4" bgcolor="#66CCFF" class="tabtop13"  font-size="16px" padding-left="15px">                     <tr>                         <td colspan="5" align="center" font-size="24"><strong>""" + 'Responses Time' + """</strong></td>                     </tr>                     <tr>                         <td width="266">""" + 'MAX' + """</td>                         <td width="266">""" + 'MIN' + """</td>                         <td width="266">""" + 'AVG' + """</td>                     </tr>                     <tr>                         <td bgcolor="#C8EDFF">""" + str(max(time_lst)) + 'ms' + """</td>                         <td bgcolor="#C8EDFF">""" + str(min(time_lst)) + 'ms' + """</td>                         <td bgcolor="#C8EDFF">""" + str(avg_time) + 'ms' + """</td>                     </tr>                     <tr>                         <td colspan="3" bgcolor="#C8EDFF">""" + '耗时最大api:' + str(max_case_name) + """</td>                     </tr>                 </table>                 """         return case_result      def __table_time(self):         """         接口响应时长分析 模块-content         :return:         """         table_td = ''         aly_time = self._get_time_analy()['aly_time']         aly_name = self._get_time_analy()['aly_name']         if len(aly_time) == len(aly_name):             for i in range(len(aly_time)):                 test_tmp = """                         <table width="800" border="1" bordercolor="#FFFFFF" style="border-collapse:collapse;" cellspacing="1" cellpadding="4" bgcolor="#fa8072" class="tabtop13"  font-size="16px" padding-left="15px">                             <tr>                                 <td width="80">""" + str(aly_time[i]) + 'ms' + """</td>                                 <td width="840">""" + str(aly_name[i]) + """</td>                              </tr>                         </table>                         """                 table_td += str(test_tmp)         return table_td      def __table_time_module(self):         """         错误信息统计和分析 模块         :return:         """         table_header = """                         <p> </p>                         <table width="800" border="1" bordercolor="#FFFFFF" style="border-collapse:collapse;" cellspacing="1" cellpadding="4" bgcolor="#66CCFF" class="tabtop13"  font-size="16px" padding-left="15px">                             <tr>                                 <td colspan="5" align="center" font-size="24"><strong>""" + 'Slow Responses' + """</strong></td>                             </tr>                             <tr>                                 <td width="80">""" + 'TIME' + """</td>                                 <td width="720">""" + 'API' + """</td>                             </tr>                         </table>                     """         return table_header + self.__table_time()      def __table_assembly(self):         rsp_data = self._get_case_analy()         table_td = ''         if rsp_data:             for i in range(len(rsp_data)):                 test_tmp = """                             <table width="920" border="1" bordercolor="#FFFFFF" style="border-collapse:collapse;" cellspacing="1" cellpadding="4" bgcolor="#fa8072" class="tabtop13"  font-size="16px" padding-left="15px">                                 <tr>                                     <td width="80">""" + str(rsp_data[i][0]) + """</td>                                     <td width="80">""" + str(rsp_data[i][2]) + """</td>                                     <td width="360">""" + str(rsp_data[i][1]) + """</td>                                     <td width="360">""" + str(rsp_data[i][4]) + """</td>                                     <td width="320">""" + str(rsp_data[i][3]) + """</td>                                 </tr>                             </table>                         """                 table_td += str(test_tmp)          return table_td      def __table_error_module(self):         """         错误信息统计和分析 模块         :return:         """         table_header = """                 <p> </p>                 <table width="920" border="1" bordercolor="#FFFFFF" style="border-collapse:collapse;" cellspacing="1" cellpadding="4" bgcolor="#66CCFF" class="tabtop13"  font-size="16px" padding-left="15px">                     <tr>                         <td colspan="5" align="center" font-size="24"><strong>""" + 'Error Analysis' + """</strong></td>                     </tr>                     <tr>                         <td width="80">""" + 'STATUS' + """</td>                         <td width="80">""" + 'CODE' + """</td>     					<td width="320">""" + 'CASE_NAME' + """</td>     					<td width="360">""" + 'CASE_DETAIL' + """</td>     					<td width="360">""" + 'API' + """</td>                     </tr>                 </table>             """         return table_header + self.__table_assembly()      def dd_analy(self):         rsp = self._get_summary()         time_lst = rsp['time_lst']         case_detail = rsp['case_detail']         aly_time, aly_case = [], []         if time_lst:             for i in range(len(time_lst)):                 if int(time_lst[i]) >= 2000:                     aly_time.append([time_lst[i], case_detail[i][3]])          if case_detail:             for j in range(len(case_detail)):                 rsp_code = json.loads(case_detail[j][4])                 rsp_status = case_detail[j][0]                 if case_detail[j][2] == 200:                     if rsp_code['code'] != '00000' and rsp_status != 'success':                         aly_case.append([case_detail[j][3], rsp_code['errorMsg']])                 else:                     aly_case.append([case_detail[j][3], case_detail[j][2]])         return {             'aly_time': aly_time,             'aly_case': aly_case,         }      def html_temp(self):         """         数据统计 模板 html for Email         :return: html 模板         """         speed_lst = self._get_time_analy()['aly_time']         analy_lst = self._get_case_analy()         rp = ''         if len(speed_lst) >= 1 and len(analy_lst) >= 1:             rp = self.__table_total() + self.__table_time_module() + self.__table_error_module()         elif len(speed_lst) >= 1 or len(analy_lst) >= 1:             if len(speed_lst) >= 1:                 rp = self.__table_total() + self.__table_time_module()             else:                 rp = self.__table_total() + self.__table_error_module()         return rp   if __name__ == '__main__':     pass  
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!