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

HttpRunner 框架

简介

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

相关链接
  • HttpRunner中文使用文档
  • Github_HttpRunner
  • 作者博客-DebugTalk
框架对比
框架 最新版本 开发语言 支持语言 持续集成 拓展难度 性能测试 数据分离 推广门槛 其它特性
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 做Http接口自动化测试_第1张图片

关于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 搭建接口自动化框架概况
浅谈HttpRunner 做Http接口自动化测试_第2张图片
工程结构
浅谈HttpRunner 做Http接口自动化测试_第3张图片
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
浅谈HttpRunner 做Http接口自动化测试_第4张图片
报告拓展 -> 邮件报告
浅谈HttpRunner 做Http接口自动化测试_第5张图片
报告拓展 -> 钉钉机器人提醒
浅谈HttpRunner 做Http接口自动化测试_第6张图片

拓展 源码:邮件 + 钉钉

#!/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 = """
                

Api check Report

"""
+ '详见附件...' + """
"""
+ time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + """
""" + 'API CheckReport' + """
""" + 'START AT' + """ """ + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + """
""" + 'DURATION' + """ """ + str(duration) + 'seconds' + """
""" + 'SERVER' + """ """ + server_path + """
""" + 'TOTAL' + """ """ + 'SUCCESS' + """ """ + 'FAILED' + """ """ + 'ERROR' + """ """ + 'SKIPPED' + """
""" + str(self.summary['stat']['testsRun']) + """ """ + str(self.summary['stat']['successes']) + """ """ + str(self.summary['stat']['failures']) + """ """ + str(self.summary['stat']['errors']) + """ """ + str(self.summary['stat']['skipped']) + """

""" + 'Responses Time' + """
""" + 'MAX' + """ """ + 'MIN' + """ """ + 'AVG' + """
""" + str(max(time_lst)) + 'ms' + """ """ + str(min(time_lst)) + 'ms' + """ """ + str(avg_time) + 'ms' + """
""" + '耗时最大api:' + str(max_case_name) + """
"""
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 = """
""" + str(aly_time[i]) + 'ms' + """ """ + str(aly_name[i]) + """
"""
table_td += str(test_tmp) return table_td def __table_time_module(self): """ 错误信息统计和分析 模块 :return: """ table_header = """

""" + 'Slow Responses' + """
""" + 'TIME' + """ """ + 'API' + """
"""
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 = """
""" + str(rsp_data[i][0]) + """ """ + str(rsp_data[i][2]) + """ """ + str(rsp_data[i][1]) + """ """ + str(rsp_data[i][4]) + """ """ + str(rsp_data[i][3]) + """
"""
table_td += str(test_tmp) return table_td def __table_error_module(self): """ 错误信息统计和分析 模块 :return: """ table_header = """

""" + 'Error Analysis' + """
""" + 'STATUS' + """ """ + 'CODE' + """ """ + 'CASE_NAME' + """ """ + 'CASE_DETAIL' + """ """ + 'API' + """
"""
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

你可能感兴趣的:(接口自动化)