接口測試的方式有不少,好比能夠用工具(jmeter,postman)之類,也能夠本身寫代碼進行接口測試,工具的使用相對來講都比較簡單,重點是要搞清楚項目接口的協議是什麼,而後有針對性的進行選擇,甚至當工具不太適合項目時須要本身進行開發。html
在咱們項目的初期,咱們採用的是jmeter進行接口測試,當時以爲這個工具上手簡單,團隊成員學習成本低,而且接口測試的腳本稍微調整一下還能夠用來作性能測試。python
不過隨着項目規模、團隊人數的不斷增加,漸漸的這個工具備適應不了當前項目的需求了,爲此咱們項目也從新開發了相關接口自動化的平臺。可是,可是。。。多是我讓你們中毒太深,如今不少同窗一提到接口測試關聯到jmeter,爲此,我深深感到不安。畢竟jmeter只是個工具,換個項目換個協議你是否還能玩轉接口測試呢?session和cookie有什麼區別?工具又是怎麼實現的呢?git
好比session如何保存,接口依賴如何處理,case如何管理及執行順序,測試數據如何管理等等題,這個過程也有助於咱們更加深入的理解接口測試和http協議。github
本文主要採用python語言,python中http協議接口相關的庫有urllib,urllib2以及reqeusts庫,這其中reqeusts庫用來起來最方便,所以我也主要採用requests庫來作http協議的接口測試。首先來看下須要哪些環境信息:json
1、安裝pythonpython3.x
mac下自帶安裝了python,這個很少說了。服務器
2、安裝虛擬環境:cookie
咱們在一臺機器上能夠安裝多個python版本,爲了使每一個版本的環境相互不受干擾,能夠安裝虛擬環境,安裝方法以下:網絡
一、安裝virtualenv:pip install virtualenvsession
二、新建名爲venv的虛擬環境:virtualenv venv
三、進入新環境:source venv/bin/activate
四、退出:deactivate
3、安裝requests庫:
>>>pip install requests
ps:用python作http協議的接口測試會用到這個庫。
4、http測試工具:
一個使用 Python + Flask 編寫的 HTTP 請求和響應服務,該服務主要用於測試 HTTP 庫。後續測試咱們都基於這個網站。
http://httpbin.org
5、在本地搭建httpbin:
考慮到測試時要不斷訪問 httpbin 網站,請求過多擔憂被拉到黑名單,咱們本身在本志搭建一套httpbin服務。
一、安裝:pip install gunicorn
二、安裝:pip install httpbin
三、啓動:gunicorn httpbin:app
至此,環境搭建已經完畢,能夠開始玩了~
環境搭建好後,接下來咱們先來了解一下requests的一些簡單使用,主要包括:
本節首先來了解一下requests庫中如何發送get請求:
1、看下方法定義:
一、到官方文檔去了下requests.get()方法的定義,以下:
二、點擊右上角的【source】,看一下它的源碼以下:
看到最後一行return,get方法最後是經過調用 requests.request 方法實現的,其實在其它的請求方法如post,put,head,delete等方法都是調用的request方法,而後把請求方法的類型傳遞給request方法第一個參數。
三、HTTP協議是一個基於請求/響應模式的、無狀態的,應用層協議。既然有請求,就有響應,來看下resquest中經常使用的響應信息:
2、get方法簡單使用:
一、不帶參數的get:
# -*- coding:utf-8 -*- #不帶參數的get import requests import json host = "http://httpbin.org/" endpoint = "get" url = ''.join([host,endpoint]) r = requests.get(url) #response = r.json() print type(r.text) print (eval(r.text))
輸出:
{ 'origin': '183.14.133.88', 'headers': { 'Connection': 'close', 'Host': 'httpbin.org', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'User-Agent': 'python-requests/2.18.1' }, 'args': { }, 'url': 'http: //httpbin.org/get' }
二、 帶參數的get:
# -*- coding:utf-8 -*- #帶參數的get import requests import json host = "http://httpbin.org/" endpoint = "get" url = ''.join([host,endpoint]) params = {"show_env":"1"} r = requests.get(url=url,params=params) print r.url
輸出:
http://httpbin.org/get?show_env=1 { 'origin': '183.14.133.88', 'headers': { 'X-Request-Id': 'ebe922b4-c463-4fe9-9faf-49748d682fd7', 'Accept-Encoding': 'gzip, deflate', 'X-Forwarded-Port': '80', 'Total-Route-Time': '0', 'Connection': 'close', 'Connect-Time': '0', 'Via': '1.1vegur', 'X-Forwarded-For': '183.14.133.88', 'Accept': '*/*', 'User-Agent': 'python-requests/2.18.1', 'X-Request-Start': '1504755961007', 'Host': 'httpbin.org', 'X-Forwarded-Proto': 'http' }, 'args': { 'show_env': '1' }, 'url': 'http: //httpbin.org/get?show_env=1' }
三、帶header的get:
# -*- coding:utf-8 -*- import requests import json host = "http://httpbin.org/" endpoint = "get" url = ''.join([host,endpoint]) headers = {"User-Agent":"test request headers"} r = requests.get(url) r = requests.get(url,headers=headers) #response = r.json() print (eval(r.text))['headers']['User-Agent']
輸出:
test request headers
四、同時帶參數和header:
# -*- coding:utf-8 -*- import requests import json host = "http://httpbin.org/" endpoint = "get" url = ''.join([host,endpoint]) headers = {"User-Agent":"test request headers"} params = {"show_env":"1"} r = requests.get(url) r = requests.get(url,headers=headers,params=params) #response = r.json() print (eval(r.text))['headers']['User-Agent'] print r.url
輸出:
test request headers
http://httpbin.org/get?show_env=1
1、方法定義
2、post方法簡單使用
一、帶數據的post
二、帶header的post
三、帶json的post
四、帶參數的post
五、普通文件上傳
六、定製化文件上傳
七、多文件上傳
1、方法定義:
一、到官方文檔去了下requests.post()方法的定義,以下:
二、源碼:
三、經常使用返回信息:
2、post方法簡單使用:
一、帶數據的post:
# -*- coding:utf-8 -*- import requests import json host = "http://httpbin.org/" endpoint = "post" url = ''.join([host,endpoint]) data = {'key1':'value1','key2':'value2'} r = requests.post(url,data=data) #response = r.json() print (r.text)
輸出:
{ "args": {}, "data": "", "files": {}, "form": { "key1": "value1", "key2": "value2" }, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Connection": "close", "Content-Length": "23", "Content-Type": "application/x-www-form-urlencoded", "Host": "httpbin.org", "User-Agent": "python-requests/2.18.1" }, "json": null, "origin": "183.14.133.88", "url": "http://httpbin.org/post" }
二、帶header的post:
# -*- coding:utf-8 -*- import requests import json host = "http://httpbin.org/" endpoint = "post" url = ''.join([host,endpoint]) headers = {"User-Agent":"test request headers"} # r = requests.post(url) r = requests.post(url,headers=headers) #response = r.json()
輸出:
{ "args": {}, "data": "", "files": {}, "form": {}, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Connection": "close", "Content-Length": "0", "Host": "httpbin.org", "User-Agent": "test request headers" }, "json": null, "origin": "183.14.133.88", "url": "http://httpbin.org/post" }
三、帶json的post:
# -*- coding:utf-8 -*- import requests import json host = "http://httpbin.org/" endpoint = "post" url = ''.join([host,endpoint]) data = { "sites": [ { "name":"test" , "url":"www.test.com" }, { "name":"google" , "url":"www.google.com" }, { "name":"weibo" , "url":"www.weibo.com" } ] } r = requests.post(url,json=data) # r = requests.post(url,data=json.dumps(data)) response = r.json()
輸出:
{ "args": {}, "data": "{\"sites\": [{\"url\": \"www.test.com\", \"name\": \"test\"}, {\"url\": \"www.google.com\", \"name\": \"google\"}, {\"url\": \"www.weibo.com\", \"name\": \"weibo\"}]}", "files": {}, "form": {}, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Connection": "close", "Content-Length": "140", "Content-Type": "application/json", "Host": "httpbin.org", "User-Agent": "python-requests/2.18.1" }, "json": { "sites": [ { "name": "test", "url": "www.test.com" }, { "name": "google", "url": "www.google.com" }, { "name": "weibo", "url": "www.weibo.com" } ] }, "origin": "183.14.133.88", "url": "http://httpbin.org/post" }
四、帶參數的post:
# -*- coding:utf-8 -*- import requests import json host = "http://httpbin.org/" endpoint = "post" url = ''.join([host,endpoint]) params = {'key1':'params1','key2':'params2'} # r = requests.post(url) r = requests.post(url,params=params) #response = r.json() print (r.text)
輸出:
{
"args": {
"key1": "params1",
"key2": "params2"
},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Connection": "close",
"Content-Length": "0",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.18.1"
},
"json": null,
"origin": "183.14.133.88",
"url": "http://httpbin.org/post?key2=params2&key1=params1"
}
5.普通文件上傳:
# -*- coding:utf-8 -*- import requests import json host = "http://httpbin.org/" endpoint = "post" url = ''.join([host,endpoint]) #普通上傳 files = { 'file':open('test.txt','rb') } r = requests.post(url,files=files) print (r.text)
輸出:
{ "args": {}, "data": "", "files": { "file": "hello world!\n" }, "form": {}, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Connection": "close", "Content-Length": "157", "Content-Type": "multipart/form-data; boundary=392865f79bf6431f8a53c9d56c62571e", "Host": "httpbin.org", "User-Agent": "python-requests/2.18.1" }, "json": null, "origin": "183.14.133.88", "url": "http://httpbin.org/post" }
6.定製化文件上傳:
# -*- coding:utf-8 -*- import requests import json host = "http://httpbin.org/" endpoint = "post" url = ''.join([host,endpoint]) #自定義文件名,文件類型、請求頭 files = { 'file':('test.png',open('test.png','rb'),'image/png') } r = requests.post(url,files=files) print (r.text)heman793
7.多文件上傳:
# -*- coding:utf-8 -*- import requests import json host = "http://httpbin.org/" endpoint = "post" url = ''.join([host,endpoint]) #多文件上傳 files = [ ('file1',('test.txt',open('test.txt', 'rb'))), ('file2', ('test.png', open('test.png', 'rb'))) ] r = requests.post(url,files=files) print (r.text)
8.流式上傳:
# -*- coding:utf-8 -*- import requests import json host = "http://httpbin.org/" endpoint = "post" url = ''.join([host,endpoint]) #流式上傳 with open( 'test.txt' ) as f: r = requests.post(url,data = f) print (r.text)
輸出:
{ "args": {}, "data": "hello world!\n", "files": {}, "form": {}, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Connection": "close", "Content-Length": "13", "Host": "httpbin.org", "User-Agent": "python-requests/2.18.1" }, "json": null, "origin": "183.14.133.88", "url": "http://httpbin.org/post" }
掌握了前面幾節的的內容,就能夠作一些簡單的http協議接口的請求發送了,可是這些還不夠。HTTP協議是一個無狀態的應用層協議,也就是說先後兩次請求是沒有任何關係的,那若是咱們測試的接口以前有相互依賴關係怎麼辦呢(好比我要在博客園發文章,是須要先登陸的),這時咱們就要用到cookie和session技術來保持客戶端與服務器端鏈接的狀態,這也就是本節要介紹的內容:
1、Cookie:
一、獲取cookie:
# -*- coding:utf-8 -*- #獲取cookie import requests import json url = "https://www.baidu.com/" r = requests.get(url) #將RequestsCookieJar轉換成字典 c = requests.utils.dict_from_cookiejar(r.cookies) print r.cookies print c for a in r.cookies: print a.name,a.value
輸出:
<RequestsCookieJar[<Cookie BDORZ=27315 for .baidu.com/>]> {'BDORZ': '27315'} BDORZ 27315
二、發送Cookie
# -*- coding:utf-8 -*- #發送cookie到服務器 import requests import json host = "http://httpbin.org/" endpoint = "cookies" url = ''.join([host,endpoint]) #方法一:簡單發送 # cookies = {"aaa":"bbb"} # r = requests.get(url,cookies=cookies) # print r.text #方法二:複雜發送 s = requests.session() c = requests.cookies.RequestsCookieJar() c.set('c-name','c-value',path='/xxx/uuu',domain='.test.com') s.cookies.update(c)
2、Session
一、保持會話同步:
# -*- coding:utf-8 -*- import requests import json host = "http://httpbin.org/" endpoint = "cookies" url = ''.join([host,endpoint]) url1 = "http://httpbin.org/cookies/set/sessioncookie/123456789" r = requests.get(url) print r.text print "------" s = requests.session() #初始化一個session對象 s.get(url1) #cookie的信息存在了session中 r = s.get(url) print r.text
輸出:
{ "cookies": {} } ------ { "cookies": { "sessioncookie": "123456789" } }
二、保存繪畫信息:
# -*- coding:utf-8 -*- import requests import json host = "http://httpbin.org/" endpoint = "headers" url = ''.join([host,endpoint]) header1 = {"testA":"AAA"} header2 = {"testB":"BBB"} s = requests.session() #初始化一個session對象 s.headers.update(header1) #已經存在於服務中的信息 r = s.get(url,headers=header2) #發送新的信息 print r.text
輸出:
{ "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Connection": "close", "Host": "httpbin.org", "Testa": "AAA", "Testb": "BBB", "User-Agent": "python-requests/2.18.1" } }
3.刪除已存在的會話信息,保存爲None
# -*- coding:utf-8 -*- import requests import json host = "http://httpbin.org/" endpoint = "headers" url = ''.join([host,endpoint]) header1 = {"testA":"AAA"} header2 = {"testB":"BBB"} s = requests.session() #初始化一個session對象 s.headers.update(header1) #已經存在於服務中的信息 r = s.get(url,headers=header2) #發送新的信息 print r.text print '--------' s.headers['testA'] = None #刪除會話裏的信息testA r1 = s.get(url,headers = header2) print r1.text
{ "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Connection": "close", "Host": "httpbin.org", "Testa": "AAA", "Testb": "BBB", "User-Agent": "python-requests/2.18.1" } } -------- { "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Connection": "close", "Host": "httpbin.org", "Testb": "BBB", "User-Agent": "python-requests/2.18.1" } }
四、提供默認數據:
s = requests.Session() s.auth = ('user', 'pass') s.headers.update({'x-test': 'true'}) # both 'x-test' and 'x-test2' are sent s.get('http://httpbin.org/headers', headers={'x-test2': 'true'})
參考:
http://docs.python-requests.org/en/master/user/quickstart/#cookies
http://docs.python-requests.org/en/master/user/advanced/#session-objects
1、認證
一、基本認證:
# -*- coding:utf-8 -*- import requests url = "http://httpbin.org/basic-auth/user/passwd" r1 = requests.get(url) print "未提供用戶名密碼:" + str(r1.status_code) #Basic Authentication r2 = requests.get(url,auth=('user','passwd')) print "已提供用戶名密碼:" + str(r2.status_code)
輸出:
未提供用戶名密碼:401
已提供用戶名密碼:200
二、數字認證:
>>> from requests.auth import HTTPDigestAuth >>> url = 'http://httpbin.org/digest-auth/auth/user/pass' >>> requests.get(url, auth=HTTPDigestAuth('user', 'pass')) <Response [200]>
三、OAuth認證:
參考:http://docs.python-requests.org/en/master/user/authentication/
2、代理
一、方法一:proxy參數:
import requests proxies = { "https": "http://41.118.132.69:4433" } r = requests.post("http://httpbin.org/post", proxies=proxies) print r.text
二、方法二:設置環境變量:
$ export HTTP_PROXY="http://10.10.1.10:3128" $ export HTTPS_PROXY="http://10.10.1.10:1080" $ python >>> import requests >>> requests.get('http://example.org')
三、HTTP Basic Auth使用代理方法:http://user:password@host/
proxies = {'http': 'http://user:pass@10.10.1.10:3128/'}
3、證書驗證
一、SSL證書(HTTPS):
import requests #跳過12306 的證書驗證,把 verify 設置爲 False: r = requests.get('https://kyfw.12306.cn/otn/', verify=False) print r.text
二、客戶端證書:
>>> requests.get('https://kennethreitz.org', cert=('/path/client.cert', '/path/client.key')) <Response [200]>
or
s = requests.Session() s.cert = '/path/client.cert'
4、超時配置
1 、利用timeout參數來配置最大請求時間:
r = requests.get('https://github.com', timeout=5)
二、設置timeout=None,告訴請求永遠等待響應,而不將請求做爲超時值傳遞
r = requests.get('https://github.com', timeout=None)
5、錯誤異常:
一、全部Requests顯式拋出的異常都繼承自:requests.exctptions.RequestException
二、遇到網絡問題(如:DNS查詢失敗,拒絕鏈接等)時,requests會拋出一個 ConnectionError 異常
三、遇到罕見的無效HTTP響應時,Request則會拋出一個 HTTPError 異常
四、若請求超時,則拋出一個 Timeout 異常
五、若請求超過了最大的重寫向次數,則會拋出一個 TooManyRedirects 異常
上面主要介紹了環境搭建和requests庫的使用,可使用這些進行接口請求的發送。可是如何管理接口案例?返回結果如何自動校驗?這些內容光靠上面五節是不行的,所以從本節開始咱們引入python單元測試框架 unittest,用它來處理批量用例管理,校驗返回結果,初始化工做以及測試完成後的環境復原工做等等。
1、單個用例管理起來比較簡單,參考以下圖,單個用例通常多用在調試的時候:
2、代碼以下:
# -*- coding:utf-8 -*- # 單個用例執行 # 一、導入模塊 import unittest # 二、繼承自unittest.TestCase類 class TestOne(unittest.TestCase): # 三、配置環境:進行測試前的初始化工做 def setUp(self): print '\ncases before' pass # 四、定義測試用例,名字以「test」開頭 def test_add(self): '''test add method''' print 'add...' a = 3 + 4 b = 7 # 五、定義assert斷言,判斷測試結果 self.assertEqual(a, b) def test_sub(self): '''test sub method''' print 'sub...' a = 10 - 5 b = 4 self.assertEqual(a, b) # 六、清理環境 def tearDown(self): print 'case after' pass # 七、該方法會搜索該模塊下全部以test開頭的測試用例方法,並自動執行它們 if __name__ == '__main__': unittest.main()
輸出:
Ran 2 tests in 0.001s OK cases before add... case after cases before sub... case after Process finished with exit code 0
用例的管理問題解決了後,接下來要考慮的就是報告我問題了,這裏生成測試報告主要用到 HTMLTestRunner.py 這個模塊,下面簡單介紹一下如何使用:
1、下載HTMLTestRunner下載:
這個模塊不能經過pip安裝,只能下載安裝,下載地址以下:
2、mac下配置:
一、終端進入python環境
二、輸入:
import sys print sys.path
二、運行後生成報告以下:
三、run_all_case.py代碼以下:
# -*- coding:utf-8 -*- import unittest import os import time import HTMLTestRunner # 用例路徑 case_path = os.path.join(os.getcwd()) # 報告存放路徑 report_path = os.path.join(os.getcwd(), 'report') print report_path def all_case(): discover = unittest.defaultTestLoader.discover(case_path, pattern="test*.py", top_level_dir=None) print discover return discover if __name__ == '__main__': # 一、獲取當前時間,這樣便於下面的使用。 now = time.strftime("%Y-%m-%d-%H_%M_%S", time.localtime(time.time())) # 二、html報告文件路徑 report_abspath = os.path.join(report_path, "result_"+now+".html") # 三、打開一個文件,將result寫入此file中 fp = open(report_abspath, "wb") runner = HTMLTestRunner.HTMLTestRunner(stream=fp, title=u'接口自動化測試報告,測試結果以下:', description=u'用例執行狀況:') # 四、調用add_case函數返回值 runner.run(all_case()) fp.close()