自動化測試在各大互聯網公司全面鋪開,那麼針對於自動化測試好的設計思想有哪些呢?.....今天咱們共同探討下Unittest之數據驅動(DDT是 「Data-Driven Tests」的縮寫)。html
對於接口自動化的數據驅動模式是大多數公司所選擇的主流設計思想,有經過Mysql實現數據驅動,有經過Excel實現數據驅動,可是客觀的認爲,都沒有Python模塊中DDT模塊所作的數據驅動方便,靈活。測試人員能夠編寫腳本進行自動化測試工做和接口迴歸測試,開發人員也能夠進行提測以前的自測工做,保證代碼質量。python
什麼是數據驅動呢?mysql
數據驅動就是相同的測試腳本使用不一樣的測試數據來執行,測試數據和測試行爲徹底分離,這樣的測試腳本設計模式稱爲數據驅動。例如,測試網站的登陸功能,自動化測試工程師想驗證不用的用戶名和密碼在網站登陸時對系統影響結果,就可使用數據驅動模式來進行自動化測試ios
一.安裝ddt模塊sql
unittest是python自帶的單元測試框架因此不用安裝可是因爲ddt不是Python的標準庫因此咱們須要pip安裝ddt模塊(注:若是Python的Scripts目錄已增長到環境變量,請忽略下方操做,直接pip3 install ddt安裝便可。)數據庫
二.使用pycharm建立unittest文件json
自動生成文件以下設計模式
import unittest #生成一個測試類(繼承unittest.TestCase這個測試類) class MyTestCase(unittest.TestCase): def test_something(self): #結果斷言 self.assertEqual(True, False) if __name__ == '__main__': unittest.main()
三.單元測試用例書寫方法api
例子1:併發
import unittest #請求方法 import requests class MyTestCase(unittest.TestCase): #每次方法執行以前執行 def setUp(self): print('==============起始============') #執行測試的函數 注意:全部執行測試的方法必須以test開頭,執行順序之後面的羅馬數字升序執行 def test_01(self): print('這是第一個用例') #斷言 True 等於 True self.assertEqual(True, True) def test_02(self): print('這是第二個用例') #斷言 True 等於 True self.assertEqual(True, True) #每次方法執行以後執行 def tearDown(self): print('==============結束============') if __name__ == '__main__': unittest.main()
運行結果以下;
分析以下:
def setUp(self) 和 def tearDown(self)是unittest的內置方法,他們的意思是在執行用例的先後都被執行一次,每一個用例在執行是都會被調用,至關於前置與後置處理方便咱們自定義添加固定的方法,適用場景:請求前須要將加入時間戳是實時數據,請求後須要記錄某個參數數據
例子2:
import unittest class MyTestCase(unittest.TestCase):
#必須裝飾這個類 @classmethod def setUpClass(cls): print('最初執行一次') def test_01(self): print('這是用例1') self.assertEqual(True, True) def test_02(self): print('這是用例2') self.assertEqual(True, True) @classmethod def tearDownClass(cls): print('最後執行一次') if __name__ == '__main__': unittest.main()
運行結果以下:
分析以下:
def setUpClass(cls): 和 def tearDownClass(cls):一樣也是unittest內置方法但要注意的是須要加上:@classmethod來進行裝飾,它們的意思是在執行用例是隻有最初和
最後會被執行,適用場景是:例如登陸接口會返回一個token,將這個token提取出來放在header中,這樣其餘接口才能夠正常訪問
例子三:將unittest與requests模塊結合 進行接口測試
1.將requests的post請求與get請求封裝成一個類,便於在unittest中進行調用
import requests import json class RunMain(object): def get(self, url, data): res = requests.get(url=url, data=data).json() return res def post(self, url, data): res = requests.post(url=url, data=data).json() return res def run_main(self, url, method, data=None): res = None
#不區分大小寫 if method.lower() == 'GET': res = self.get(url, data) else: res = self.post(url, data) return res
2.unittest中代碼以下
import unittest #導入請求類 from basis import method class MyTestCase(unittest.TestCase): def setUp(self): #初始化請求類 self.run=method.RunMain() def test_01(self): ''' 查詢紅包餘額接口 get請求方法 :return: ''' print('這是第一個用例') url='http://ios.wecash.net/biz/wallet/amount' data='CUSTOMER_ID=56256A951F81F0BCA10780AD02139B29' rep=self.run.get(url,data) print(rep) # if rep['successful'] == 1: # print('經過') # else: # print('沒經過') #對返回結果進行斷言判斷(內置斷言) 提取的值,斷言的值,匹配不上的輸出信息 self.assertEqual(rep['successful'],1, '查詢紅包餘額接口失敗') # self.assertEqual()驗證兩個是否相等 # self.assertNotEqual() 判斷不想等 # self.assertTrue()布爾類型判斷 若是返回是True == True def test_02(self): ''' 豆瓣API,發送一條廣播 :return: ''' print('這是第二個用例') url='https://api.douban.com/shuo/statuses/' data="{'media': [{'imgsrc': 'http://icanhascheezburger.files.wordpress.com/2009/04/funny-pictures-hairless-cat-phones- home.jpg', 'src': 'http://www.mapsofwar.com/photos/EMPIRE17.swf', 'type': 'flash'}]}" rep=self.run.post(url,data) print(rep) self.assertEqual(rep['msg'],'須要登陸','發送廣播失敗') if __name__ == '__main__': unittest.main()
運行結果以下:
分析以下:
咱們在setUp中初始化RunMain()便於在用例中引用,將url與data必填參數傳參,而後經過self.assertEqual()對結果進行結果斷言
例子4:探索unittest中接口用例如何上下參數關聯
咱們來想想 若是下面的用例須要上面的用例的返回值 咱們怎麼作?
import unittest #導入請求類 from basis import method class MyTestCase(unittest.TestCase): def setUp(self): #初始化請求類 self.run=method.RunMain() self.successful=self.test_01() def test_01(self): ''' 查詢紅包餘額接口 get請求方法 :return: ''' print('這是第一個用例') url='http://ios.wecash.net/biz/wallet/amount' data='CUSTOMER_ID=56256A951F81F0BCA10780AD02139B29' rep=self.run.get(url,data) print(rep) # if rep['successful'] == 1: # print('經過') # else: # print('沒經過') #對返回結果進行斷言判斷(內置斷言) 提取的值,斷言的值,匹配不上的輸出信息 self.assertEqual(rep['successful'],1, '查詢紅包餘額接口失敗') return rep['successful'] # self.assertEqual()驗證兩個是否相等 # self.assertNotEqual() 判斷不想等 # self.assertTrue()布爾類型判斷 若是返回是True == True def test_02(self): ''' 豆瓣API,發送一條廣播 :return: ''' print('這是第二個用例') print(self.successful) url='https://api.douban.com/shuo/statuses/' data="{'media': [{'imgsrc': 'http://icanhascheezburger.files.wordpress.com/2009/04/funny-pictures-hairless-cat-phones- home.jpg', 'src': 'http://www.mapsofwar.com/photos/EMPIRE17.swf', 'type': 'flash'}]}" rep=self.run.post(url,data) print(rep) self.assertEqual(rep['msg'],'須要登陸','發送廣播失敗') if __name__ == '__main__': unittest.main()
結果以下:
分析以下:
咱們將想要提取的successful 在用例一的地方return出來 在從setup中實例化,這樣作咱們確實能夠獲得successful,可是咱們須要重複的調用,由於每個用例都要走setUp(),很顯然這樣是浪費資源的,因此咱們要引入新的知識點:全局變量 globals(),注意:在unittest中用例的執行順序是按照函數名的羅馬數字順序執行的
import unittest #導入請求類 from basis import method class MyTestCase(unittest.TestCase): def setUp(self): #初始化請求類 self.run=method.RunMain() # self.successful=self.test_01() def test_01(self): ''' 查詢紅包餘額接口 get請求方法 :return: ''' print('這是第一個用例') url='http://ios.wecash.net/biz/wallet/amount' data='CUSTOMER_ID=56256A951F81F0BCA10780AD02139B29' rep=self.run.get(url,data) print(rep) # if rep['successful'] == 1: # print('經過') # else: # print('沒經過') #對返回結果進行斷言判斷(內置斷言) 提取的值,斷言的值,匹配不上的輸出信息 self.assertEqual(rep['successful'],1, '查詢紅包餘額接口失敗') #將successful放入全局變量 globals()['successful'] =rep['successful'] # return rep['successful'] # self.assertEqual()驗證兩個是否相等 # self.assertNotEqual() 判斷不想等 # self.assertTrue()布爾類型判斷 若是返回是True == True def test_02(self): ''' 豆瓣API,發送一條廣播 :return: ''' print('這是第二個用例') #從全局變量中獲取 print(globals()['successful']) url='https://api.douban.com/shuo/statuses/' data="{'media': [{'imgsrc': 'http://icanhascheezburger.files.wordpress.com/2009/04/funny-pictures-hairless-cat-phones- home.jpg', 'src': 'http://www.mapsofwar.com/photos/EMPIRE17.swf', 'type': 'flash'}]}" rep=self.run.post(url,data) print(rep) self.assertEqual(rep['msg'],'須要登陸','發送廣播失敗') if __name__ == '__main__': unittest.main()
結果以下:
四.經過unittest管理用例
例子1:當咱們有不少用例時,咱們想要跳過一些case去執行,咱們怎麼去作呢?
固然咱們能夠直接把這個用例註釋掉 可是這樣太low啦,因此這就引入了新的知識點:@unittest.skip('test_02'),在case上引入這個裝飾類 中間填寫的就是這個case
的名稱
代碼以下:
import unittest #導入請求類 from basis import method class MyTestCase(unittest.TestCase): def setUp(self): #初始化請求類 self.run=method.RunMain() # self.successful=self.test_01() def test_01(self): ''' 查詢紅包餘額接口 get請求方法 :return: ''' print('這是第一個用例') url='http://ios.wecash.net/biz/wallet/amount' data='CUSTOMER_ID=56256A951F81F0BCA10780AD02139B29' rep=self.run.get(url,data) print(rep) # if rep['successful'] == 1: # print('經過') # else: # print('沒經過') #對返回結果進行斷言判斷(內置斷言) 提取的值,斷言的值,匹配不上的輸出信息 self.assertEqual(rep['successful'],1, '查詢紅包餘額接口失敗') #將successful放入全局變量 globals()['successful'] =rep['successful'] # return rep['successful'] # self.assertEqual()驗證兩個是否相等 # self.assertNotEqual() 判斷不想等 # self.assertTrue()布爾類型判斷 若是返回是True == True @unittest.skip('test_02') def test_02(self): ''' 豆瓣API,發送一條廣播 :return: ''' print('這是第二個用例') #從全局變量中獲取 print(globals()['successful']) url='https://api.douban.com/shuo/statuses/' data="{'media': [{'imgsrc': 'http://icanhascheezburger.files.wordpress.com/2009/04/funny-pictures-hairless-cat-phones- home.jpg', 'src': 'http://www.mapsofwar.com/photos/EMPIRE17.swf', 'type': 'flash'}]}" rep=self.run.post(url,data) print(rep) self.assertEqual(rep['msg'],'須要登陸','發送廣播失敗') if __name__ == '__main__': unittest.main()
結果以下:
例子2:咱們如今執行程序仍是經過unittest.main(),來執行MyTestCase下面的全部的用例,那麼有沒有別的方式來執行用例呢?
答案固然是有的,可是這種方式與main()方式來執行有什麼區別呢?
#建立一個放用例的容器 suite=unittest.TestSuite() #須要往這個容器裏面去添加case suite.addTest(MyTestCase('test_01')) suite.addTest(MyTestCase('test_02')) #執行(將咱們的容器放進去) unittest.TextTestRunner.run(suite)
例子3:咱們在書寫用例的時候並非一我的在寫,項目組的小夥伴們也在寫,不可能吧全部用例都寫在一個py文件裏,因此咱們要引入一個新的知識點:
testLoader
代碼以下:
from API.method_4_double import MyTestCase as MyTestCase2
# 此用法能夠同時測試多個類
suite1 = unittest.TestLoader().loadTestsFromTestCase(MyTestCase)
suite2 = unittest.TestLoader().loadTestsFromTestCase(MyTestCase2)
suite = unittest.TestSuite([suite1, suite2])
unittest.TextTestRunner(verbosity=2).run(suite)
結果以下:
下面針對上述腳本中應用到的unittest模塊下的幾個成員進行簡單的介紹,以便於理解上述代碼:
TestCase:全部測試用例的基本類,給一個測試方法的名字,就會返回一個測試用例實例;
TestSuit:組織測試用例的實例,支持測試用例的添加和刪除,最終將傳遞給 testRunner進行測試執行;
TextTestRunner:進行測試用例執行的實例,其中Text的意思是以文本形式顯示測試結果。測試的結果會保存到TextTestResult實例中,包括運行了多少測試用例,成功了多少,失敗了多少等信息;
TestLoader:用來加載TestCase到TestSuite中的,其中有幾個 loadTestsFrom__()方法,就是從各個地方尋找TestCase,建立它們的實例,而後add到TestSuite中,再返回一個TestSuite實例;
五.使用unittest和HTMLTestRunner結合生成測試報告
下載網上一個開源的HTMLTestRunner.py文件,這個是生成報告的模板文件
下載地址:http://www.javashuo.com/article/p-xfjruain-q.html
重點:當咱們下載下來的時候 咱們要辦這個文件放在python安裝目錄
/Users/wangsen/miniconda3/lib/python3.5/site-packages/HTMLTestRunner.py 這樣就大功告成了
如今咱們來使用HTMLTestRunner來生成測試報告
代碼以下:
import unittest
from API import method_5
import HTMLTestRunner
################以一個類的維度控制測試用例的執行#############
cases=unittest.TestLoader().loadTestsFromTestCase(method_5.MyTestCase)
mysuite=unittest.TestSuite([cases])
filename = '/Users/wangsen/PycharmProjects/lufei_learn/report/test.html'
#一二進制方式打開文件,準備寫
file_object=open(filename,'wb')
#使用HTMLTestRunner配置參數,輸出報告路徑,報告標題,描述,都可以配置
runner=HTMLTestRunner.HTMLTestRunner(
stream=file_object,
title='報告主題:接口測試報告',
description='報告詳細描述',
)
#運行測試集合
runner.run(mysuite)
結果以下:
7.DDT數據驅動執行接口測試
- 代碼複用率高。同一測試邏輯編寫一次,能夠被多條測試數據複用,提升了測試代碼的複用率,同時能夠提升測試腳本的編寫效率。
- 異常排查效率高。測試框架依據測試數據,每條數據生成一條測試用例,用例執行過程相互隔離,在其中一條失敗的狀況下,不會影響其餘的測試用例。
- 代碼的可維護性高。清晰的測試框架,利於其餘測試工程師閱讀,提升了代碼的可維護性。
1.以元組, 列表,字典傳遞數據
好了 ,上代碼:
import unittest #導入ddt的模塊 from ddt import ddt,data,unpack from basis import method #用ddt來修飾咱們的類 @ddt class MyTestCase(unittest.TestCase): def setUp(self): self.run=method.RunMain() #引入data來修飾咱們的數據,參數化數據 @data(('查詢紅包餘額接口','get','http://ios.wecash.net/biz/wallet/amount','CUSTOMER_ID=56256A951F81F0BCA10780AD02139B29','successful',1), ('發送一條廣播','post','https://api.douban.com/shuo/statuses/',"{'media': [{'imgsrc': 'http://icanhascheezburger.files.wordpress.com/2009/04/funny-pictures-hairless-cat-phones- home.jpg', 'src': 'http://www.mapsofwar.com/photos/EMPIRE17.swf', 'type': 'flash'}]}",'msg','須要登陸')) #導入參數 @unpack def test_something(self,name,method,url,data,assertion,value): print('測試接口:%s'%name) rep=self.run.run_main(url,method,data) print(rep) self.assertEqual(rep[str(assertion)],value,'測試不經過') if __name__ == '__main__': unittest.main()
代碼解析:
若是咱們要引用ddt實現數據驅動 首先須要導入模塊from ddt import ddt,data,unpack
@ddt是用來是用來裝飾unittest類的
傳入元組、字典、列表等複雜結構數據,@data 方法結合 @unpack方法使用
2.以文件做爲數據傳遞@file_data
代碼以下
import unittest from basis import method from ddt import ddt,file_data #裝飾ddt @ddt class MyTestCase(unittest.TestCase): def setUp(self): self.run=method.RunMain() #導入file_data()獲取json中的數據,在test_something測試函數中接受 @file_data("test_data_list.json") def test_something(self,message): name, method, url, data, assertion, value=message[0],message[1],message[2],message[3],message[4],message[5] print("測試接口爲:%s"%name) rep=self.run.run_main(url,method,data) print(rep) self.assertEqual(rep[assertion],value,'測試不經過') if __name__ == '__main__': unittest.main()
json文件格式以下
[ ["查詢紅包餘額接口","get","http://ios.wecash.net/biz/wallet/amount","CUSTOMER_ID=56256A951F81F0BCA10780AD02139B29","successful",1], ["發送一條廣播","post","https://api.douban.com/shuo/statuses/","{'media': [{'imgsrc': 'http://icanhascheezburger.files.wordpress.com/2009/04/funny-pictures-hairless-cat-phones- home.jpg', 'src': 'http://www.mapsofwar.com/photos/EMPIRE17.swf', 'type': 'flash'}]}","msg","須要登陸"] ]
3.還有最後一種方式 那就是從excel中讀取數據
代碼以下:
import unittest from basis import method from ddt import ddt,data,unpack,file_data from API.ExcelUtil import ParseExcel excelPath='/Users/wangsen/PycharmProjects/lufei_learn/API/接口自動化測試數據.xlsx' sheetName='接口數據表' #建立ParseExcel類的實例對象 excel=ParseExcel(excelPath,sheetName) @ddt class MyTestCase(unittest.TestCase): def setUp(self): self.run=method.RunMain() # 導入excel的數據(若是@ddt.data()括號中傳的是一個方法,方法前須要加星號(*)) @data(*excel.getDatasFromSheet()) def test_something(self,message): name, method, url, data, assertion, value=tuple(message) print("測試接口爲:%s" % name) rep = self.run.run_main(url, method, data) print(rep) self.assertEqual(rep[assertion], value, '測試不經過') if __name__ == '__main__': unittest.main()
讀取excel數據的代碼以下:
import xlrd import traceback class ParseExcel(object): #excel路徑,sheet頁名稱 def __init__(self,excelPath,sheetName): try: #將讀取得excel加載到內存 self.wb=xlrd.open_workbook(excelPath) except Exception as e: print(traceback.format_exc()) else: #經過工做表名稱獲取一個工做表對象 self.sheet=self.wb.sheet_by_name(sheetName) #獲取工做表中存在數據的區域的最大行號 self.maxRowNum=self.sheet.nrows def getDatasFromSheet(self): #用於存放從工做表中讀取出來的數據 dataList=[] #由於工做表中的第一行是標題行,因此須要去掉 for i in range(1,self.maxRowNum): #行內容 列表類型 row = self.sheet.row_values(i) if row: temList=[] temList.append(row[1]) temList.append(row[2]) temList.append(row[3]) temList.append(row[4]) temList.append(row[5]) temList.append(row[6]) dataList.append(temList) # print(dataList) return dataList
8.將unittest於Jenkins結合併發送報告郵件(自動化測試項目)
咱們已經將unittest如何管理case如何生成報告如何進行數據驅動測試都已經講了,如今講一下如何將unittest變成python自動化測試框架。
本着一切都往高大上走的原則,咱們進行以下設計:
開發語言:python
應用模塊:requests
case管理:unittest框架
生成測試報告:HTMLTestRunner
數據如何存儲管理:能夠用mysql管理,excel管理,json管理
如何進行測試:Jenkins+unittest進行持續集成
問題
如何管理case(如何跳過case,case寫在哪)
如何case執行(如何管理,順序前後不是放在前面就先執行而是根據case命名的升序來執行)
如何解決case的依賴(定義全局變量,也能夠放在配置文件裏也能夠放在數據庫裏都是能夠的)
如何生成測試報告(放在安裝目錄 lib下面,py2到py3一些修改)