標籤:SM eth getx .text 一段 set 情況 正則 提示
發表於: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-8class calculator:def __init__(self, a, b):self.a=int(a)self.b=int(b)def myadd(self):return self.a+self.bdef mysubs(self):return self.a-self.bdef mymultiply(self):return self.a*self.bdef mydivide(self):try:return self.a/self.bexcept 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-8import unittestfrom Calculator import calculatorclass 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 io539行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 = o772行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-8import unittestfrom HTMLTestRunner import HTMLTestRunnertest_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.pyimport 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.pyimport unittest,requestsclass 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 = 0for keyin AAA:aaaValue = aaa[i].firstChild.databbbValue = bbb[i].firstChild.datacccValue = ccc[i].firstChild.dataprint(aaaValue)print(bbbValue)print(cccValue)i =i+1 |
我們來看一下測試代碼。
| loginConfig.xml#!/usr/bin/env python#coding:utf-8import unittest,requestsfrom xml.dom import minidomclass mylogin(unittest.TestCase):def setUp(self):print("--------測試結束--------")#從XML中讀取資料dom = minidom.parse(‘loginConfig.xml‘)root = dom.documentElementTestIds = 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 = 0mylists=[]for TestId in TestIds:mydicts={}#擷取每一個資料,形成字典mydicts["TestId"] = TestIds[i].firstChild.datamydicts["Title"] = Titles[i].firstChild.datamydicts["Method"] = Methods[i].firstChild.datamydicts["Desc"] = Descs[i].firstChild.datamydicts["Url"] = Urls[i].firstChild.datamydicts["InptArg"] = InptArgs[i].firstChild.datamydicts["Result"] = Results[i].firstChild.datamydicts["CheckWord"] =CheckWords[i].firstChild.datamylists.append(mydicts)i = i+1self.mylists = mylistsdef 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-8from xml.dom import minidomclass GetXML():def getxmldata(xmlfile):#從XML中讀取資料dom = minidom.parse(xmlfile)root = dom.documentElementTestIds = 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 = 0mylists=[]for TestId in TestIds:mydicts={}#擷取每一個資料,形成字典mydicts["TestId"] = TestIds[i].firstChild.datamydicts["Title"] = Titles[i].firstChild.datamydicts["Method"] = Methods[i].firstChild.datamydicts["Desc"] = Descs[i].firstChild.datamydicts["Url"] = Urls[i].firstChild.datamydicts["InptArg"] = InptArgs[i].firstChild.datamydicts["Result"] = Results[i].firstChild.datamydicts["CheckWord"] =CheckWords[i].firstChild.datamylists.append(mydicts)i = i+1return 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介面測試