(一)接口測試框架的思想
自動化測試框架不是一個模式,而是一種思想和方法的集合,通俗的講就是一個架構。
為了更好的了解自動化測試框架,應該對以下幾種自動化測試框架思想有一定的認知:
- 模塊化思想
- 庫思想
- 數據驅動思想
- 關鍵字驅動思想
以上僅僅是代表了一種自動化測試的思想,並不能定義為框架。
上面講到框架=思想+方法,於是演化了以下五種框架:
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
目錄:存放接口測試報告目錄。
創建好后如下圖:
接下來我們要一步一步實現這個框架里邊的功能。
Dictionary
和Python 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文檔中抽取指定信息的工具,提供多種語言實現版本,包括: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
使用文檔)
(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()
以上就完成了一個最簡單,最基礎的接口自動化測試框架的搭建。