天天看點

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

目錄

  • (一)接口測試架構的思想
    • 1、子產品化測試腳本架構
    • 2、測試庫架構
    • 3、關鍵字驅動或表驅動的測試架構
    • 4、資料驅動測試架構
    • 5、混合測試自動化架構
  • (二)接口測試架構結構解析
  • (三)接口自動化測試架構封裝實作
    • 1、建立測試架構項目
    • 2、封裝發送請求方法
    • 3、封裝擷取接口傳回結果指定内容
      • (1)JsonPath 介紹
      • (2)JsonPath 安裝
      • (3)JsonPath與XPath文法對比
      • (4)getKeyword_forResult.py檔案實作
    • 4、接口目錄中的方法的實作
    • 5、測試用例目錄的實作
    • 6、測試用例參數化實作

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

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

  • 子產品化思想
  • 庫思想
  • 資料驅動思想
  • 關鍵字驅動思想

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

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

需要建立小而獨立的可以描述的子產品、片斷以及待測應用程式的腳本。

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

與子產品化測試腳本架構很類似,并且具有同樣的優點。

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

架構需要建立描述子產品、片斷以及待測應用程式的功能庫檔案。

架構需要開發資料表和關鍵字。

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

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

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

同樣的代碼在用資料表來産生各個測試用例的同時被複用。

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

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

整個程式中,測試腳本來讀取數值檔案,記載測試狀态和資訊。這類似于表驅動測試,在表驅動測試中,它的測試用例是包含在資料檔案而不是在腳本中,對于資料而言,腳本僅僅是一個“驅動器”,或者是一個傳送機構。

然而資料驅動測試不同于表驅動測試,盡管導航資料并不包含在表結構中。

在資料驅動測試中,資料檔案中隻包含測試資料。

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

混合測試架構是由大部分架構随着時間并經過若幹項目演化而來的。

  • common目錄:

    一些公共方法存放目錄。
    • 封裝請求

      send_method.py

      :封裝接口請求方法。
    • 封裝擷取傳回值

      getKeyword_forResult.py

      :通過關鍵字擷取接口傳回值。
    • 讀取資料方法
  • interface目錄:

    存放接口的目錄。

    每一個接口或者一類接口來寫一個interface(也就是一個接口對應一個.py檔案)

    • 對該接口的請求:用于單接口測試
    • 根據業務擷取接口傳回值:用于關聯接口測試
  • script目錄:

    存放測試用例的目錄。

    也可以指令為testCase目錄。

    接口測試用例包括:

    • 單接口測試用例
    • 關聯接口測試用例
  • Config目錄:

    存放配置檔案。配置一些常量,例如資料庫的相關資訊,接口的相關資訊等。
  • Data目錄:

    存放公共部分資料,比如測試資料,excel檔案等等。
  • Log目錄:

    存放logging日志資訊。
  • Reports目錄:

    存放接口測試報告目錄。
  • runMain.py檔案:

    主程式入檔案口,用于執行case。

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

Student Management System Interface testing framework

建立一個測試項目

SMSITF

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

同理建立如下目錄:

  • interface

    目錄:存放接口的目錄。
  • script

    目錄:存放測試用例的目錄。
  • Config

    目錄:存放配置檔案。配置一些常量,例如資料庫的相關資訊,接口的相關資訊等。
  • Data

    目錄:存放公共部分資料,比如測試資料,excel檔案等等。
  • Log

    目錄:存放logging日志資訊。
  • Reports

    目錄:存放接口測試報告目錄。

建立好後如下圖:

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

接下來我們要一步一步實作這個架構裡邊的功能。

Dictionary

Python Package

目錄說明:

Dictionary

在Pycharm中就是一個檔案夾,放置資源檔案,該檔案夾其中并不包含

__init.py__

檔案。

Python Package

檔案夾會自動建立

__init.py__

檔案,換句話說

Python Package

就是建立一個目錄,其中包括一組子產品和一個

__init.py__

一些公共的方法,要寫在

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))

           

該檔案是封裝處理傳回值結果的一些方法。

我們需要用到一個Python中的子產品

JsonPath

,下面就先來介紹一下

JsonPath

子產品。

(1)

JsonPath

介紹

用來解析多層嵌套的Json資料。

JsonPath

是一種資訊抽取類庫,是從JSON文檔中抽取指定資訊的工具,提供多種語言實作版本,包括:

Javascript

Python

PHP

Java

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

使用文檔)

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

XPath JSONPath 描述

/

$

根節點

.

@

現行節點

/

.

or

[]

取子節點

..

n/a 取父節點,Jsonpath未支援

//

..

就是不管位置,選擇所有符合條件的條件

*

*

比對所有元素節點

@

根據屬性通路,Json不支援,因為Json是個Key-value遞歸結構,不需要屬性通路。

[]

[]

疊代器标示(可以在裡邊做簡單的疊代操作,如數組下标,根據内容選值等)
|

[,]

支援疊代器中做多選。

[]

?()

支援過濾操作.

()

支援表達式計算

()

分組,JsonPath不支援

"""
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))

           

每一個接口或者一類接口封裝成一個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))

           

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()


           

(1)準備資料

先建立一個Excel表格,裡邊填寫如下資料

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

把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()

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