最近給公司的新員工培訓web api接口測試,發現這一塊的內部需求還比較大,不只僅是新員工,包括一些常常寫接口測試老員工,對接口也是似懂非懂的,因此我絕對有必要寫一篇博客來普及下。css
在咱們公司內部,通常使用ruby或者python語言來作接口測試,這篇文件主要是講解使用python語言來作接口測試。html
若是要作接口測試,其實只要會抓包,會組裝http請求頭和請求體,會檢查http響應頭和響應體,就能夠能夠,因此咱們須要須要掌握下面這些知識!!!另外還須要掌握一些經常使用的測試框架,好比unittest和pytest等python
一、python語言requests庫web
二、http協議基本知識,包括請求頭,響應頭、請求體、響應體數據庫
三、session-cookie(若是你們對session和cookies不熟悉,能夠看我以前寫 的博客)json
https://www.cnblogs.com/bainianminguo/p/9147418.html後端
https://www.cnblogs.com/bainianminguo/p/8850043.htmlapi
四、fiddler抓包工具瀏覽器
五、測試框架,這裏不會講,你們有興趣能夠看下我以前寫的博客,介紹unittest測試框架ruby
https://www.cnblogs.com/bainianminguo/p/11706244.html
https://www.cnblogs.com/bainianminguo/p/11616526.html
下面進入正題,聽我娓娓道來。
web api接口大都是基於http協議的,因此要進行接口測試,首先要了解HTTP協議的基礎知識。
HTTP協議全稱是超文本傳輸協議。因爲HTTP最初是用來在瀏覽器和網站服務器之間傳輸超文本的(網頁,視頻,圖片等)信息的。因爲HTTP簡潔易用,後來,不只僅是瀏覽器和服務器之間使用它,服務器和服務器之間,手機app和服務器之間,都普遍的採用,成了一個軟件系統間通訊的首選協議之一。
HTTP協議有好幾個版本,包括0.九、1.0、1.一、1.2,當前最普遍使用的是HTTP/1.1版本
HTTP協議最大的特色是通信雙方分爲客戶端和服務端。
因爲目前HTTP是基於TCP協議,因此要進行通信,客戶端必須先河服務端建立TCP鏈接。並且HTTP雙方的信息交互,必需要這樣一種形式
a、客戶端先發送http請求(request)給服務器
b、而後服務器發送http響應(response)給客戶端
c、特別要注意,在http協議中,服務端是不能主動發消息給客戶端的
流程圖以下
http1.1版本先建立TCP鏈接,而後在這個鏈接內能夠進行屢次交互信息,這裏注意,是客戶端主動給服務端發請求的
下面是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協議的版本是什麼,裏面包含了三部分消息。請求的方法、操做資源的地址、協議的版本號
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格式的消息體
請求體中不只僅能夠存放字符串,還能夠放二進制信息,好比如下視頻、文本之類的,用於咱們上傳文件的場景,不過一般接口測試不會涉及二進制信息,都是字符串信息,後面我會專門寫一篇博客來介紹如何上傳文件
響應的消息咱們重點關注狀態碼
a、2xx
一般表示請求消息沒有問題,並且服務器也正確處理了
b、3xx
這是重定向響應,常見的是是30一、302,表示客戶端的這個請求的url地址已經改變了,須要客戶端重啓發起一個請求到另一個url
c、400
表示客戶端請求不符合接口要求,好比格式徹底錯誤
d、401
表示客戶端須要先認證才能發送請求
e、403
表示客戶端美譽哦權限要求服務器處理這樣的請求,好比普通用戶的沒有管理員的權限
f、404
表示客戶端方法的url不存在
g、5xx
表示服務端在處理請求中,發送了未知錯誤,一般是服務端的代碼設計的問題,或者服務端系統出了故障了
有了以上的基礎,咱們就能夠作web的接口測試了
咱們一般說的接口測試,其實就是對軟件系統的消息交互接口的參數,消息交互接口是軟件系統和其餘軟件系統交互的那部分,好比,你正在用瀏覽器使用一個網站,瀏覽器和後端服務器之間就是消息交互的;在好比,你手機上使用美團訂餐,美團app和美團服務器之間,也是消息交互的,當你提交訂單,使用功能微信支付的時候,美團服務器和微信服務器之間也是經過消息交互的
接口測試就是
依據接口規範,寫出測試用例
使用軟件工具,直接經過消息接口對被測系統進行消息收發
驗證被測系統行爲是否正確
目前軟件系統之間的消息接口大部分是基於HTTP協議收發的
HTTP協議的特色是,客戶端發出一個HTTP請求給服務端,服務端就返回一個HTTP相應,好像API程序調用;
全部接口測試一般又被稱爲API接口測試或者WEB API接口測試
API接口傳遞數據信息是經過HTTP協議進行收發的,網站獲取網頁,圖片,css等資源,也是經過HTTP協議進行收發的
那麼這二者有什麼區別呢?爲何獲取網頁,圖片這些HTTP消息不叫作API接口消息呢?
網頁,圖片,css這些資源都是靜態資源,就是一個一個文件存儲在服務器中,獲取這些消息,服務端直接讀取文件,返回給客戶端便可,無需特別的數據處理
而API接口請求消息,一般都須要服務端程序進行一番處理,好比對請求的權限檢查,從數據庫中讀出數據,進行消息過濾和格式轉換,最後在HTTP響應中返回給客戶端
接口測試須要工具和被測系統之間進行消息的收發,這個工具能夠是別人開發的,也能夠本身開發,基於HTTP的接口測試工具備Postman,Jmeter等
這裏咱們使用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
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 )
當咱們進行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 )
注意下面這裏
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方法
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()