『居善地』接口測試 — 8、接口自動化測試框架的設計與實現


(一)接口測試框架的思想

自動化測試框架不是一個模式,而是一種思想和方法的集合,通俗的講就是一個架構。

為了更好的了解自動化測試框架,應該對以下幾種自動化測試框架思想有一定的認知:

  • 模塊化思想
  • 庫思想
  • 數據驅動思想
  • 關鍵字驅動思想

以上僅僅是代表了一種自動化測試的思想,並不能定義為框架。

上面講到框架=思想+方法,於是演化了以下五種框架:

1、模塊化測試腳本框架

需要創建小而獨立的可以描述的模塊、片斷以及待測應用程序的腳本。

這些樹狀結構的小腳本組合起來,就能組成能用於特定的測試用例的腳本。

2、測試庫框架

與模塊化測試腳本框架很類似,並且具有同樣的優點。

不同的是測試庫框架把待測應用程序分解為過程和函數而不是腳本。

框架需要創建描述模塊、片斷以及待測應用程序的功能庫文件。

3、關鍵字驅動或表驅動的測試框架

框架需要開發數據表和關鍵字。

這些數據表和關鍵字獨立於執行它們的測試自動化工具,並可以用來“驅動"待測應用程序和數據的測試腳本代碼,關鍵宇驅動測試看上去與手工測試用例很類似。

在一個關鍵字驅動測試中,把待測應用程序的功能和每個測試的執行步驟一起寫到一個表中。

測試框架可以通過很少的代碼來產生大量的測試用例。

同樣的代碼在用數據表來產生各個測試用例的同時被復用。

4、數據驅動測試框架

在這里測試的輸入和輸出數據是從數據文件中讀取(數據池,ODBC源,CSV文件,EXCEL文件,Json文件,Yaml文件,ADO對象等)並且通過捕獲工具生成或者手工生成的代碼腳本被載入到變量中。

在這個框架中,變量不僅被用來存放輸入值還被用來存放輸出的驗證值。

整個程序中,測試腳本來讀取數值文件,記載測試狀態和信息。這類似於表驅動測試,在表驅動測試中,它的測試用例是包含在數據文件而不是在腳本中,對於數據而言,腳本僅僅是一個“驅動器”,或者是一個傳送機構。

然而數據驅動測試不同於表驅動測試,盡管導航數據並不包含在表結構中。

在數據驅動測試中,數據文件中只包含測試數據。

5、混合測試自動化框架

最普遍的執行框架是上面介紹的所有技術的一個結合,取其長處,彌補其不足。

混合測試框架是由大部分框架隨着時間並經過若干項目演化而來的。

(二)接口測試框架結構解析

  • common目錄:一些公共方法存放目錄。
    • 封裝請求
      send_method.py:封裝接口請求方法。
    • 封裝獲取返回值
      getKeyword_forResult.py:通過關鍵字獲取接口返回值。
    • 讀取數據方法
  • interface目錄:存放接口的目錄。
    每一個接口或者一類接口來寫一個interface(也就是一個接口對應一個.py文件)
    • 對該接口的請求:用於單接口測試
    • 根據業務獲取接口返回值:用於關聯接口測試
  • script目錄:存放測試用例的目錄。
    也可以命令為testCase目錄。
    接口測試用例包括:
    • 單接口測試用例
    • 關聯接口測試用例
  • Config目錄:存放配置文件。配置一些常量,例如數據庫的相關信息,接口的相關信息等。
  • Data目錄:存放公共部分數據,比如測試數據,excel文件等等。
  • Log目錄:存放logging日志信息。
  • Reports目錄:存放接口測試報告目錄。
  • runMain.py文件:主程序入文件口,用於執行case。

(三)接口自動化測試框架封裝實現

之前分析完了接口測試框架的設計與架構,下面我們就來一步一步的完成接口自動化測試框架的實現。

1、創建測試框架項目

Student Management System Interface testing framework創建一個測試項目SMSITF

項目名上右鍵 —> New —> Python Package —> 創建common目錄。

同理創建如下目錄:

  • interface目錄:存放接口的目錄。
  • script目錄:存放測試用例的目錄。
  • Config目錄:存放配置文件。配置一些常量,例如數據庫的相關信息,接口的相關信息等。
  • Data目錄:存放公共部分數據,比如測試數據,excel文件等等。
  • Log目錄:存放logging日志信息。
  • Reports目錄:存放接口測試報告目錄。

創建好后如下圖:

image

接下來我們要一步一步實現這個框架里邊的功能。

DictionaryPython Package目錄說明:

Dictionary在Pycharm中就是一個文件夾,放置資源文件,該文件夾其中並不包含__init.py__文件。

Python Package文件夾會自動創建__init.py__文件,換句話說Python Package就是創建一個目錄,其中包括一組模塊和一個__init.py__文件。

2、封裝發送請求方法

一些公共的方法,要寫在common目錄中,主要是封裝使用Requests庫發送請求的方法。

其他所有的公共的方法都可以封裝在common目錄中。

"""
send_method.py 文件說明:
1,封裝接口請求方式
    根據項目接口文檔提供的內容進行封裝
    不同的項目,sendmethod也不太一樣,如請求體格式等。
2.封裝思路-結合接口三要素
    請求方式+請求地址
    請求參數
    返回值
3.以學生管理系統SMS為例:
    結合學生管理系統項目的接口文檔,封裝SendMethod類

"""
# 導入所需模塊
import requests
import json


# 封裝請求模塊
class SendMethod:
    """
        結合學生管理系統SMS,請求方式包括如下:
            get ---> parmas標准請求參數
            post--->請求參數類型 json
            put --->請求參數類型 json
            delete ---> parmas標准請求參數
    """

    # 定義該方法為靜態方法
    @staticmethod
    def send_method(method, url, parmas=None, json=None):
        """
        封裝適用於學生管理系統項目的接口請求
        :param method: 請求方式
        :param url: 請求地址
        :param parmas: get和delete請求參數
        :param json: post和put請求參數
        :param headers: 請求頭
        :return:
        """
        # 定義發送請求的方法
        if method == "get" or method == "delete":
            response = requests.request(method=method, url=url, params=parmas)
        elif method == "post" or method == "put":
            response = requests.request(method=method, url=url, json=json)
            # 如果有不同的請求頭,還可以繼續添加接收的參數
            # response = requests.request(method=method, url=url, json=json, data=data, files=data)
        else:
            # 這里是簡單處理,完成封裝需要加上異常處理。
            response = None
            print("請求方式不正確")

        # 如果請求方式是delete,只返回狀態碼
        # 這是根據項目接口文檔中delete方法的返回規則定的。
        if method == "delete":
            return response.status_code
        else:
            # 項目中接口的返回值是json格式的,就可以用json()進行格式化返回結果。
            return response.json()

    @staticmethod
    def json_2_python(res):
        """
        格式化返回數據
        :param res:接口返回的數據
        :return:
        """
        return json.dumps(res, indent=2, ensure_ascii=False)


if __name__ == '__main__':
    method = "post"
    url = "http://127.0.0.1:8000/api/departments/"
    data = {
        "data": [
            {
                "dep_id": "T02",
                "dep_name": "接口測試學院",
                "master_name": "Test-Master",
                "slogan": "Here is Slogan"
            }
        ]
    }
    res = SendMethod.send_method(method=method, url=url, json=data)
    # print(res)
    print(SendMethod.json_2_python(res))

    # method = "get"
    # params = {"$dep_id_list": "1, 2, 3"}
    # res = SendMethod.send_method(method=method, url=url, json=data)
    # print(SendMethod.json_2_python(res))

3、封裝獲取接口返回結果指定內容

該文件是封裝處理返回值結果的一些方法。

我們需要用到一個Python中的模塊JsonPath ,下面就先來介紹一下JsonPath 模塊。

(1)JsonPath 介紹

用來解析多層嵌套的Json數據。

JsonPath是一種信息抽取類庫,是從JSON文檔中抽取指定信息的工具,提供多種語言實現版本,包括:JavascriptPythonPHPJava

JsonPath 對於 JSON 來說,相當於 XPath 對於 XML。

(2)JsonPath 安裝

安裝方法:pip install jsonpath

使用方法如下:

# 導入jsonpath模塊
import jsonpath模塊

# 嵌套n層也能取到所有key_nane信息,
# 其中:"$"表示最外層的{},
# ".."表示模糊匹配,
# 當傳入不存在的key_nane時,程序會返回false。

res = jsonpath.jsonpath(response, f"$..{keyword}")[0]

"""
jsonpath方法說明
jsonpath(obj, expr, result_type='VALUE', debug=0, use_eval=True):

# obj表是要處理的json對象。
# expr是jsonpath匹配表達式。$..{keyword} 這種方式比較通用
"""

JsonPath官方文檔:http://goessner.net/articles/JsonPath

github上有它的應用:https://github.com/json-path/JsonPath(Java中的JsonPath使用文檔)

(3)JsonPath與XPath語法對比

Json結構清晰,可讀性高,復雜度低,非常容易匹配,下表中對應了XPath的用法。

XPath JSONPath 描述
/ $ 根節點
. @ 現行節點
/ .or[] 取子節點
.. n/a 取父節點,Jsonpath未支持
// .. 就是不管位置,選擇所有符合條件的條件
* * 匹配所有元素節點
@ n/a 根據屬性訪問,Json不支持,因為Json是個Key-value遞歸結構,不需要屬性訪問。
[] [] 迭代器標示(可以在里邊做簡單的迭代操作,如數組下標,根據內容選值等)
| [,] 支持迭代器中做多選。
[] ?() 支持過濾操作.
n/a () 支持表達式計算
() n/a 分組,JsonPath不支持

(4)getKeyword_forResult.py文件實現

"""
getKeyword_forResult.py文件說明:
1.作用
    在接口返回值中,通過關鍵獲取獲取對應字段內容
2,前提:需要安裝一個庫:jsonpath庫
    安裝jsonpath : pip install jsonpath
    使用jsonpath模塊進行處理更加方便

"""
# 導入jsonpath模塊
import jsonpath


# 封裝獲取接口返回值方法
class GetKeyword:
    # 定義成一個靜態方法
    @staticmethod
    def get_keyword(response: dict, keyword):
        """
        通過關鍵字獲取對應返回值,如果有多個值,只返回第一個,
        如果關鍵字不存在,返回False。
        :param:response 數據源  字典格式
        :param:keyword 要獲取的字段
        :return:
        """
        try:
            return jsonpath.jsonpath(response, f"$..{keyword}")[0]
        except:
            print("關鍵字不存在")

    @staticmethod
    def get_keywords(response: dict, keyword):
        """
        通過關鍵字獲取一組數據
        :param response: 數據源 dict格式
        :param keyword:  如果關鍵字不存在,返回False
        :return:
        """
        try:
            return jsonpath.jsonpath(response, f"$..{keyword}")
        except:
            print("關鍵字不存在")


if __name__ == '__main__':
    response = {
        "count": 2,
        "next": "下一頁",
        "previous": None,
        "results": [
            {
                "dep_id": "10",
                "dep_name": "tester_10",
                "master_name": "master_10",
                "slogan": "隨便"
            },
            {
                "dep_id": "11",
                "dep_name": "tester_11",
                "master_name": "master_11",
                "slogan": "隨便"
            }
        ]
    }
    keyword = "dep_id"
    # print(GetKeyword.get_keyword(response, keyword))
    print(GetKeyword.get_keywords(response, keyword))

4、接口目錄中的方法的實現

每一個接口或者一類接口封裝成一個interface(也就是一個接口對應一個.py文件)

  • 對該接口的請求:用於單接口測試。
  • 根據業務獲取接口返回值:用於關聯接口測試。

(關於一個接口,所對應要測試哪幾個方面的業務,都封裝到該文件中,會用到上面commn目錄中封裝好的公共方法)

示例如下:

(1)示例1:封裝新增學院接口

"""
新增學院接口
1.單接口測試方法
2.關聯接口測試方法
    獲取返回值中的字段
"""
# 導入自定義的公共方法
from common.send_method import SendMethod
from common.getKeyword_forResult import GetKeyword


# 封裝新增學院接口測試
class Add_department:

    # url和請求方式對於一個接口來說是固定的,
    # 所以這兩個參數可以寫在初始化方法中。
    def __init__(self, url, method="post"):
        self.method = method
        self.url = url

    def add_dep(self, data):
        """
        定義新增學院接口:針對單接口測試
        :param data: 新增學院的請求參數
        :return:
        """
        return SendMethod.send_method(self.method, url=self.url, json=data)

    def get_keyword(self, data, keyword):
        """
        獲取新增成功后的關鍵字值:為關聯接口測試准備
        :param data:
        :param keyword:
        :return:
        """
        res = self.add_dep(data)

        # 獲取新增學院接口返回值中的學院的具體某一屬性
        return GetKeyword.get_keyword(res, keyword)


if __name__ == '__main__':
    url = "http://127.0.0.1:8000/api/departments/"
    data = {
        "data": [
            {
                "dep_id": "T03",
                "dep_name": "Test學院",
                "master_name": "Test-Master",
                "slogan": "Here is Slogan"
            }
        ]
    }
    add = Add_department(url)
    res = add.add_dep(data)  # 新增學院接口方法
    print(res)
    keyword = "dep_id"
    dep_id = add.get_keyword(data, keyword)  # 獲取新增成功后depid
    print(dep_id)

(2)示例2:封裝查詢學院接口

"""
get_dep.py文件說明:
1.查詢接口測試
2.獲取查詢接口返回值
"""
from common.send_method import SendMethod


class Get_Departments:
    def __init__(self, url, method="get"):
        self.url = url
        self.method = method

    def get_departments(self):
        """
        查詢所有學院
        :return:
        """
        return SendMethod.send_method(method=self.method, url=self.url)

    def get_department(self, dep_id):
        """
        根據id查詢單個學院
        :return:
        """
        url = self.url + f"{dep_id}/"
        return SendMethod.send_method(method=self.method, url=url)

    def get_department_for_multpart(self, data):
        """
        根據參數查詢學院
        :return:
        """
        return SendMethod.send_method(method=self.method, url=self.url, parmas=data)


if __name__ == '__main__':
    url_1 = "http://127.0.0.1:8000/api/departments/"
    data = {"$dep_id_list": "12,13"}
    get_dep = Get_Departments(url=url_1)
    # 查詢所有學院
    # print(get_dep.get_departments())
    # 查詢指定學院
    dep_id = 16
    # print(get_dep.get_department(dep_id))
    # 根據條件查詢學院
    print(get_dep.get_department_for_multpart(data))

5、測試用例目錄的實現

script目錄中存放的是測試用例,包括單接口和組合接口的測試用例。

測試用例是在unittest框架下編寫,用法同UI測試框架。

(1)編寫單接口測試用例

"""
測試新增學院接口
"""

# 測試用例是在unittest框架下編寫
import unittest
from interface.add_departments import Add_department  # 導入新增學院接口
from common.getKeyword_forResult import GetKeyword  # 返回值處理接口


# 測試添加和查詢學院的關聯型接口
class Test_Add_Dep(unittest.TestCase):
    def setUp(self) -> None:
        self.url = "http://127.0.0.1:8000/api/departments/"
        # 實例化Add_department
        self.add_dep = Add_department(self.url)

    # 開始編寫測試用例
    def test_add_dep_success(self):
        """
        測試添加學院成功接口
        :return:
        """

        # 封裝請求參數
        data = {
            "data": [
                {
                    "dep_id": "T100",
                    "dep_name": "Test學院",
                    "master_name": "Test-Master",
                    "slogan": "Here is Slogan"
                }
            ]
        }

        # 新增學院
        response = self.add_dep.add_dep(data)
        # 獲取添加成功后的dep.id
        """
        # 因為直接使用該方法相當於又執行了一次添加學院接口
        # 所以不能夠這樣調用
        self.add_dep.get_depid(data)
        """
        res_dep_id = GetKeyword.get_keyword(response["create_success"], "dep_id")
        expect = "T100"
        self.assertEqual(res_dep_id, expect)

    # 測試添加學院完整性實現
    def test_add_dep(self):
        """
        測試添加學院接口
        :return:
        """

        # 封裝請求參數
        data = {
            "data": [
                {
                    "dep_id": "T101",
                    "dep_name": "Test學院",
                    "master_name": "Test-Master",
                    "slogan": "Here is Slogan"
                }
            ]
        }
        # 新增學院
        response = self.add_dep.add_dep(data)

        """
        並返回值的驗證有3種情況
            #1.添加成功
            #2.添川id已存在的學院
            #3.參敖錯誤(自己實現)
        根據對接口檔的分析
            可以通過判斷返回值是否包含“status_code”區分1,2和3,然后區分1,2
            根據返回值中already_exist.count是否為0,判斷是否添加成功
        """

        # 這里只判斷上面的1,2情況,工作中根據實際業務自己在完成
        if GetKeyword.get_keyword(response["already_exist"], "count") == 0:
            # 獲取添加成功后的dep.id
            res_dep_id = GetKeyword.get_keyword(response["create_success"], "dep_id")
        else:
            res_dep_id = GetKeyword.get_keyword(response["already_exist"], "dep_id")

        expect = "T101"
        self.assertEqual(res_dep_id, expect)


if __name__ == '__main__':
    unittest.main()

(2)編寫組合接口測試用例

"""
測試新增和查詢接口(組合接口業務)
    先新增--->再查詢
"""
# 測試用例是在unittest框架下編寫
import unittest
from interface.add_departments import Add_department  # 導入新增學院接口
from interface.get_departments import Get_Departments  # 查詢學院接口
from common.getKeyword_forResult import GetKeyword  # 返回值處理接口


# 測試添加和查詢學院的關聯型接口
class Test_Add_Get_Dep(unittest.TestCase):
    def setUp(self) -> None:
        self.url = "http://127.0.0.1:8000/api/departments/"
        # 實例化Add_department添加學院
        self.add_dep = Add_department(self.url)
        # 實例化Get_Departments查詢學院
        self.get_dep = Get_Departments(self.url)

    # 開始編寫測試用例
    def test_add_get(self):
        # 封裝請求參數
        add_data = {
            "data": [
                {
                    "dep_id": "T03",
                    "dep_name": "Test學院",
                    "master_name": "Test-Master",
                    "slogan": "Here is Slogan"
                }
            ]
        }

        # 一下邏輯待查證,知道組合的形式即可。
        # 執行添加學院接口。目的:獲取添加成功后的學院id
        # 獲取新增學院后的id
        dep_id = self.add_dep.get_keyword(add_data, "dep_id")
        # 查詢新增學院信息
        result = self.get_dep.get_department(dep_id)
        # 通過獲取查詢后的學院id作為實際結果
        res_dep_id = GetKeyword.get_keyword(result, "dep_id")
        # 獲取預期結果id
        expect = GetKeyword.get_keyword(add_data, "dep_id")
        # 斷言結果
        self.assertEqual(expect, res_dep_id)


if __name__ == '__main__':
    unittest.main()


6、測試用例參數化實現

(1)准備數據

先創建一個Excel表格,里邊填寫如下數據

dep_id dep_nane master_nane slogan expect
T1001 學院1001 tester_1001 slogan1001 T1001
學院1002 tester_1002 slogan1002 400
T1003 tester_1003 slogan1003 400
T1004 學院1004 slogan1004 400
T1005 學院1005 tester_1005 T1005

把Excel表格中的數據准備好之后,放入項目的data目錄中即可。注意要把Excel表格存儲為.xls格式,兼容性好。

(2)在common目錄中編寫讀取Excel數據的腳本

編寫opreation_excel.py腳本如下:

import xlrd
from xlrd import xldate_as_tuple
from datetime import datetime

class OperationExcel:
    def __init__(self, filepath):
        book = xlrd.open_workbook(filename=filepath)
        self.sheet = book.sheet_by_index(0)

    def read_excel(self):
        rows = self.sheet.nrows
        cols = self.sheet.ncols
        all_data_list = []
        for row in range(1, rows):
            data_list = []
            for col in range(cols):
                ctype = self.sheet.cell(row, col).ctype
                cell = self.sheet.cell_value(row, col)
                if ctype == 2 and cell % 1 == 0:
                    cell = int(cell)
                elif ctype == 3:
                    date = datetime(*xldate_as_tuple(cell, 0))
                    cell = date.strftime("%Y-%m-d %H-%M-%S")
                elif ctype == 4:
                    cell = True if cell == 1 else False  # 三目雲算法
                data_list.append(cell)
            all_data_list.append(data_list)
        return all_data_list

    def get_data_by_dict(self):
        keys = self.sheet.row_values(0)
        values = self.read_excel()
        data_list = []
        for value in values:
            tmp = zip(keys, value)
            data_list.append(dict(tmp))
        return data_list


if __name__ == '__main__':
    oper = OperationExcel('testdata.xlsx')
    # data = oper.read_excel()
    data = oper.get_data_by_dict()
    print(data)

(3)在script目錄中編寫測試用例

在script目錄中編寫test_add_dep_batch.py測試用例。

"""
新增學院接口測試--批量新增
"""

# 測試用例是在unittest框架下編寫
import unittest
from interface.add_departments import Add_department  # 導入新增學院接口
from common.getKeyword_forResult import GetKeyword  # 返回值處理接口
# 步驟1:導入OperationExcel數據讀取腳本和ddt模塊
from common.opreationexcel import OperationExcel
import ddt

# 步驟2:對OperationExcel進行實例化
# 獲得文件對象
oper = OperationExcel("../data/add_dep.xls")
# 獲取數據
test_data = oper.get_data_by_dict()


# 測試添加和查詢學院的關聯型接口
# 步驟3
@ddt.ddt()
class Test_Add_Dep(unittest.TestCase):
    def setUp(self) -> None:
        self.url = "http://127.0.0.1:8000/api/departments/"
        # 實例化Add_department
        self.add_dep = Add_department(self.url)

    # 開始編寫測試用例
    # 步驟4
    @ddt.data(*test_data)
    def test_add_dep_success(self, data):  # 步驟5:出入data參數
        """
        測試添加學院成功接口
        :return:
        """
        # 步驟6:准備數據
        req_data = {
            "data": [
                {
                    "dep_id": data["dep_id"],
                    "dep_name": data["dep_name"],
                    "master_name": data["master_name"],
                    "slogan": data["slogan"]
                }
            ]
        }

        # 新增學院
        response = self.add_dep.add_dep(req_data)
        # 獲取添加成功后的dep.id

        # 步驟7:完成測試邏輯
        # 如果添加學院參數請求錯誤,會出現status_code屬性
        # 且status_code屬性返回400
        if "status_code" in response.keys():
            res = GetKeyword.get_keyword(response, "status_code")
        else:
            # 添加學院成功,則獲取添加后學院的id
            """
            # 因為直接使用該方法相當於又執行了一次添加學院接口
            # 所以不能夠這樣調用
            self.add_dep.get_depid(data)
            """
            res = GetKeyword.get_keyword(response["create_success"], "dep_id")

        # 斷言
        self.assertEqual(res, data["expect"])


if __name__ == '__main__':
    unittest.main()

以上就完成了一個最簡單,最基礎的接口自動化測試框架的搭建。


免責聲明!

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



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