目錄
- (一)接口測試架構的思想
- 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目錄:
-
存放公共部分資料,比如測試資料,excel檔案等等。Data目錄:
-
存放logging日志資訊。Log目錄:
-
存放接口測試報告目錄。Reports目錄:
-
主程式入檔案口,用于執行case。runMain.py檔案:
之前分析完了接口測試架構的設計與架構,下面我們就來一步一步的完成接口自動化測試架構的實作。
為
Student Management System Interface testing framework
建立一個測試項目
SMSITF
。
項目名上右鍵 —> New —> Python Package —> 建立common目錄。
同理建立如下目錄:
-
目錄:存放接口的目錄。interface
-
目錄:存放測試用例的目錄。script
-
目錄:存放配置檔案。配置一些常量,例如資料庫的相關資訊,接口的相關資訊等。Config
-
目錄:存放公共部分資料,比如測試資料,excel檔案等等。Data
-
目錄:存放logging日志資訊。Log
-
目錄:存放接口測試報告目錄。Reports
建立好後如下圖:

接下來我們要一步一步實作這個架構裡邊的功能。
和
Dictionary
目錄說明:
Python Package
在Pycharm中就是一個檔案夾,放置資源檔案,該檔案夾其中并不包含
Dictionary
檔案。
__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
介紹
JsonPath
用來解析多層嵌套的Json資料。
JsonPath
是一種資訊抽取類庫,是從JSON文檔中抽取指定資訊的工具,提供多種語言實作版本,包括:
Javascript
,
Python
PHP
Java
JsonPath
對于 JSON 來說,相當于 XPath 對于 XML。
(2) JsonPath
安裝
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()
以上就完成了一個最簡單,最基礎的接口自動化測試架構的搭建。