apiAutoTest:基於mitmproxy實現介面錄製

zy7y發表於2021-05-22

apiAutoTest

先軟文介紹下:apiAutoTest是個人和眾多測試同行參與(提供新的需求)的一個介面測試工具專案.

Gitee: https://gitee.com/zy7y/apiAutoTest

Github: https://github.com/zy7y/apiAutoTest

目前功能

  • [x] 測試前後資料庫備份操作,個人理解算資料清洗
  • [x] 各介面之間的測試資料依賴
  • [x] 自定義擴充套件函式定義,解決部分加密演算法
  • [x] 後置sql,結果用於依賴或者斷言(select 語句只能查出第一條)1. * *
  • [x] 實際結果可動態提取,與預期結果絕對==
  • [x] 可選用例失敗重跑機制
  • [x] 基於mitmproxy錄製介面生成用例檔案

重大更新(個人認為)

在之前的一篇自定義函式簡單實現方式時,有提醒到語法可能出現衝突,所以在前兩天更新時已經統一了語法${}

無論是使用依賴引數還是自定義方法都使用${}, 為了避免每次使用其他介面返回提取jsonpath表示式在用例中的冗餘(或許也提高了些效能,之前版本是會儲存整個響應內容的),用例中增加了提取引數來實現形式如下

{
    // key -> id 為其他介面使用時的引數變數 用法 ${id}
    "id": "$.data.id" // $.data.id 實則為jsonpath表示式 從當前響應中提取id值
}

相關使用文件: 可到原始碼Readme.md 檔案中前往 線上文件檢視

本次更新

**本次更新內容使用演示視訊: 點選訪問B站 **

契機

有同志,希望有個錄製功能來減少手寫引數的時間

根本

基於mitmproxy, 抓包微信小程式 使用其提供的擴充套件API, 通過mitmproxy 實現代理之後捕獲到HTTP/HTTPS請求,並把請求已追加的形式新增到excel中,當錄製完成務必使用ctrl + c 關閉錄製,將生成一個完成的用例資料檔案

可指定錄製包含請求地址的介面

如何錄製

  1. 前置條件: https://www.cnblogs.com/zy7y/p/14798151.html

  2. 開啟本機代理

  3. 修改tools\recording.py中配置抓包請求地址, 用例生成路徑

  4. apiAutoTest根目錄下執行

    mitmweb -s tools\recording.py
    

  5. 正常去使用就行了,當不需要錄製的時候 在上面這個視窗Ctrl + C停止錄製,然後關閉本機代理

錄製的用例

因為預設錄製的url是完整的url,所以如果直接用這個檔案,請把config/config.yaml中的serve dev 基準地址換成"", 因為條件有限沒法覆蓋測試很多內容這快功能可能會有Bug, 目前個人測試了Graphql規範介面的錄製,RestFul規範介面錄製, 不排除其他的無法完整的生成用例檔案

需要注意Excel 單元格字元數限制問題, Graphql規範介面非常容易出現不可寫入的情況, 單從業務介面來說應該不容易出現此類問題

執行錄製的用例

config/config.yaml修改基準地址dev"",指定使用錄製的用例檔案

server:
  # 本地介面服務
  test: http://127.0.0.1:8888/
  # https://space.bilibili.com/283273603 演示專案後端服務來自
#  dev: http://www.ysqorz.top:8888/api/private/v1/
  dev: ''
# 基準的請求頭資訊
request_headers: {}
file_path:
  test_case: data/case_data1.xls	# 指定使用那個用例,這裡使用了錄製的用例
  report: report/
  log: log/run{time}.log
....

執行結果

實現原始碼

#!/usr/bin/env/ python3
# -*- coding:utf-8 -*-
"""
@Project: apiAutoTest
@File  :recording.py
@Author:zy7y
@Date  :2021/5/21 22:07
@Desc  : 錄製介面,生成用例檔案
基於mitmproxy實現
參考資料:
https://blog.wolfogre.com/posts/usage-of-mitmproxy/
https://www.cnblogs.com/liuwanqiu/p/10697373.html
"""

import json

import mitmproxy.http
import xlwt

# 上傳檔案介面不能錄入檔案引數 , excel單元格限制: Exception: String longer than 32767 characters
from mitmproxy import ctx


class Counter:
    def __init__(self, filter_url: str, filename: str = "data/case_data1.xls"):
        """
        基於mitmproxy抓包生成用例資料
        :param filter_url: 需要過濾的url
        :param filename: 生成用例檔案路徑
        """
        self.url = filter_url
        self.excel_row = [
            '編號',
            '用例標題',
            '請求頭',
            '介面地址',
            '是否執行',
            '請求方式',
            '入參關鍵字',
            '上傳檔案',
            '請求資料',
            '提取引數',
            '後置sql',
            '預期結果']
        self.cases = [self.excel_row]
        self.counter = 1
        self.file = filename

    def response(self, flow: mitmproxy.http.HTTPFlow):
        """
        mitmproxy抓包處理響應,在這裡彙總需要資料
        :param flow:
        :return:
        """
        if self.url in flow.request.url:
            # 編號
            number = "C" + str(self.counter)
            # 標題
            title = "mitmproxy錄製介面" + str(self.counter)
            try:
                token = flow.request.headers["Authorization"]
            except KeyError:
                token = ''
            header = json.dumps({"Authorization": token})
            data = flow.request.text
            # 請求地址,config.yaml 裡面基準環境地址 寫 空字串
            method = flow.request.method.lower()
            url = flow.request.url
            try:
                content_type = flow.request.headers['Content-Type']
            except KeyError:
                content_type = ''
            if 'form' in content_type:
                data_type = "data"
            elif 'json' in content_type:
                data_type = 'json'
            else:
                data_type = 'params'
                if '?' in url:
                    data = url.split('?')[1]
            data = self.handle_form(data)
            # 預期結果
            expect = json.dumps(
                {".": json.loads(flow.response.text)}, ensure_ascii=False)

            # 日誌
            ctx.log.info(url)
            ctx.log.info(header)
            ctx.log.info(content_type)
            ctx.log.info(method)
            ctx.log.info(data)
            ctx.log.info(flow.response.text)
            case = [
                number,
                title,
                header,
                url.split('?')[0],
                "是",
                method,
                data_type,
                "",
                data,
                "",
                "",
                expect]
            self.cases.append(case)
            self.counter += 1
            # 檔案末尾追加
            self.excel_cases()

    def excel_cases(self):
        """
        對二維列表cases進行迴圈並將內容寫入單元格中
        :return:
        """
        workbook = xlwt.Workbook()
        worksheet = workbook.add_sheet('用例資料')
        for x in range(len(self.cases)):
            for y in range(len(self.cases[x])):
                worksheet.write(x, y, self.cases[x][y])
        try:
            workbook.save(self.file)
        except Exception as e:
            print(e)

    def handle_form(self, data: str):
        """
        處理 Content-Type:	application/x-www-form-urlencoded
        預設生成的資料 username=admin&password=123456
        :param data: 獲取的data 類似這樣  username=admin&password=123456
        :return:
        """
        data_dict = {}
        if data.startswith('{') and data.endswith('}'):
            return data
        try:
            for i in data.split('&'):
                data_dict[i.split('=')[0]] = i.split('=')[1]
            return json.dumps(data_dict)
        except IndexError:
            return ''


addons = [
    Counter("http://www.ysqorz.top:8888/api/private/v1/")
]

"""

mitmweb -s tools\recording.py 啟動
ctrl + C 停止 並生成完整用例
"""

參考資料

https://docs.mitmproxy.org/stable/
https://blog.wolfogre.com/posts/usage-of-mitmproxy/
https://www.cnblogs.com/liuwanqiu/p/10697373.html

相關文章