服务端测试知识汇总
目錄
?
服務端測試思想
經濟學?度
?字塔模型
技術?度
HTTP協議?
三次握??
HTTP完整請求?
通信模式?
URI信息
請求?法?
請求狀態碼?
請求/響應頭
常?請求數據格式?
COOKIE請求流程?
?SESSION請求流程
TOKEN請求流程?
API測試維度?
單接?測試
多個接?測試
開放平臺API
Requests實戰
Requests概述
GET?法實戰
POST?法實戰
SESSION處理
?token實戰
Requests封裝
輕量級測試框架-Tavern框架
Tavern基本使?
MockServer
moco?
mock
?
服務端測試思想
經濟學?度
在軟件測試中,?般性的我們建議測試越早投?進去,最后它的成本?較低,這是因為越早期的投?進去進?整個測試,能夠發現底層架構的設計是否合理,以及能夠盡早的發現問題,從?更快的解決問題,這樣修復問題它的成本?較低。如發現的問題可能需要顛覆?前的架構設計和MQ中間件的交互,那么這個時候,這個問題在早期發現的時候,修復它的成本是很低的,如果到后?產品集成到?起發現了該問題,修復它的成本是?常?的。
?字塔模型
?字塔的模型是軟件測試??最具有指導思想的模型之?,在這個模型??,它通過更加直觀的?式把軟件測試從宏觀的思維分為三層,最底層的是單元測試,中間層是API測試,最上層是UI?動化測試,如下所示:
在?字塔的模型中,在測試分為三個維度來進?思考,分別是單元,服務和UI三個層級。這地?主要的說下 服務層的測試,在服務層的測試維度中,主要針對的是業務接?的測試,來驗證接?功能是否完整,如內部邏輯, 異常處理。這樣的?的是驗證接?它是否穩定,所以接?的測試相對???較容易?且更加?效,測試?例的維護成 本也低。有很多主流的測試?具都可以做接?測試,如PostMan,JMeter,SoupUi等,除了?具還有在Python語?中 很多的第三?的庫都是可以來做接?測試的,如:urllib,requests,aiohttp等。在新的基于微服務的架構模型中,我們會針對?字塔的測試模型做?個微型的調整,具體調整為如下:??
?
在如上這個測試模型中,在?字塔的底部從下往上移動,應該投?的測試越來越少,這也符合軟件測試經濟學的成本思想。應該更多精?的投?更多底層的測試。底層的測試將主要就包含了在微服務的架構模型中,針對單個服務的測試,多個服務多實例的測試,以及服務與服務之間的調?和測試,以及服務之間的數據?致性,和服務 與MQ中間件之間的交互,以及服務與DB層?的交互。?
技術?度
- ?企業從傳統架構往saas,paas化架構轉型
- ?數據技術的全?落地,那么就演變?來的是?數據平臺中資源調度,資源管理,資源優先級,資源合理化的使?
- 技術的復雜性,產品的技術架構呈現混沌形態
- 如何在不確定性的技術架構下經濟形態下保持業務的?速發展
HTTP協議?
在ISO的模型中(7層?絡協議分別是:物理層,數據鏈路層,?絡層,傳輸層,會話層,表示層,應?層)上下層之間進?交互時需要互相遵守的約定叫“接?”,同?層之間的交互所遵守的約定叫“協議”。所謂協議就是客戶端與服務端交互的語?是?致的,下?演示交互不?致的情況,具體如下:
?
?協議?致的時候,溝通是沒有任何的障礙的,如下所示:
三次握??
當然在這個過程中,我們還需要關注三次握?之間的?絡交互,它的具體信息為Client端發送連接請求報?,Server端接受連接后回復ACK報?,并為這次連接分配資源。Client端接收到ACK報?后也向Server段發送ACK報?,并分配資源,這樣TCP連接就建?了。總結三次握?具體為:
- 第?次握?:起初兩端都處于CLOSED關閉狀態,Client將標志位SYN置為1,隨機產??個值seq=x,并將該數據包發送給Server,Client進?SYN-SENT狀態,等待Server確認;
- 第?次握?:Server收到數據包后由標志位SYN=1得知Client請求建?連接,Server將標志位SYN和ACK都置為1,ack=x+1,隨機產??個值seq=y,并將該數據包發送給Client以確認連接請求,Server進?SYN-RCVD狀態,此時操作系統為該TCP連接分配TCP緩存和變量;
- 第三次握?:Client收到確認后,檢查ack是否為x+1,ACK是否為1,如果正確則將標志位ACK置為1,ack=y+1,并且此時操作系統為該TCP連接分配TCP緩存和變量,并將該數據包發送給Server,Server檢查ack是否為y+1,ACK是否為1,如果正確則連接建?成功,Client和Server進?ESTABLISHED狀態,完成三次握?,隨后Client和Server就可以開始傳輸數據。?
? ? ? ? ? ? ? ??
HTTP完整請求?
?HTTP是應?層的協議,它不需要刻意的去關注底層?絡傳輸層協議的東?。在整體應?層的協議中,通俗的說在整個API的測試維度上,需要關注的是?個完整的HTTP請求流程,請求?法,請求頭響應頭,COOKIE請求流程,SESSION的請求流程和TOKEN的請求流程,以及HTTPS的請求流程。在微服務的架構模式下,使?的也是輕量級的通信模式(REST API),在微服務的架構模式中,需要清楚的是它的通信可以分為同步通信模式和異步通信模式,或者更加具體本質的說就是請求/響應和異步請求/響應(發布/訂閱模式)。在API的?動化測試中,我們更多關注的是HTTP應?層的交互,因為即使主流的架構如微服務架構,它的通信模式也是基于REST API輕量級的通信模式。在HTTP的協議中,?個完整的HTTP請求流程具體為:
- 客戶端與服務端之間建?TCP的連接請求
- 客戶端向服務端發送Request的請求
- 服務端Response相應回復給客戶端
- 客戶端與服務端之間關閉TCP的連接請求
更多關于http請求可以關注?:?HTTP協議_菜鳥學識的博客-CSDN博客
通信模式?
同步通信
在客戶端與服務端在進?交互的時候,通信模式主要分為同步通信和異步通信。同步通信簡單的可以理解為客戶端發送請求給服務端,服務端必須得回應客戶端的請求。所以同步通信它存在如下的缺點,具體為:
- 容易超時,客戶端發送請求后,服務端遲遲沒有回應客戶端的請求
- 如果請求是存在?的計算量和邏輯存在問題,就會導致請求堵塞,后?的都積壓
異步通信
由于同步交互存在超時以及堵塞的情況,所以也就有了異步的交互。在異步的交互中,客戶端和服務端互相不需要關注對?的存在,只需要關注對應的MQ的消息,客戶端與服務端的交互主要是會通過MQ的消息中間件作為消息的傳遞來進?交互
URI信息
URI是英?單詞Uniform Resource Identifier的簡寫,主要是?來識別被?于互聯?主?地址(郵箱地址)等。URL的英?單詞是Uniform Resource Locator,URL主要?來表示互聯?中資源的具體地址。URL與URI中,URL是?個狹義的概念,?URI它更加具備深度和?度,?如URI可以?于除WWW之外的其他應?層協議中。針對該地址,可以分解為如下的信息:
- Https://:表示的是應?層的協議
- Item.jd.com:服務器域名地址信息
- 12516591.html:具體的資源地址信息
請求?法?
在HTTP的應?層協議中,常?的請求?法具體為GET,POST,PUT,DELETE的請求?法,具體如下所示:
請求狀態碼?
當客戶端向服務端發送?個請求后,服務端響應回復返回給客戶端,在返回的信息中會包含?個HTTP請求頭的狀態碼信息?以響應客戶端的請求。在?站https://http.cat中可以看?各個不同表情的狀態碼的顯示,如調?https://http.cat/504就會顯示如下對應的信息。常?的狀態碼具體為:
- 200 請求成功
- 301 永久重定向
- 302 臨時重定項
- 400 Bad Request 客戶端請求錯誤
- 401 Unauthorized
- 403 Forbidden
- 404 請求的資源不存在
- 405 不被允許的請求?法
- 500 服務器內部錯誤
- 504 GateWay Timeout
請求/響應頭
常?請求數據格式?
COOKIE請求流程?
?HTTP狀態是?狀態的協議,所以也就導致了COOKIE技術的發展,早期的產品基本對于產品認證體系使?的都是COOKIE的技術,COOKIE它是存儲在客戶端,在安全?度上不怎么友好
cookie是存儲在客戶端記錄客戶信息的一小段文本,由服務器生成發送給瀏覽器,下一次請求時會把該cookie發送給服務器(組成:key value,有效域,失效時間,安全標志(https))
?SESSION請求流程
session是存儲在服務器上,客戶第一次發送請求,服務器生成一個sessionId,并返回給客戶端通過cookie,客戶端再次發送請求給服務器的時候帶著cookie(有sessionId),服務器就知道發請求的是誰了
?具體我們以?個登錄為案例,login的請求?法是登錄,index是訪問主?,具體如下:
TOKEN請求流程?
token是產生的過程也是服務端返回一個帶簽名的token,存儲在客戶端,再次請求的時候header里帶上token,服務器以同樣的算法對數據進行計算比較,和session相比,以token的形式,服務端不用存儲sessionId了
API測試維度?
單接?測試
接?維度總結如下?點:
- 驗證必填參數是否為空
- 驗證參數的數據類型是否做了校驗
- 驗證參數的字段?度是否做了校驗
- 接?的安全性校驗和性能校驗
對單個API的測試,如果測試的API涉及到?付以及與?錢有關系的接?,都需要考慮API的安全測試,可以從下??個維度來思考,分別是:?
是否增加了反爬?的機制
是否增加了請求次數的限制
是否增加了對應的請求頭信息
是否增加了鑒權的認證信息(基本認證,常規認證,?定義認證)
是否對請求進?了加密
是否在被請求的服務端增加了IP的限制(?名單設置和IP的限制請求)
API的性能測試主要是基于服務的測試,可以使?常規的測試?具如JMeter測試?具來進?這部分的測試。?
多個接?測試
單個接?測試是必要的,但是?法保障到全鏈路的產品質量保障,所以需要基于產品全鏈路的質量保障,也就是業務場景的測試,簡單的說就是通過API的測試技術,模擬?的操作?為,實現產品業務場景的覆蓋,這種覆蓋包含了產品正常的業務邏輯以及異常的程序邏輯判斷。在基于業務場景的測試中,需要考慮的是參數上下關聯的解決?案和思路,如有?個圖書管理系統,可以增加書籍,查看增加書籍的信息,修改書籍的信息以及刪除數據的信息,那么在鏈路的測試場景設計中,需要考慮的是添加書籍信息成功后,需要拿到書籍的ID,這樣在后?的業務測試中才能夠對添加的書籍信息進?信息的查詢,信息的修改和信息的刪除。這地?就會涉及使?到函數的返回值,把添
加書籍成功后書籍ID通過函數返回值返回后,在下個請求中調?這個變量。如編寫?個函數返回值的代碼具體如下:
開放平臺API
在實際的?作場景中,經常涉及到對應和第三?公司的對接,或者說公司提供給第三?公司的API,這樣的API我們稱呼為開放平臺,也就是open api。針對開放平臺,本質上我們可以理解為“賦能”。open api都是有加密的?式的,?般業界都是有統?的標準來進?加密,以及進?很難破解的過程。
Requests實戰
Requests概述
Requests在官?的?檔中,有這么介紹的?句話,具體為:HTTP For Humans,翻譯過來就是:“讓HTTP服務?類”。Requests是?常優秀的?個Python的第三?庫,它在HTTP的應?層的協議中,客戶s端與服務端的交互請求?常的輕量級,交互?常的友好。下?還是通過具體的案例代碼來演示下Requests的基本使??法。
GET?法實戰
在處理GET請求?法的時候,我們通常需要處理url中的請求參數,如
https://www.xx.com/name=wuya&age=18,下?具體演示下這部分的應?,案例代碼如下:
再次來看params的請求參數的應?,具體如下:
import requests,json def getMethod():params={'name':'wuya','age':18}r=requests.get(url='http://httpbin.org/get',params=params)print(r.url)if __name__ == '__main__':getMethod()特別注意:不管是那個請求?法,凡事路徑的參數,我們都是可以使?params的參數來進?解決的。
POST?法實戰
在POST的請求?法中,它的形式參數存在兩種,?種是json,還有?個是data,具體源碼為:
def post(url, data=None, json=None, **kwargs):r"""Sends a POST request.:param url: URL for the new :class:`Request` object.:param data: (optional) Dictionary, list of tuples, bytes, or file-likeobject to send in the body of the :class:`Request`.:param json: (optional) json data to send in the body of the :class:`Request`.?般性的,請求數據格式為application/json中,如果使?json的參數,數據是字典的數據類型,如果參數是data,數據類型是字符串的類型。針對登錄的微服務來進?測試,具體測試代碼如下:
import requests,json dict1={"password":"admin","sex":"男","age":18 } def jsonRequest():'''請求參數為json'''r=requests.post(url='http://localhost:5000/login',data=json.dumps(dict1),headers={'content-type':'application/json'})print(r.json()) def dataRequest():'''請求參數為data'''r=requests.post(url='http://localhost:5000/login',data=dict1,headers={'content-type':'application/json'})print(r.json())def dataStrRequest():'''請求參數為data,但是進?了序列化的處理'''r=requests.post(url='http://localhost:5000/login',data=json.dumps(dict1),headers={'content-type':'application/json'})print(r.json())if __name__ == '__main__':while True:f=int(input('1、json 2、data 3、dataStr\n'))if f==1:jsonRequest()elif f==2:dataRequest()elif f==3:dataStrRequest()else:break?執?結果信息如下:
1、json 2、data 3、dataStr 1 {'message': {'username': '?戶名不能為空'}} 1、json 2、data 3、dataStr 2 {'message': 'Failed to decode JSON object: Expecting value: line 1 column 1 (char 0)'} 1、json 2、data 3、dataStr 3 {'message': {'username': '?戶名不能為空'}} 1、json 2、data 3、dataStr 4SESSION處理
我們就以登錄為案例,把登錄成功后的token信息返回,然后再下次請求的時候帶上它登錄成功后的登錄信息。
import requestsdef login():r = requests.post(url='https://home.51cto.com/index?reback=https%3A%2F%2Fedu.51cto.com%2Fcenter%2Fuser%2Findex%2Flogin-success%3Fsign%3Da0c8BVMJUQNUBwQIVFFTAlABAQBQCAEGUFVRU1ZQTBVASwgaTFwFRkoCBVsQXR9QW10eAQReQwEUTQJZFkpLBB9UV1YXTBNWFhhXVxFAQlZVAFJa&iframe=0&is_go_to_user_set_mobile=1',headers={"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.83 Safari/537.36","Content-Type": "application/x-www-form-urlencoded","Referer": "https://home.51cto.com/index?reback=https://edu.51cto.com/center/user/index/login-success?sign=a0c8BVMJUQNUBwQIVFFTAlABAQBQCAEGUFVRU1ZQTBVASwgaTFwFRkoCBVsQXR9QW10eAQReQwEUTQJZFkpLBB9UV1YXTBNWFhhXVxFAQlZVAFJa&relogin=1","Cookie": "_ssxmod_itna2=QqjxBDyDgG5eq4BPGKHnrIKPhxGr27bo4iKG8D8d6WeGXub4GaiQQIkvCr0Ix8gno5uYB2hqHVCW35+OtKAebtPirQPvqLtQm6Kn1d2ZwFHHzGSnuWcratOFCYTTNBfRb92Vu0zQT6PiexBNYnWOiaRWTQvOtBhH=rYF2151epK1EEoFO4PxAgK4ED07KDrbiFqaqQrTA9wdY2STUtE5qD08DYI54D==; acw_tc=2760779416489697187698597ef95319033b93239ae9f5b89268f81cb55c6c; _ourplusReturnCount=3; _ourplusReturnTime=122-4-3-15-8-39; login_from=edu.51cto.com; reg_from=edu.51cto.com; Hm_lpvt_844390da7774b6a92b34d40f8e16f5ac=1648969720"},data={"_csrf": "dzRpQVd3emUHcjgGPARDVgViRCUcMRzPTA5X1wnAwFNEg==","LoginForm[username]": "13222245195","LoginForm[password]": "123","show_qr": "0"})return r.cookies#print(r.status_code) #print(r.cookies) #print(r.text)def profile():r = requests.get(url='https://edu.51cto.com/center/course/lecturer/course',cookies=login())print(r.status_code)print(r.text)profile()?token實戰
import requestsdef login():r = requests.post(url='http://23.95.142.233:8000/login/auth/',json={"username": "1322245195", "password": "168888"})return r.json()['token']def index():r = requests.get(url='http://23.95.142.233:8000/interface/index',headers={'Authorization': 'JWT {0}'.format(login())})print(r.status_code)print(r.text)Requests封裝
import requestsclass Requests:def request(self,url,method='get',**kwargs):if method=='get':return requests.request(url=url,method=method,**kwargs)elif method=='post':return requests.request(url=url,method=method,**kwargs)elif method=='put':return requests.request(url=url,method='put',**kwargs)elif method=='delete':return requests.request(url=url,method='delete',**kwargs)def get(self,url,**kwargs):return self.request(url=url,**kwargs)def post(self,url,**kwargs):return self.request(url=url,method='post',**kwargs)def put(self,url,**kwargs):return self.request(url=url,method='put',**kwargs)def delete(self,url,**kwargs):return self.request(url=url,method='delete',**kwargs)輕量級測試框架-Tavern框架
Tavern基本使?
Tavern是?款輕量級的測試框架,集合Pytest的測試框架,可以把測試的描述信息(API的請求信息)以及測試斷?都可以編寫在Yaml的?件中,然后結合Pytest的測試框架直接解析Yaml就可以來批量的執?。在Tavern的測試框架中,它追求的是“Easier API testing”的設計理念,不過從?前實踐的應?來看,它是符合這樣的?種簡單的模式的,Easy to Write, Easy to Read and Understand。下?我們?先來安裝它,安裝的命令為:
pip3 install tavern==1.12.2下?我們把之前編寫的登錄服務的測試?例,整合到Yaml的?件中,Yaml?件的名稱為:test_login.tavern.yaml,具體Yaml?件的內容為:
test_name: 登錄微服務GET請求stages:- name: 登錄微服務GET請求request:url: http://127.0.0.1:5000/loginmethod: GETresponse:status_code: 200json:status: 0msg: okdata: this is a login page--- test_name: 用戶名信息為空stages:- name: 用戶名信息為空request:url: http://127.0.0.1:5000/loginmethod: POSTdata:password: adminage: 18sex: 男response:status_code: 400json:message:username: 用戶名不能為空--- test_name: 密碼信息不能為空stages:- name: 密碼信息不能為空request:url: http://127.0.0.1:5000/loginmethod: POSTdata:username: adminage: 18sex: 男response:status_code: 400json:message:password: 賬戶密碼不能為空--- test_name: 年齡不能為正整數stages:- name: 年齡不能為正整數request:url: http://127.0.0.1:5000/loginmethod: POSTdata:username: adminpassword: adminage: asdsex: 男response:status_code: 400json:message:age: 年齡必須為正正數--- test_name: 驗證性別只能是男或者女stages:- name: 驗證性別只能是男或者女request:url: http://127.0.0.1:5000/loginmethod: POSTdata:username: adminpassword: adminage: 18sex: asdresponse:status_code: 400json:message:sex: 性別只能是男或者女--- test_name: 驗證性別只能是男或者女stages:- name: 驗證性別只能是男或者女request:url: http://127.0.0.1:5000/loginmethod: POSTdata:username: adminpassword: adminage: 18sex: 男response:status_code: 200json:username: adminpassword: adminage: 18sex: 男?執?的命令具體為:
python3 -m pytest -v -s test_login.tavern.yamlMockServer
被測系統在運?時候會時常依賴另外?些系統,依賴會導致測試的復雜化,并減慢測試速度,基于這樣的現實考慮,就需要?種單獨測試被測系統的?法,解決?案是測試替身(Test double)來消除被測系統的依賴性,測試替身是?個測試對象,該對象負責模擬依賴項的?為。什么是mock?mock簡單的理解就是開發在開發的過程中,需要依賴?部分的接?,但是對?沒有提供或者環境等等情況,總之是沒有,那么開發使?mock server??來mock數據,?便??正常的進?開發和對編寫的功能進??測。
moco?
在https://github.com/dreamhead/moco中下載moco-runner-0.11.0-standalone.jar,啟動它的前提是需要搭建好Java的開發環境,下來我們簡單的編寫?個登錄的,?編寫的login.json字符串:
[{"request":{"method":"post","uri":"/login","json":{"username":"admin","password":"admin","roleID":22}},"response":{"json":{"username":"wuya","userID":22,"token":"asdgfhh32456asfgrsfss"}}} ]?下來來啟動具體的moco的服務信息,啟動的命令為:
java -jar moco-runner-0.11.0-standalone.jar http -p 12306 -c login.json#啟動后輸出的信息為: 16 ?? 2021 18:36:51 [main] INFO Server is started at 12306 16 ?? 2021 18:36:51 [main] INFO Shutdown port is 53659下來我們調?下該模擬的請求信息,使?的?具是PostMan,具體發送請求后就會返回我們模擬的信息,具體如下;
??般??,如果響應數據是很多的情況下,其實也是可以分離到JSON?件的,?如針對上?的案例,可以把響應數據分離到其他的JSON?件的,那么具體調整后的login.json?件信息為:
[{"request":{"method":"post","uri":"/login","json":{"username":"admin","password":"admin","roleID":22}},"response":{"file": "login_response.json"}} ]??login_response.json?件的內容就為:
{"username":"wuya","userID":22,"token":"asdgfhh32456asfgrsfss" }mock
moco-runner-0.11.0-standalone.jar中,通過編寫json的?件來實現,那么我們現在來看Python之中的mock,那么怎么理解mock了,mock翻譯過來就是模擬的意思,也就是說,它是將測試對象所依存的對象替換為虛構對象的庫,該虛構對象的調?允許事后查看。在python的2.x版本中,它是屬于第三?的庫,需要單獨的安裝,在python3.3的版本以后,不需要單獨的安裝,直接導?就可以了。在mock中,使?return_value來模擬?個對象,來驗證被測試的程序。
import pytest import mockclass MockPay(object):def pay(self):'''?付接?'''passdef pay_result(self):'''模擬?付接?'''result=self.pay()if result['payType']=='aliPay' and result['status']:return 'aliPay'elif result['payType']=='CMB' and result['status']==False:return 'China Merchants Bank welcomes your use'else:return 'An unknown error is required'return result['msg']def test_aliPay():'''模擬?付寶?付?式'''objPay=MockPay()mockValue={'payType':'aliPay','msg':'?付寶','status':True}#依據object的?式查找需要的mock對象objPay.pay=mock.Mock(return_value=mockValue)assert objPay.pay_result()=='aliPay' def test_cmb():'''模擬招商銀??付?式'''objPay=MockPay()mockValue={'payType':'CMB','msg':'China Merchants Bank welcomes your use','status':False}#依據object的?式查找需要的mock對象objPay.pay=mock.Mock(return_value=mockValue)assert objPay.pay_result()=='China Merchants Bank welcomes your use' def test_unknown(mocker):'''模擬未知錯誤信息'''objPay=MockPay()mockValue={'payType':'wuya','msg':'An unknown error is required'}#依據object的?式查找需要的mock對象objPay.pay=mock.Mock(return_value=mockValue)assert objPay.pay_result()=='An unknown error is required' if __name__ == '__main__':pytest.main(["-s","-v","test_moco.py"])?
?
總結
- 上一篇: 知识:什么是进销存软件系统?
- 下一篇: 《鬼谷子》飞箝第五(译文)