基於Python Requests的數據驅動的HTTP接口測試

發表於:2017-8-30 11:56  做者:顧翔   來源:51Testing軟件測試網原創
http://www.51testing.com/html/69/n-3720769-2.html
 
一、測試金字塔
  
圖 1軟件測試金字塔
  圖 1是Main Cohn提出的 軟件測試金字塔,他認爲做爲一個測試工程師應該把大量的 工做花在 單元測試接口測試,而其他的發在UI測試以及探索式測試。縱然,單元測試的優勢很突出,它接近於代碼自己,運行速度快,開發能夠一邊寫產品代碼一邊寫單元測試代碼,一旦在單元測試中發現缺陷,能夠立刻找到對應的產品代碼來進行修改。然而單元測試的缺點也很明顯,就是你有多少產品代碼,就要有相應的單元測試代碼與它相對應,這樣形成的結果是單元測試代碼等於甚至超過與產品代碼的數量,這也就是爲何單元測試在通常的中小型企業很難全面推廣的緣由。對於基於UI層面的測試因爲需求變動,頁面調整比較頻繁,因此在許多企業,基於UI的 自動化測試僅僅用於需求不帶變化的核心功能的自動化,每每是一些冒煙 測試用例。而基於二者之間的接口測試(Interface Test),基於代碼量不是不少,變動比較少的優點下愈來愈獲得各大企業的支持。
   二、unittest
  因爲本文是介紹Django的,而Django是基於 Python語言的,因此咱們接下來介紹在這裏我主要介紹基於Python Requests的軟件接口測試。首先讓咱們來了解一下基於Python的unittest,unittest 原名爲pytest,他是屬於XUnit框架下的。先讓咱們來看一下一段產品代碼。
  Calculator.py
#!/usr/bin/env python
#coding:utf-8
class calculator:
def __init__(self, a, b):
self.a=int(a)
self.b=int(b)
def myadd(self):
return self.a+self.b
def mysubs(self):
return self.a-self.b
def mymultiply(self):
return self.a*self.b
def mydivide(self):
try:
return self.a/self.b
except ZeroDivisionError:
print ("除數不能爲零")
return 9999999999999999
  很顯然這個代碼實現的是加、減、乘、除四則運算的功能。類calculator有兩個成員變量,self.a和self.b,myadd、mysubs、mymultiply、mydivide分別實現self.a+self.b、self.a-self.b、self.a*self.b、self.a/self.b四個功能,在mydivide中,若是被除數self.b爲0,咱們就進行對應的處理,打印"除數不能爲零"的警告,而後返回一個很大的數:9999999999999999。如今讓咱們來看一看這段代碼所對應的unittest框架的測試代碼。
  CalculatorTest.py
#!/usr/bin/env python
#coding:utf-8
import unittest
from Calculator import calculator
class calculatortest(unittest.TestCase):
def setUp(self):
print ("Test  start!")
def test_base(self):
j=calculator(4,2)
self.assertEqual(j.myadd(),6)
self.assertEqual(j.mysubs(),2)
self.assertEqual(j.mymultiply(),8)
self.assertEqual(j.mydivide(),2)
def test_divide(self):
j=calculator(4,0)
self.assertEqual(j.mydivide(),9999999999999999)
def tearDown(self):
print ("Test end!")
if __name__=='__main__':
#構造測試集
suite=unittest.TestSuite()
suite.addTest(calculatortest("test_base"))
suite.addTest(calculatortest("test_divide"))
#運行測試集合
runner=unittest.TextTestRunner()
runner.run(suite)
  首先咱們使用unittest測試框架必須先importunittest類,unittest類是Python自帶的測試類,只要你安裝了Python,這個類就自動安裝上了。
  而後咱們引入被測試類:fromCalculator import calculator。
  unittest的測試類參數必須爲unittest.TestCase。
  和其餘XUnit測試框架同樣,unittest也存在着一個初始化函數和清除函數,分別定義爲def setUp(self):和def tearDown(self):,因爲在這裏沒有具體實際性的操做咱們僅僅在def setUp(self):函數中打印一個"Test start!"字符串;在def tearDown(self):函數中打印一個"Testend!"字符串。
  unittest具體測試函數的函數名必須以test_開頭,這個有點相似於JUnit3,j=calculator(4,2)先定義一個self.a =4和self.b = 2的類變量j,而後經過斷言self.assertEqual()函數來驗證是否是計算結果與預期結果一致。
  在deftest_divide(self):函數中咱們專門對被除數爲0的狀況進行了測試。
  unittest的主函數爲與其餘主函數同樣爲if__name__=='__main__':,先經過suite=unittest.TestSuite()來構造測試集,而後經過suite.addTest(calculatortest("test_base")),suite.addTest(calculatortest("test_divide"))把兩個測試函數加進去,接下來經過runner=unittest.TextTestRunner(),runner.run(suite)來執行測試工做。
   當許多測試文件須要批量運行的時候,咱們能夠進行以下操做:
  1,  把這些測試文件的文件名定義成一個能夠用正則函數匹配的模式,好比都以Test開始或結尾的.py文件。
  2,  創建一個批處理py文件,好比runtest.py。
  runtest.py
  #!/usr/bin/env python
  #coding:utf-8
  import unittest
  test_dir='./'
  discover=unittest.defaultTestLoader.discover(test_dir,pattern="*Test.py")
  if __name__=='__main__':
  runner=unittest.TextTestRunner()
  runner.run(discover)
  test_dir:定義測試文件的路徑,這裏爲當前路徑。
  discover=unittest.defaultTestLoader.discover(test_dir,pattern="*Test.py")爲調用測試路徑下以Test結尾的.py文件(pattern="*Test.py")
  而後在主函數中經過調用runner=unittest.TextTestRunner(),runner.run(discover)兩行代碼來實現匹配的全部文件中的測試用例的執行。
  既然介紹到了unittest的批量操做,在這裏我頗有必要來介紹一下如何經過unittest來生成一封好看的測試報告。
  咱們先到網站http://tungwaiyip.info/software/HTMLTestRunner.html下載HTMLTestRunner.py文件放入到%PYTHON_HOME%\Lib\目錄下。若是你使用的是Python2.X就不須要進行修改,不然請做以下修改:
94行
import  StringIO
改成
import  io
539行
self.outputBuffer  = StringIO.StringIO()
改成
self.outputBuffer  = io.StringIO()
631行
print  >>sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime)
改成
print  (sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime))
642行
if not  rmap.has_key(cls):
改成
if not  cls in rmap:
766行
uo = o.decode('latin-1')
改成
uo = o
772行
ue =  e.decode('latin-1')改成
ue = e
  這樣咱們在runtest.py頭部加入fromHTMLTestRunner import HTMLTestRunner,runner.run(discover)前面加上fp=open("result.html","wb"),runner=HTMLTestRunner(stream=fp,title='測試報告',description='測試用例執行報告'),後面加上fp.close(),運行測試用例完畢就能夠生成一份美觀的基於HTML的測試報告了,最後的runtest.py代碼以下。
  runtest.py
#!/usr/bin/env python
#coding:utf-8
import unittest
from HTMLTestRunner import HTMLTestRunner
test_dir='./'
discover=unittest.defaultTestLoader.discover(test_dir,pattern="*Test.py")
if __name__=='__main__':
runner=unittest.TextTestRunner()
#如下用於生成測試報告
fp=open("result.html","wb")
runner  =HTMLTestRunner(stream=fp,title='測試報告',description='測試用例執行報告')
runner.run(discover)
fp.close()
  圖2測試報表,固然這裏的測試用例剛纔介紹的要多。
 
 圖2 unittest測試報表
   三、resuests對象介紹與使用
  咱們要是用request首先要先下載 requests,咱們能夠用老辦法,經過pip命令下載
  >pip install requests
   首先我來介紹一下 requests對象的使用。
  1)  經過requests發送GET請求。
  response = requests.get(url,params=payload)
  url爲發送的地址,payload爲請求的參數,格式爲字典類型,前面變量名爲params,response爲返回變量。
  好比:
  url =http://www.a.com/user.jsp
  payload={「id」:」1」,」name」:」Tom」}
  data = requests.get(url,params=payload)
   2)  經過requests發送POST請求。
  response = requests.post(url,data=payload)
  url爲發送的地址,payload爲請求的參數,格式爲字典類型,前面變量名爲data,response爲返回變量。
  好比:
  url =http://www.b.com/login.jsp
  payload={「username」:」Tom」,」password」:」123456」}
  data = requests.post(url,data=payload)
 
   3)  requests的返回值
  這裏讓咱們來討論下requests的返回值。見表1。
  表1:requests的返回值
  請求網址的內容信息
  在這裏介紹一下請求頁面的狀態(狀態碼),這個在基於HTTP協議的接口測試中常常做爲一個驗證點。
  1XX:表示消息
  這個比較少用
  2XX:表示成功
  常用的是:
  200:正確
  #3XX 表示重定向.
  常用的是:
  304: 沒有改變
  4XX 表示客戶端錯誤
  常用的是:
  404: 網址不存在
  5XX,6XX表示服務器錯誤.
  常用的是:
  500:服務器內部錯誤
  4)有了上面這些知識,咱們來看一下經過request如何來實現接口測試,咱們這裏之前面介紹的登陸模塊做爲測試對象來設置測試用例。測試用例見表2。
  表2:登陸模塊測試用例
  進入登陸後頁面,出現「查看購物車」
  假設咱們的正確用戶名爲 jerry,正確密碼爲000000,這樣咱們設計測試代碼
testLogin.py
import  requests
#正確的用戶名,錯誤的密碼
url=「http://127.0.0.1:8000/login_action/
payload={{"username":"jerry","password":「000000"}}
data =  requests.post(url,data=payload)
if  (str(data.status_code)==‘200’) and (「用戶名或者密碼錯誤」 in str(data.text))
print(「pass」)
else:
print(「Fail」)
#錯誤的用戶名,正確的密碼
url=「http://127.0.0.1:8000/login_action/
payload={{"username":「tom","password":「123456"}}
data =  requests.post(url,data=payload)
if  (str(data.status_code)==‘200’) and (「用戶名或者密碼錯誤」 in str(data.text))
print(「pass」)
else:
print(「Fail」)
#錯誤的用戶名,錯誤的密碼
url=「http://127.0.0.1:8000/login_action/
payload={{"username":「tom","password":「000000"}}
data =  requests.post(url,data=payload)
if  (str(data.status_code)==‘200’) and (「用戶名或者密碼錯誤」 in str(data.text))
print(「pass」)
else:
print(「Fail」)
#正確的用戶名,正確的密碼
url=「http://127.0.0.1:8000/login_action/
payload={{"username":「jerry","password":「123456"}}
data =  requests.post(url,data=payload)
if  (str(data.status_code)==‘200’) and (「查看購物車」 in str(data.text))
print(「pass」)
else:
print(「Fail」)
  這樣的代碼雖然能夠測試,可是沒有測試框架進行限制,代碼不利於維護,更不利於批量地執行,咱們用剛纔介紹的unittest框架進行改造。
testLogin.py
import  unittest,requests
class  mylogin(unittest.TestCase):
def setUp(self):
print("--------測試開始--------")
def test_login_1:
url=「http://127.0.0.1:8000/login_action/
payload={{"username":「tom","password":「000000"}}
data =  requests.post(url,data=payload)
self.assertEqual(‘200’,str(data.status_code))
self.assertIn((「用戶名或者密碼錯誤」,str(data.text))
def test_login_2:
url=「http://127.0.0.1:8000/login_action/
payload={{"username":「jerry","password":「123456"}}
data =  requests.post(url,data=payload)
self.assertEqual(‘200’,str(data.status_code))
self.assertIn((「用戶名或者密碼錯誤」,str(data.text))
def test_login_3:
url=「http://127.0.0.1:8000/login_action/
payload={{"username":「tom","password":「000000"}}
data =  requests.post(url,data=payload)
self.assertEqual(‘200’,str(data.status_code))
self.assertIn((「用戶名或者密碼錯誤」,str(data.text))
def test_login_4:
url=「http://127.0.0.1:8000/login_action/
payload={{"username":「jerry","password":「000000"}}
data =  requests.post(url,data=payload)
self.assertEqual(‘200’,str(data.status_code))
self.assertIn((「查看購物車」,str(data.text))
def tearDown(self):
print("--------測試結束--------")
if  __name__=='__main__':
#構造測試集
suite=unittest.TestSuite()
suite.addTest(mylogin("  test_login_1 "))
suite.addTest(mylogin("  test_login_2 "))
suite.addTest(mylogin("  test_login_3 "))
suite.addTest(mylogin("  test_login_4 "))
#運行測試集合
runner=unittest.TextTestRunner()
runner.run(suite)
  程序經過self.assertEqual(‘200’,str(data.status_code)),來判斷返回碼是否是與預期的相同;經過self.assertIn((「用戶名或者密碼錯誤」,str(data.text))來判斷返回的文本中是否是包括指定的字符串。測試用例test_login_一、test_login_2和test_login_3爲錯誤狀況的測試用來,將在返回頁面中出現「用戶名或者密碼錯誤」的提示,test_login_4爲正確的測試用例,登陸知足需求,頁面跳入到登陸商品列表後頁面,而且顯示「查看購物車」的鏈接,因此咱們以返回頁面中是否存在「查看購物車」來判斷測試是否成功。
   四、數據驅動的自動化接口測試
  數據驅動的自動化測試是HP在其著名的產品QTP中進行提出,而且成爲了業內自動化測試的一個標準,所謂數據驅動能夠理解爲測試數據參數化。因爲Python讀取XML的技術至關成熟,咱們能夠把測試數據放在XML裏來進行設計數據驅動的自動化接口測試。首先來看一下我是如何設計XML文件的。
loginConfig.xml
<node>
<case>
<TestId>testcase001</TestId>
<Title>用戶登陸</Title>
<Method>post</Method>
<Desc>正確用戶名,錯誤密碼</Desc>
<Url>http://127.0.0.1:8000/login_action/</Url>
<InptArg>{"username":"jerry","password":"12345"}</InptArg>
<Result>200</Result>
<CheckWord>用戶名或者密碼錯誤</CheckWord>
</case>
<case>
<TestId>testcase002</TestId>
<Title>用戶登陸</Title>
<Method>post</Method>
<Desc>錯誤用戶名,正確密碼</Desc>
<Url>http://127.0.0.1:8000/login_action/</Url>
<InptArg>{"username":"smith","password":"knyzh158"}</InptArg>
<Result>200</Result>
<CheckWord>用戶名或者密碼錯誤</CheckWord>
</case>
<case>
<TestId>testcase003</TestId>
<Title>用戶登陸</Title>
<Method>post</Method>
<Desc>錯誤用戶名,錯誤密碼</Desc>
<Url>http://127.0.0.1:8000/login_action/</Url>
<InptArg>{"username":"smith","password":"12345"}</InptArg>
<Result>200</Result>
<CheckWord>用戶名或者密碼錯誤</CheckWord>
</case>
<case>
<TestId>testcase004</TestId>
<Title>用戶登陸</Title>
<Method>post</Method>
<Desc>正確用戶名,正確密碼</Desc>
<Url>http://127.0.0.1:8000/login_action/</Url>
<InptArg>{"username":"jerry","password":"knyzh158"}</InptArg>
<Result>200</Result>
<CheckWord>查看購物車</CheckWord>
</case>
</node>
  在這裏<node></node>是根標識,<case>…</case>表示一個測試用例,這裏面有四個<case>…</case>對,分別上述表示四個測試用例。在<case>…</case>對中,有些數據是爲了咱們讀起來比較方便,有些數據是程序中要是用的,下面來進行分別的介紹。
  <Desc>…</Desc> :測試用例描述
  <Url></Url> :測試的URL地址(程序用到)
  <InptArg>…</InptArg> :請求參數,用{}括起來,爲符合Python字典格式的值參對(程序用到)
  <Result>…</Result> :返回碼(程序用到)
  <CheckWord>…</CheckWord> :驗證字符串(程序用到)
  在py文件中咱們經過調用from xml.dom import minidom來引入minidom類;dom =  minidom.parse('loginConfig.xml')來獲取所須要讀取的xml文件; root = dom.documentElement來開始獲取文件中節點的內容,而後經過語句aaa = root.getElementsByTagName('AAA')來得到文件中的全部葉子節點<AAA>…</AAA>對中的數據,由於文件中有多個<AAA>…</AAA>對,因此返回參數aaa爲一個對象列表對,而後經過
  for keyin aaa:
  aaaValue = key.firstChild.data
  print(aaaValue)
  來獲取每個<AAA>…</AAA>對中的參數。可是因爲XML文件中的標籤每每不止一個,且對出現,真像咱們文件因此loginConfig.xml中的<TestId>…<TestId>、<Title >…
<Title> 、<Method>…</Method> …,因此咱們能夠這樣來得到。
aaa = root.getElementsByTagName('AAA')
bbb = root.getElementsByTagName('BBB')
ccc = root.getElementsByTagName('CCC')
i = 0
for keyin AAA:
aaaValue = aaa[i].firstChild.data
bbbValue = bbb[i].firstChild.data
cccValue = ccc[i].firstChild.data
print(aaaValue)
print(bbbValue)
print(cccValue)
i =i+1
  咱們來看一下測試代碼。
loginConfig.xml
#!/usr/bin/env  python
#coding:utf-8
import  unittest,requests
from  xml.dom import minidom
class mylogin(unittest.TestCase):
def setUp(self):
print("--------測試結束--------")
#從XML中讀取數據
dom =  minidom.parse('loginConfig.xml')
root = dom.documentElement
TestIds = root.getElementsByTagName('TestId')
Titles =  root.getElementsByTagName('Title')
Methods =  root.getElementsByTagName('Method')
Descs =  root.getElementsByTagName('Desc')
Urls = root.getElementsByTagName('Url')
InptArgs =  root.getElementsByTagName('InptArg')
Results =  root.getElementsByTagName('Result')
CheckWords  =root.getElementsByTagName('CheckWord')
i = 0
mylists=[]
for TestId in TestIds:
mydicts={}
#獲取每個數據,造成字典
mydicts["TestId"] = TestIds[i].firstChild.data
mydicts["Title"] = Titles[i].firstChild.data
mydicts["Method"]  = Methods[i].firstChild.data
mydicts["Desc"] = Descs[i].firstChild.data
mydicts["Url"] = Urls[i].firstChild.data
mydicts["InptArg"] = InptArgs[i].firstChild.data
mydicts["Result"] = Results[i].firstChild.data
mydicts["CheckWord"] =CheckWords[i].firstChild.data
mylists.append(mydicts)
i = i+1
self.mylists = mylists
def test_login(self):
for mylist in self.mylists:
payload =  eval(mylist["InptArg"])
url=mylist["Url"]
#發送請求
try:
if  mylist["Method"] == "post":
data  = requests.post(url,data=payload)
elif  mylist["Method"] == "get":
data  = requests.get(url,params=payload)
else:
print  ("Method 參數獲取錯誤")
except Exception as  e:
self.assertEqual(mylist["Result"],"404")
else:
self.assertEqual(mylist["Result"],str(data.status_code))
self.assertIn(mylist["CheckWord"],str(data.text))
def tearDown(self):
print("--------測試結束--------")
if  __name__=='__main__':
#構造測試集
suite=unittest.TestSuite()
suite.addTest(mylogin("test_login"))
#運行測試集合
runner=unittest.TextTestRunner()
runner.run(suite)
  setUp(self)主要把XML裏的全部葉子節點數據獲取到,放在一個名爲mylists的列表變量中,而且返回給self.mylists變量,列表中每一項爲一個字典類型的數據,key爲XML裏的全部葉子節點標籤,key所對應的值爲XML標籤的內容。最後self. mylists傳給每一個測試函數中使用。
  如今咱們來看一下函數test_login(self)。
  for mylistin self.mylists:把剛纔在初始化裏面定義的self.mylists每一項分別取出。
  payload =eval(mylist["InptArg"]):爲獲取標籤爲InptArg中的數據,因爲在XML格式定義的時候,這一項用{}括起來,裏面是個值參對,因爲mylist["InptArg"]返回的是一個{}括起來的具備字典格式的字符串,因此咱們必須經過函數eval()進行轉移成字典變量賦給payload。
  url=mylist["Url"]爲發送HTTP的地址。
  而後經過判斷mylist["Method"]是等於」post」仍是等於」get」,選擇使用data = requests.post(url,data=payload)或者data =requests.get(url,params=payload)來發送信息,接受信息放在變量data中。
  最後經過self.assertEqual(mylist["Result"],str(data.status_code))來判斷返回代碼是否符合指望結果,以及self.assertIn(mylist["CheckWord"],str(data.text))指望代碼mylist["CheckWord"]是否在返回內容str(data.text)中來判斷測試是否成功。在這裏特別指出在程序中except Exception as e中經過self.assertEqual(mylist["Result"],"404")來判斷是否指望結果不存在。在這個項目中咱們也加上相似的runtest.py來運行全部的測試用例。格式與前面相同,再次不在重複介紹。圖3是加上註冊接口測試代碼的測試報告。
  
圖3基於Python Requests的HTTP接口測試報告
   五、進一步優化
  細心的同窗可能會發現,上面程序中setUp函數咱們能夠進行一些封裝優化,咱們創建一個單獨的py文件getXML.py,內容以下:
getXML.py
#!/usr/bin/env  python
#coding:utf-8
from  xml.dom import minidom
class  GetXML():
def getxmldata(xmlfile):
#從XML中讀取數據
dom =  minidom.parse(xmlfile)
root = dom.documentElement
TestIds =  root.getElementsByTagName('TestId')
Titles =  root.getElementsByTagName('Title')
Methods =  root.getElementsByTagName('Method')
Descs =  root.getElementsByTagName('Desc')
Urls = root.getElementsByTagName('Url')
InptArgs =  root.getElementsByTagName('InptArg')
Results =  root.getElementsByTagName('Result')
CheckWords  =root.getElementsByTagName('CheckWord')
i = 0
mylists=[]
for TestId in TestIds:
mydicts={}
#獲取每個數據,造成字典
mydicts["TestId"]  = TestIds[i].firstChild.data
mydicts["Title"]  = Titles[i].firstChild.data
mydicts["Method"]  = Methods[i].firstChild.data
mydicts["Desc"]  = Descs[i].firstChild.data
mydicts["Url"]  = Urls[i].firstChild.data
mydicts["InptArg"]  = InptArgs[i].firstChild.data
mydicts["Result"]  = Results[i].firstChild.data
mydicts["CheckWord"]  =CheckWords[i].firstChild.data
mylists.append(mydicts)
i = i+1
return mylists
  這樣在loginTest.py改成setUp函數只須要改成:
loginConfig.xml
from  getXML import GetXML #引入剛纔創建的類
class  mylogin(unittest.TestCase):
def setUp(self):
print("--------測試開始--------")
self.mylists =  GetXML.getxmldata("loginConfig.xml")#調用類中的函數
版權聲明:51Testing軟件測試網原創出品,轉載時請務必以超連接形式標明文章原始出處、做者信息和本聲明,不然將追究法律責任
相關文章
相關標籤/搜索