看完這篇部落格,我保證你肯定會做介面測試了。
最近給公司的新員工培訓web api介面測試,發現這一塊的內部需求還比較大,不僅僅是新員工,包括一些經常寫介面測試老員工,對介面也是似懂非懂的,所以我絕對有必要寫一篇部落格來普及下。
在我們公司內部,一般使用ruby或者python語言來做介面測試,這篇檔案主要是講解使用python語言來做介面測試。
如果要做介面測試,其實只要會抓包,會組裝http請求頭和請求體,會檢查http響應頭和響應體,就可以可以,所以我們需要需要掌握下面這些知識!!!另外還需要掌握一些常用的測試框架,比如unittest和pytest等
1、python語言requests庫
2、http協議基本知識,包括請求頭,響應頭、請求體、響應體
3、session-cookie(如果大家對session和cookies不熟悉,可以看我之前寫 的部落格)
https://www.cnblogs.com/bainianminguo/p/9147418.html
https://www.cnblogs.com/bainianminguo/p/8850043.html
4、fiddler抓包工具
5、測試框架,這裡不會講,大家有興趣可以看下我之前寫的部落格,介紹unittest測試框架
https://www.cnblogs.com/bainianminguo/p/11706244.html
https://www.cnblogs.com/bainianminguo/p/11616526.html
下面進入正題,聽我娓娓道來。
一、http協議
1、簡介
web api介面大都是基於http協議的,所以要進行介面測試,首先要了解HTTP協議的基礎知識。
HTTP協議全稱是超文字傳輸協議。由於HTTP最初是用來在瀏覽器和網站伺服器之間傳輸超文字的(網頁,視訊,圖片等)資訊的。由於HTTP簡潔易用,後來,不僅僅是瀏覽器和伺服器之間使用它,伺服器和伺服器之間,手機app和伺服器之間,都廣泛的採用,成了一個軟體系統間通訊的首選協議之一。
HTTP協議有好幾個版本,包括0.9、1.0、1.1、1.2,當前最廣泛使用的是HTTP/1.1版本
HTTP協議最大的特點是通訊雙方分為客戶端和服務端。
由於目前HTTP是基於TCP協議,所以要進行通訊,客戶端必須先河服務端建立TCP連線。而且HTTP雙方的資訊互動,必須要這樣一種形式
a、客戶端先發送http請求(request)給伺服器
b、然後伺服器傳送http響應(response)給客戶端
c、特別要注意,在http協議中,服務端是不能主動發訊息給客戶端的
流程圖如下
http1.1版本先建立TCP連線,然後在這個連線內可以進行多次互動資訊,這裡注意,是客戶端主動給服務端發請求的
二、http請求
下面是http的get請求和http的post請求的示例
GET /mgr/login HTTP/1.1 Host: 192.168.3.1 User-Agent:Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36 Accept-Language:zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4 Accept-Encoding:gzip, deflate, sdch
POST /api/test HTTP/1.1 Host:192.168.3.1 Origin:http://192.168.3.1 Referer:http://192.168.3.1/html/index.html User-Agent:Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36 Content-Type:application/json;charset=UTF-8 Content-Length:214 Accept-Language:zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4 Accept-Encoding:gzip, deflate {"csrf":{"csrf_param":"35iUJau6mdmmJeIg0N8W80OmoMK8A2Kr","csrf_token":"KfKSfpH0hnsSc0uQyX6ZUB8i8KRFSZ0C"},"data":{"username":"admin","firstnonce":"c7eb46830667147fc62838e7ba9a0c09187d28bafa45b133897efa9d4e46a880"}}
一個http請求訊息由下面幾個部分組成
a、請求行 request line
是http的第一行的內容。表示要操作什麼資源,使用的http協議的版本是什麼,裡面包含了三部分訊息。請求的方法、操作資源的地址、協議的版本號
GET https://securepubads.g.doubleclick.net/pagead/adview?ai=C3a-FX0L-XZ7nH8Hn2gT1vpKQBofP2MxZl96OgJYKsfz66E0QASC3tpYCYJ0ByAEBqQIDDuevOSiDPuACAKgDAcgDCqoE5AFP0PxttPf1dpFDQI04YpU7K4Qhq0WGx-H6233i9kddKMRmZ8rfGeWBQrr479rzo9D8pahF4GnAvGpwUHZntCL7HcXpQi_xqWQt3alVu7iKmqOQ2r6wzaMJli0vfW_rWq9l6hMmC3THotaE3aaCu8-yQ18_cRaCfgKTCRRx0Eze2gWYfJic33lRDI5NHSVfSlXDsc3SItNtoxG4tZhzRdw3omfX6oWwyvxUVboGzJwd8Rfij2Abfe4gFMviIlfhgVkNNRVv3U-Zhi6Xu5dTGhbV952-dpPUto1PgnRuTQpfsnvJ9UbABKmyupce4AQBkgUECAQYAZIFBAgFGASAB9GIrh6oB47OG6gH1ckbqAeT2BuoB7oGqAfy2RuoB6a-G6gH7NUb2AcB8gcEEPCNAtIIBwiAYRABGB2ACgHICwHYEwKIFAE&sigh=RmPL7gCMtsg&tpd=AGWhJmu9ZlDGyMSGWoSTQKbLoOl12TOWdSdugT6uZH2DizpViw HTTP/1.1
b、請求方法
Get請求
從伺服器上獲取資源資訊,這是一種最常見的請求
比如要從伺服器獲取網頁資源,獲取圖片資源,獲取使用者資訊資料等
Post請求
新增資源資訊到伺服器進行處理,例如要新增使用者資訊,上傳圖片資料到伺服器等,具體的資料資訊,通常在HTTP請求的訊息體中,這個後面會講
Put請求
請求伺服器更新資源資訊
比如要更新使用者、姓名地址等等
具體的更新的資料資訊,通常在HTTP的訊息體中,後面會講
Delete請求
請求伺服器刪除資源資訊
比如要刪除某個使用者,某個資源等等
HTT片協議還有許多其他的方法,比如PATCH,HEAD等,不是特別常用,暫且不講
c、資源地址
d、請求頭
這裡業務大家有個疑問,我的http請求是建立在tcp連線的基礎上的,為什麼這裡還要傳遞一個host呢?因為我們知道ip地址了,但是這個ip地址上可能有多個網站,所以這裡要指定我們要訪問的具體是哪個網站
請求體的http請求下面的內容,裡面存放一些資訊。
比如請求傳送的服務端的域名是什麼,希望接受的響應訊息使用語言,請求訊息體的長度等等;
通常請求頭有好多個,一個請求頭佔據一行
單個請求頭的格式是:名字:值
e、請求體
請求的url,請求頭中可存放一些資料資訊,但是有些資料資訊,往往需求存放在訊息體中國;特別是post,put的請求,新增,修改的資料資訊通常都是存放在請求訊息體中的;
如果HTTP請求有訊息體,協議規定,需要在訊息頭和訊息體之間插入一個空行,隔開他們;
請求訊息體中儲存了要提交個服務端的資料資訊
比如:客戶端要上傳一個檔案給伺服器,就可以通過http請求傳送檔案資料給服務端;
檔案的資料就應該在請求的訊息體中
請求的訊息體通常是某種格式的字串,常見的有三種,但是最常用的還是json格式
Json
Xml
www-form-urlencoded
後面會有詳細的描述
request payload就是一個請求體,下面這個格式就是Json格式的訊息體
請求體中不僅僅可以存放字串,還可以放二進位制資訊,比如以下視訊、文字之類的,用於我們上傳檔案的場景,不過通常介面測試不會涉及二進位制資訊,都是字串資訊,後面我會專門寫一篇部落格來介紹如何上傳檔案
2、http響應
響應的訊息我們重點關注狀態碼
a、2xx
通常表示請求訊息沒有問題,而且伺服器也正確處理了
b、3xx
這是重定向響應,常見的是是301、302,表示客戶端的這個請求的url地址已經改變了,需要客戶端重啟發起一個請求到另外一個url
c、400
表示客戶端請求不符合介面要求,比如格式完全錯誤
d、401
表示客戶端需要先認證才能傳送請求
e、403
表示客戶端美譽哦許可權要求伺服器處理這樣的請求,比如普通使用者的沒有管理員的許可權
f、404
表示客戶端方法的url不存在
g、5xx
表示服務端在處理請求中,傳送了未知錯誤,通常是服務端的程式碼設計的問題,或者服務端系統出了故障了
有了以上的基礎,我們就可以做web的介面測試了
二、介面測試
1、什麼是介面測試
我們通常說的介面測試,其實就是對軟體系統的訊息互動介面的引數,訊息互動介面是軟體系統和其他軟體系統互動的那部分,比如,你正在用瀏覽器使用一個網站,瀏覽器和後端伺服器之間就是訊息互動的;在比如,你手機上使用美團訂餐,美團app和美團服務器之間,也是訊息互動的,當你提交訂單,使用功能微信支付的時候,美團服務器和微信伺服器之間也是通過訊息互動的
介面測試就是
依據介面規範,寫出測試用例
使用軟體工具,直接通過訊息介面對被測系統進行訊息收發
驗證被測系統行為是否正確
目前軟體系統之間的訊息介面大部分是基於HTTP協議收發的
HTTP協議的特點是,客戶端發出一個HTTP請求給服務端,服務端就返回一個HTTP相應,好像API程式呼叫;
所有介面測試通常又被稱為API介面測試或者WEB API介面測試
API介面傳遞資料資訊是通過HTTP協議進行收發的,網站獲取網頁,圖片,css等資源,也是通過HTTP協議進行收發的
那麼這兩者有什麼區別呢?為什麼獲取網頁,圖片這些HTTP訊息不叫做API介面訊息呢?
網頁,圖片,css這些資源都是靜態資源,就是一個一個檔案儲存在伺服器中,獲取這些訊息,服務端直接讀取檔案,返回給客戶端即可,無需特別的資料處理
而API介面請求訊息,通常都需要服務端程式進行一番處理,比如對請求的許可權檢查,從資料庫中讀出資料,進行訊息過濾和格式轉換,最後在HTTP響應中返回給客戶端
介面測試需要工具和被測系統之間進行訊息的收發,這個工具可以是別人開發的,也可以自己開發,基於HTTP的介面測試工具有Postman,Jmeter等
2、fiddler工具
這裡我們使用python語言中的requests庫和fiddler抓包工具
Fiddler:代理式抓包
大家一定會反問,我的瀏覽器就是可以抓包了,為什麼還要安裝fiddler,多此一舉?
其實不然,因為我們是用python的requests庫去做介面測試,瀏覽器是抓不到我們發的請求的,所以需要安裝fiddler來抓包,確保我們傳送的http請求是正確的
fiddler啟動後,會啟動一個代理伺服器,監聽在8888埠上,http客戶端需要設定fiddler作為代理,把http請求訊息傳送給fiddler,fiddler轉發http訊息給服務端,服務端返回訊息也是先返回給fiddler。再由fidddler轉發給客戶端
如下圖所示
fiddler安裝後,會預設配置作業系統級別的代理,可以通過下面的方式檢視
安裝fiddler需要配置一個過濾項,因為預設fiddler是作為一個系統代理,所以fiddler抓到包會很多,所以需要配置一個過濾項
同樣,這裡的配置是支援萬用字元的
抓包
檢視原始的請求訊息
我們可以在python程式碼裡配置代理,然後通過fiddler抓包來判斷我們發的包是否準確,這裡需要配置http和https協議的代理
import requests proxies = { "http":"127.0.0.1:8888", "http1": "127.0.0.1:8888" } res = requests.get( url = "htt://www.baidu.com", proxies = proxies )
我們可以通過fiddler進行抓包
fiddler如果 要配置手機抓包代理,需要保證安裝fiddler和手機在同一個區域網中
在手機的無線網路處配置代理,代理指向執行fiddler的電腦的ip即可,埠是8888
3、requests庫的請求
a、構建請求的url引數,這個一般在get請求使用較多
什麼是url引數
比如
https://www.baidu.com/s?wd=iphone&res_spt=1
問號後面的部分wd=iphone&res_spt=1就是url引數,每個引數之間就用&隔開的。
上面的例子中有兩個引數wd和res_spt,他們的值分別iphone和1
url引數引數的格式,有個術語叫urlencoded格式
使用requests傳送HTTP請求,url裡面的引數,通常可以直接在url裡面,比如
但是有的時候,我們的url中引數裡面有特殊字元,比如引數中的值包含了一個&這個符號或者引數很多的話,我們可以採用下面的方法,構建一個字典,然後把這個字典傳遞給params引數
也可以用下面的方式傳遞url引數
res = requests.get( url = "http://www.baidu.com/", params = { "wd":"iphone", "res_spt":"1" }, proxies = proxies )
b、構建請求訊息頭
有的時候,我們需要自定義一些http的訊息頭
每個訊息頭也就是一種鍵值對的格式存放資料,在requests,只需要把抓包中的請求頭資訊放在一個字典中,然後傳遞headers即可
res = requests.get( url = "http://www.baidu.com/", headers = { "Host": "192.168.3.1", "User-Agent":"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", "Accept-Language":"zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4", "Accept-Encoding":"gzip, deflate, sdch" }, params = { "wd":"iphone", "res_spt":"1" }, proxies = proxies )
c、構建請求的訊息體
當我們進行api介面測試的時候,根據介面規範,構建的http請求,通常需要構建訊息體
http的訊息體就是一串位元組,裡面包含了一些資訊,這些資訊可能是文字,比如html網頁作為訊息體,也可能是視訊,音訊資訊
訊息體可能很短,只有一個位元組,比如字元a,也可能很長,有幾百個位元組
最常見的訊息體格式當然是表示網頁內容的html
當時在web api介面測試中,常見的HTTP訊息體的格式有三種,urlencoded,json,xml
注意:訊息體採用什麼格式,是由開發人員設計決定的,開發人員也可以自定義格式,但是我們通常不會自定義的
xml格式
前面時候了,訊息體就是存放資訊的地方,資訊的格式完全取決於設計者的需求,如果設計者決定使用xml格式傳輸一段資訊,用requests庫,只需要這樣就可以了
playload = """ <?xml version="1.0"?> <methodCall> <methodName>examples.getStateName</methodName> <params> <param> <value><i4>41</i4></value> </param> </params> </methodCall>""" res2 = requests.post( url = "http://www.baidu.com/", headers = { "Host": "192.168.3.1", "User-Agent":"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", "Accept-Language":"zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4", "Accept-Encoding":"gzip, deflate, sdch", "Content-Type": "text/xml" }, data=playload.encode("utf-8"), proxies = proxies )
由於訊息體都是位元組串,我們直接把字串使用utf-8解碼,然後傳遞給data引數即可,這裡需要注意,需要設定Content-type=text/xml
使用data引數,儲存訊息體的資料,如果傳遞的是一個字串,在http請求中,需要編碼為位元組碼,預設的編碼格式latin-1,這種編碼格式是不支援中文的;通常我們使用utf-8的編碼格式
通過fiddler抓包
檢視請求的原始資訊
Urlencoded格式
這種格式的訊息體就是一個key-value鍵值對的格式存放資料,如下所示
key1=value1&key2=value2
Requests傳送這樣的資料,當然可以直接把這種格式的字串傳入到data引數裡;但是這樣寫的話,如果引數本身就有特殊字元,比如等號,就會有歧義
我們還有更方便的方法,只需要將這些鍵值對的資料構建一個字典,如下
playload = { "key1":"value1", "key2":"value2" } res2 = requests.post( url = "http://www.example.com", headers = { "User-Agent":"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", "Accept-Language":"zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4", "Accept-Encoding":"gzip, deflate, sdch", "Content-Type": "application/x-www-form-urlencoded;charset=utf-8" }, data=playload, proxies = proxies )
這裡需要注意下面2個地方
通過fiddler抓包
先看請求頭
這裡明顯可以看到,請求頭和請求體中間有一個空行
看下請求體中的資料
Json格式的訊息體
Json字串一律用雙引號,不能用單引號
Json字串最後一個元素的後面不能加逗號
其實我們要把資料放到訊息體中,最終的資料都是位元組串,也就是把str.encode()
json格式當前被web api介面廣泛採用
json是一種表示資料的語法格式,他和python表示資料的語法非常像
json格式有兩種方式構建訊息體
方式1
playload = {"title": "test", "sub": [1, 2, 3]} res2 = requests.post( url = "http://www.example.com", headers = { "User-Agent":"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", "Accept-Language":"zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4", "Accept-Encoding":"gzip, deflate, sdch", "Content-Type": "application/json;charset=utf-8" }, json=playload, proxies = proxies )
注意下面這裡
方式2
import json playload = {"title": "test", "sub": [1, 2, 3]} res3 = requests.post( url = "http://www.example.com", headers = { "User-Agent":"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", "Accept-Language":"zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4", "Accept-Encoding":"gzip, deflate, sdch", "Content-Type": "application/json;charset=utf-8" }, data = json.dumps(playload,ensure_ascii=False).encode("utf-8"), proxies = proxies )
注意下面這裡
4、requests庫的響應
a、檢查HTTP響應狀態碼
要檢查HTTP響應的狀態碼,直接通過response物件的status_code屬性獲取
import json playload = {"title": "test", "sub": [1, 2, 3]} res3 = requests.post( url = "http://www.example.com", headers = { "User-Agent":"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", "Accept-Language":"zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4", "Accept-Encoding":"gzip, deflate, sdch", "Content-Type": "application/json;charset=utf-8" }, data = json.dumps(playload,ensure_ascii=False).encode("utf-8"), proxies = proxies ) print(res3.status_code)
執行結果發現返回的結果狀態碼就是200
如果故意寫一個不存在的地址
import requests proxies = { "http":"127.0.0.1:8888", "http1": "127.0.0.1:8888" } res = requests.get( url = "http://192.168.3.1/html/index4.html", proxies = proxies ) print(res.status_code)
執行結果發現返回的狀態碼就是404
b、檢查響應的訊息頭
要檢查HTTP響應的訊息頭,直接通過response物件的header屬性獲取
import json import requests import pprint playload = {"title": "test", "sub": [1, 2, 3]} proxies = { "http":"127.0.0.1:8888", "http1": "127.0.0.1:8888" } res3 = requests.post( url = "http://www.example.com", headers = { "User-Agent":"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", "Accept-Language":"zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4", "Accept-Encoding":"gzip, deflate, sdch", "Content-Type": "application/json;charset=utf-8" }, data = json.dumps(playload,ensure_ascii=False).encode("utf-8"), proxies = proxies ) print(pprint.pprint(dict(res3.headers)))
結果如下
c、檢查響應訊息體的文字內容
前面我們已經說過,要獲取響應的訊息體的文字內容,直接通過response物件的text屬性即可獲取
import requests import pprint # proxies = { "http":"127.0.0.1:8888", "http1": "127.0.0.1:8888" } # res = requests.get( url = "http://mirrors.sohu.com", proxies = proxies ) # # # print(res.text) print(pprint.pprint(dict(res.headers))) print(res.encoding)
響應體其實也是位元組串,但是我們呼叫text方法沒有設定解碼格式,他是怎麼解碼?他是根據響應頭的contend-type來決定解碼格式,有的時候會指定,但是大部分不會指定
我們
我們可以看到我們列印的解碼格式,和content-Type中是一樣的
如果有的時候中文解碼出來是亂碼,我們可以手動指定解碼格式
如果我們想列印響應體的位元組串可以使用content方法
5、session
a、原理
我們來思考一個問題,一個網站,比如一個購物網站,服務成千上萬的的客戶,那麼多客戶同時訪問網站,挑選物品,購物估算,都是通過hTTP請求來訪問網站的,這個網站的服務端怎麼區分每個HTTP請求呢?網站的服務端是怎麼實現的?
一種最常見的方式就是:通過Session+cookies機制
session翻譯成中文就是會話的意思
session大體的原理如下面2個圖
http協議規定了,網站的服務端放HTTP響應的訊息頭set-Cookies裡面的資料,叫做cookies資料,瀏覽器客戶端必須要儲存下來。而且後續訪問該網站,必須在http的請求頭Cookies中攜帶儲存的所有的cookie資料
使用者使用客戶端登陸服務端,服務端進行驗證,比如驗證使用者名稱和密碼,驗證通過後,服務端系統高就會為這次登陸建立一個seesion,同時建立一個唯一的sessionID。標誌這個session。然後,服務端通過HTTP響應,把sessionID告訴客戶端,客戶端在後面的HTTP請求的訊息頭,都要包含這個sessionID。這樣服務端就會知道,這個供求對應哪個session,從而知道這次的請求對應哪個使用者;
從上圖可以看出,服務端是通過HTTP的響應頭set-cookies把產生的sessionID告訴客戶端。
客戶端的後續請求,是通過HTTP請求的請求頭Cookies告訴服務端他所持有的sessionid的
b、request庫支援session的
request處理session-cookies
我們在python程式碼中如果接收到伺服器的http響應,其他set-cookies的資料怎麼儲存呢?後續怎麼樣把請求訊息頭中cookies中呢?
前面學過HTTP響應中如何獲取響應頭,構建請求怎麼設定請求頭,完全可以處理。
但是requests庫為我們這個處理
requests庫給我們提供了一個session類。通過這個類,無需我們操心cookies和session這個事情。reqeusts庫會自動幫我們儲存服務端發揮的cookies資料,HTTP請求自動在訊息頭中放入cookies資料
如下所示
import requests import json session = requests.session() playload = {"title": "test", "sub": [1, 2, 3]} proxies = { "http":"127.0.0.1:8888", "http1": "127.0.0.1:8888" } res = session.post( url = "http://www.example.com", headers = { "User-Agent":"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", "Accept-Language":"zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4", "Accept-Encoding":"gzip, deflate, sdch", "Content-Type": "application/json;charset=utf-8" }, data = json.dumps(playload,ensure_ascii=False).encode("utf-8"), proxies = proxies ) res = session.get()
&n