基於JSON的接口測試框架


更多學習資料請加QQ群: 822601020獲取

實現效果
單接口/斷言

多接口/Session依賴/中間變量

需求場景:

  1. 公司微服務接口使用數字簽名的方式, 使用Postman調試接口每次都需要修改源碼將驗簽臨時關閉, 但是關閉后,其他微服務不能正常調用該服務
  2. 從ELK中抓取到的接口參數, 需要Json格式化, 我通常使用Sublime Text + PrettyJson插件, 使用Postman調試, 有些表單類接口, 還需要把每個參數及值復制出來, 特別麻煩.

實現原理:

  • Sublime text3可以自定義編譯系統
  • Python方便的json數據處理
  • 使用Python實現簽名算法
  • requests發送接口請求

實現目標:

  • 采用json文件數據驅動, 支持一個json包含多個接口
  • 支持GET/POST/DELETE/...方法
  • 支持表單類/Json類請求
  • 支持headers, cookies, params, data, files, timeout
  • 支持session依賴, 中間變量

數據文件格式:

  • Get請求
{
  "url": "http://127.0.0.1:5000/api/user/logout/?name=張三"
}

{
  "url": "http://127.0.0.1:5000/api/user/logout/",
  "params": {"name": "張三"}
}
  • Post表單請求:
{
  "url": "http://127.0.0.1:5000/api/user/login/",
  "data": {"name": "張三","password": "123456"}
}
  • Json格式請求:
{
  "url": "http://127.0.0.1:5000/api/user/reg/",
  "headers": {"Content-Type": "application/json"},
  "data": { "name": "張991", "password": "123456"}
}

{
  "url": "http://127.0.0.1:5000/api/user/reg/",
  "type": "json",
  "data": { "name": "張991","password": "123456"}
}
  • 斷言格式(ASSERT):
{
  "url": "http://127.0.0.1:5000/api/user/login/",
  "data": {"name": "張三","password": "123456"},
  "ASSERT": ["response.status_code == 200", "'成功' in response.text"]
}
  • 需要簽名(SIGN):
{   "url": "http://127.0.0.1:5000/api/user/delUser/",
    "type": "json",
    "data": {"name": "張三"},
    "SIGN": true
}
  • 多接口,session依賴, 及中間變量(STORE)
[
	{
	    "url": "http://127.0.0.1:5000/api/user/getToken/",
	    "method": "get",
	    "params": {"appid": "136425"},
	    "STORE": {"token": "response.text.split('=')[1]"}
	},
	{   "url": "http://127.0.0.1:5000/api/user/updateUser/?token=%(token)s",
	    "headers": {"Content-Type": "application/json"},
	    "data": {"name": "張三","password": "123456"}
	}
]

工具實現:

sign.py

import hashlib

appid = user
appsecret = NTA3ZTU2ZWM5ZmVkYTVmMDBkMDM3YTBi

def md5(str):
    m = hashlib.md5()
    m.update(str.encode('utf8'))
    return m.hexdigest()  #返回摘要,作為十六進制數據字符串值


def makeSign(params):
    if not isinstance(params, dict):
        print("參數格式不正確,必須為字典格式")
        return None
    if 'sign' in params:
        params.pop('sign')
    sign = ''
    for key in sorted(params.keys()):
        sign = sign + key + '=' + str(params[key]) + '&'
    sign = md5(sign + 'appsecret=' + appsecret)
    params['sign'] = sign
    return params

post_json.py

import json
import os
import requests
from sign import makeSign

timeout = 10


def postJson(path, timeout=60):
    try:
        with open(path, encoding='utf-8') as f:
            apis = json.load(f)
    except IOError as e:
        print(e)

    except json.decoder.JSONDecodeError:
        print("json文件格式有誤")
        
    if isinstance(apis, dict):
        apis=[apis]

    session = requests.session()
    for api in apis:

        # 處理全局變量
        str_api = json.dumps(api)
        if '%' in str_api:
            api = json.loads(str_api % globals())

        # 獲取接口相關項
        url = api.get('url')
        method = api.get('method')
        type = api.get('type')
        headers = api.get('headers')
        cookies = api.get('cookies')
        params = api.get('params')
        data = api.get('data')
        files = api.get('files')

        # 特定動作(系統標簽)
        _sign = api.get('SIGN')  # 對數據進行簽名
        _store = api.get('STORE') # 存儲變量
        _assert = api.get('ASSERT') # 結果斷言
        
        # 處理簽名
        if _sign:
        	data = makeSign(data)

        # 如果json請求,轉換一下數據
        if type and type == 'json' or headers and 'json' in json.dumps(headers):
            data = json.dumps(data)

        # 根據method發送不同請求
        if not method or method.lower() == 'post': # 有data字段默認使用post方法
            response = session.post(url=url, headers=headers, cookies=cookies, params=params, data=data, files=files, timeout=timeout)  
        elif not data or method.lower() == 'get': # 沒有data字段默認采用get方法
        	  response = session.get(url=url, headers=headers, cookies=cookies, params=params, timeout=timeout)
        elif method.lower() == 'delete':
            response = session.delete(url=url, headers=headers, cookies=cookies, params=params, data=data, files=files, timeout=timeout)
        elif method.lower() == 'put':
            response = session.put(url=url, headers=headers, cookies=cookies, params=params, data=data, files=files, timeout=timeout)
        elif method.lower() == 'patch':
            response = session.patch(url=url, headers=headers, cookies=cookies, params=params, data=data, files=files, timeout=timeout)
        elif method.lower() == 'head':
            response = session.head(url=url, headers=headers, cookies=cookies, params=params, data=data, files=files, timeout=timeout)
        else:
            print("不支持當前方法")
        
        # 存儲中間結果
        if _store:
            for key in _store:
                globals()[key]=eval(_store[key])
        
        # 處理響應
        try:
            response_text = json.dumps(response.json(), ensure_ascii=False, indent=2)
        except json.decoder.JSONDecodeError:  # only python3
            try:
                response_text = response.text
            except UnicodeEncodeError:
                # print(response.content.decode("utf-8","ignore").replace('\xa9', ''))
                response_text = response.content
        finally:
            pass

        # 處理斷言
        status = "PASS"
        if _assert:
            assert_results = []
            for item in _assert:
                try:
                    assert eval(item)
                    assert_results.append("PASS: <%s>" % item)
                except AssertionError:
                    assert_results.append("FAIL: <%s>" % item)
                    status = "FAIL"
                except Exception as e:
                    assert_results.append("ERROR: <%s>\n%s" % (item, repr(e)))
                    status = "ERROR"
        
        # 打印結果
        print("="*80)
        print("請求:")
        print("Url: %s\nHeaders: %s\nData: %s" % (url, headers, data if isinstance(data, str) else json.dumps(data)))
        print("-"*80)
        print("響應:")
        print(response_text)
        if _assert:
            print("-"*80)
            print("斷言:")
            for assert_result in assert_results:
                print(assert_result)

def main():
    if len(sys.argv) != 2:
        print("缺少參數:json文件")
    else:
        postJson(sys.argv[1])

main()

**集成到Sublime **

  1. Sublime Text3 -> Tools -> Build System -> New Build System
    復制以下代碼, 修改Python為你的Python3環境, 腳本路徑為你的post_json.py路徑, 保存為PostJson.sublimet-build
  • Windows 系統
{
    "cmd": ["python3","C:/PostJson/post_json.py","$file"],
    "file_regex": "^[ ] *File \"(...*?)\", line ([0-9]*)",
    "selector": "source.json",
    "encoding":"cp936",
}

  • Linux & Mac
{
    "cmd": ["/Library/Frameworks/Python.framework/Versions/3.6/bin/python3","-u","/Users/apple/Applications/PostJson/post_json.py","$file"],
    "file_regex": "^[ ] *File \"(...*?)\", line ([0-9]*)",
    "selector": "source.json",
    "env": {"PYTHONIOENCODING": "utf8"}, 
}

使用方法

建議安裝PrettyJson插件

  1. 在Sublime text3中, 新建json文件,並保存為*.json
{
  "url": "http://127.0.0.1:5000/api/user/login/",
  "data": {"name": "張三","password": "123456"}
}
  1. 按Ctrl + B運行

項目路徑

Github: PostJson

補充了以下功能:

  • 用例discover, 自動發現和批量執行
  • 生成報告
  • 命令行參數
    • verbose: 顯示級別: 1. 只顯示用例結果 2. 只顯示響應文本(默認) 3. 顯示請求,響應及斷言信息
    • host: 指定服務器地址
    • env: 指定環境
    • timeout: 指定timeout
    • html: 指定輸出html報告路徑
    • log: 指定輸出log路徑
    • collect-only: 只列出所有用例

ToDo

  • 完善測試報告
  • 支持數據庫對比
  • 支持執行Sql, Shell, Cmd命令等
  • 使用協程, 進行異步並發
  • 支持分布式執行
  • 支持定時執行
  • 進行持續集成


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM