1. 程式人生 > >基於Python Requests的數據驅動的HTTP接口測試

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

明顯 post請求 pri junit span odin 初始化 visio pan

發表於:2017-8-30 11:56 作者:顧翔 來源:51Testing軟件測試網原創 http://www.51testing.com/html/69/n-3720769-2.html 1、測試金字塔 技術分享圖片   圖 1軟件測試金字塔   圖 1是Main Cohn提出的軟件測試金字塔,他認為作為一個測試工程師應該把大量的工作花在單元測試接口測試,而其余的發在UI測試以及探索式測試。縱然,單元測試的優點很突出,它接近於代碼本身,運行速度快,開發可以一邊寫產品代碼一邊寫單元測試代碼,一旦在單元測試中發現缺陷,可以馬上找到對應的產品代碼來進行修改。然而單元測試的缺點也很明顯,就是你有多少產品代碼,就要有相應的單元測試代碼與它相對應,這樣造成的結果是單元測試代碼等於甚至超過與產品代碼的數量,這也就是為什麽單元測試在一般的中小型企業很難全面推廣的原因。對於基於UI層面的測試由於需求變更,頁面調整比較頻繁,所以在許多企業,基於UI的自動化測試
僅僅用於需求不帶變化的核心功能的自動化,往往是一些冒煙測試用例。而基於兩者之間的接口測試(Interface Test),基於代碼量不是很多,變更比較少的優勢下越來越得到各大企業的支持。   2、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測試報表   3、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_1、test_login_2和test_login_3為錯誤情況的測試用來,將在返回頁面中出現“用戶名或者密碼錯誤”的提示,test_login_4為正確的測試用例,登錄滿足需求,頁面跳入到登錄商品列表後頁面,並且顯示“查看購物車”的連接,所以我們以返回頁面中是否存在“查看購物車”來判斷測試是否成功。   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接口測試報告   5、進一步優化   細心的同學可能會發現,上面程序中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軟件測試網原創出品,轉載時請務必以超鏈接形式標明文章原始出處、作者信息和本聲明,否則將追究法律責任

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