臺(tái)州seo網(wǎng)站推廣費(fèi)用昆明seo排名
什么是數(shù)據(jù)驅(qū)動(dòng)?
數(shù)據(jù)驅(qū)動(dòng)是指測(cè)試數(shù)據(jù)的變更從而驅(qū)動(dòng)自動(dòng)化測(cè)試的執(zhí)行,最終引起測(cè)試結(jié)果的改變。
如測(cè)試登錄接口,在使用數(shù)據(jù)驅(qū)動(dòng)前是這樣的(以下代碼只是簡(jiǎn)寫(xiě),并不規(guī)范):
def login_success():username = "John"password = "123456"expected_result = "success"login(username,password)#斷言等操作def login_fail_username_undefined():username = "tom"password = "123456"expected_result = "error"login(username,password)#斷言等操作def login_fail_password_wrong():username = "John"password = "000000"expected_result = "error"login(username,password)#斷言等操作
使用username = “John”,password = "123456"作為數(shù)據(jù),執(zhí)行測(cè)試用例的時(shí)候斷言是成功的,也就是說(shuō)登錄是成功的;換成username = “tom”,password = "123456"作為數(shù)據(jù),執(zhí)行測(cè)試用例的時(shí)候斷言是失敗的,也就是說(shuō)登錄是失敗的。由此可看出,測(cè)試數(shù)據(jù)不一樣,測(cè)試的結(jié)果也會(huì)不一樣;反過(guò)來(lái),我想得到什么樣的結(jié)果,我就得對(duì)應(yīng)的使用能驅(qū)使產(chǎn)生這一結(jié)果的數(shù)據(jù)。這就是數(shù)據(jù)驅(qū)動(dòng)測(cè)試的思想。
有了這種思想的指導(dǎo),我們進(jìn)而能實(shí)現(xiàn)數(shù)據(jù)與代碼的分離。上面的例子我們可以看到,除了數(shù)據(jù)不一樣之外,下面的(1)調(diào)用接口返回實(shí)際結(jié)果(2)預(yù)期結(jié)果與實(shí)際結(jié)果進(jìn)行斷言的操作每個(gè)測(cè)試用例都是一樣的。這時(shí)我們可以想,把(1)(2)步驟封裝在同一個(gè)方法里,然后將測(cè)試數(shù)據(jù)用某種方式收集起來(lái),最后用數(shù)據(jù)去驅(qū)動(dòng)這三個(gè)步驟的執(zhí)行,從而得到對(duì)應(yīng)不同的結(jié)果。
由此看得出來(lái),數(shù)據(jù)驅(qū)動(dòng)的核心是將數(shù)據(jù)與代碼抽離出來(lái),實(shí)現(xiàn)數(shù)據(jù)與代碼的解耦。這樣一來(lái),代碼的邏輯就分成了兩塊:第一塊是測(cè)試用例的準(zhǔn)備,第二塊是測(cè)試用例的執(zhí)行。
為什么要數(shù)據(jù)驅(qū)動(dòng)?
還拿上面代碼的例子舉例。過(guò)去都是有幾個(gè)測(cè)試用例就寫(xiě)幾個(gè)方法,這樣的工作非常的重復(fù)且無(wú)效率,看起來(lái)特別低級(jí)。而我們把所有的測(cè)試數(shù)據(jù)收集起來(lái),測(cè)試的代碼都寫(xiě)在一個(gè)方法里,把測(cè)試數(shù)據(jù)當(dāng)做參數(shù)傳給方法去執(zhí)行,這樣一來(lái)就能節(jié)省很多重復(fù)的代碼,效率也就提升了。
數(shù)據(jù)驅(qū)動(dòng)的演變
受上面思想的啟發(fā),我們可以將測(cè)試數(shù)據(jù)用列表裝起來(lái),接著測(cè)試用例的代碼都寫(xiě)在一個(gè)方法里,再在方法里去調(diào)用需要用到的測(cè)試數(shù)據(jù)。
第一階段:測(cè)試數(shù)據(jù)放在列表中
import unittest
from unittest import TestCase#模擬的登錄接口
def login(username=None, password=None):"""登錄"""if (not username) or (not password):# 用戶(hù)名或者密碼為空return {"msg": "empty"}if username == 'John' and password == '123456':# 正確的用戶(hù)名和密碼return {"msg": "success"}return {"msg": "error"}#將數(shù)據(jù)從代碼中抽離出來(lái),單獨(dú)用列表嵌套字典的方式存儲(chǔ)
datas = [{"username": "John", "password": "123456", "expected_result": "success"},{"username": "tom", "password": "123456", "expected_result": "error"},{"username": "John", "password": "000000", "expected_result": "error"}
]#測(cè)試類(lèi)
class test_login(unittest.TestCase):#測(cè)試方法,封裝測(cè)試用例代碼def test_login1(self):#將數(shù)據(jù)注入到測(cè)試代碼中for data in datas:for param in data:username = data["username"]password = data["password"]expected_result = data["expected_result"]actual_result = login(username,password)["msg"] TestCase.assertTrue(expected_result,actual_result)if __name__ == "__main__":unittest.main()
這樣寫(xiě)下來(lái),雖然測(cè)試數(shù)據(jù)是與測(cè)試用例的代碼解耦了,但把測(cè)試數(shù)據(jù)全寫(xiě)出來(lái)未免顯得過(guò)于臃腫,測(cè)試數(shù)據(jù)多了也不好管理。這時(shí)我們可以考慮將測(cè)試數(shù)據(jù)全放到excel中,然后讀取excel的數(shù)據(jù)出來(lái)供測(cè)試代碼使用。這樣我們就進(jìn)入到了下面的第二階段。
第二階段:測(cè)試數(shù)據(jù)存放在excel中
import unittest
from unittest import TestCase
from interface_test import excel_handler#模擬的登錄接口
def login(username=None, password=None):"""登錄"""if (not username) or (not password):# 用戶(hù)名或者密碼為空return {"msg": "empty"}if username == 'John' and password == '123456':# 正確的用戶(hù)名和密碼return {"msg": "success"}return {"msg": "error"}#測(cè)試類(lèi),測(cè)試數(shù)據(jù)的準(zhǔn)備及測(cè)試用例的代碼都在同一個(gè)類(lèi)內(nèi)用不同方法呈現(xiàn)
class test_login(unittest.TestCase):#準(zhǔn)備測(cè)試數(shù)據(jù)方法。讀取excel中的數(shù)據(jù),將其作為結(jié)果返回def data_prepared(self):data = excel_handler.excel_handler("cases.xlsx").get_data("login")return data#測(cè)試方法,封裝測(cè)試用例代碼def test_login1(self):#調(diào)用測(cè)試數(shù)據(jù)準(zhǔn)備的方法,獲得所有的測(cè)試數(shù)據(jù)datas = self.data_prepared()#將數(shù)據(jù)注入到測(cè)試代碼中for data in datas:for i in data:username = data[0]password = data[1]expected_result = data[2]actual_result = login(username,password)["msg"]TestCase.assertTrue(expected_result,actual_result)if __name__ == "__main__":unittest.main()
這個(gè)階段只是將測(cè)試數(shù)據(jù)的準(zhǔn)備做了優(yōu)化,讓測(cè)試數(shù)據(jù)更好管理。但在測(cè)試用例的執(zhí)行中,我們遇到了一個(gè)新的問(wèn)題。
下面是我們通過(guò)unittest執(zhí)行測(cè)試用例的結(jié)果:
可看到我們準(zhǔn)備的測(cè)試數(shù)據(jù)不止有一組,但結(jié)果返回的卻只有一個(gè)。這說(shuō)明只執(zhí)行了第一組測(cè)試數(shù)據(jù),后面的測(cè)試數(shù)據(jù)都沒(méi)執(zhí)行。究其原因是因?yàn)閡nittest只會(huì)以方法為單位來(lái)執(zhí)行,也就是說(shuō)一個(gè)方法對(duì)應(yīng)一條測(cè)試用例。所以不管你準(zhǔn)備了多少測(cè)試數(shù)據(jù),你只在一個(gè)方法里面執(zhí)行這些數(shù)據(jù),那unittest只會(huì)把它當(dāng)成一條測(cè)試用例,因此結(jié)果也只有一個(gè)。所以我們需要對(duì)測(cè)試用例的執(zhí)行部分進(jìn)行優(yōu)化。
第三階段 引入ddt模塊
這時(shí)我們就需要使用到python的ddt模塊來(lái)進(jìn)行數(shù)據(jù)驅(qū)動(dòng)。
python中的ddt是什么?
ddt(Data Driven Testing)是python的第三方模塊,用來(lái)進(jìn)行數(shù)據(jù)驅(qū)動(dòng)。
ddt在框架中扮演什么樣的角色?
ddt在接口自動(dòng)化測(cè)試框架中負(fù)責(zé)兩塊內(nèi)容:接收來(lái)自excel的數(shù)據(jù);用數(shù)據(jù)驅(qū)動(dòng)測(cè)試用例的執(zhí)行。
ddt原理
要知道for循環(huán)為什么驅(qū)動(dòng)不了unittest測(cè)試用例的執(zhí)行,而ddt卻能驅(qū)動(dòng)的原因,得先知道ddt的原理。
下面是ddt源碼中三個(gè)重要函數(shù):ddt、unpack、data的代碼:
def ddt(cls):"""Class decorator for subclasses of ``unittest.TestCase``.Apply this decorator to the test case class, and thendecorate test methods with ``@data``.For each method decorated with ``@data``, this will effectively create asmany methods as data items are passed as parameters to ``@data``.The names of the test methods follow the pattern``original_test_name_{ordinal}_{data}``. ``ordinal`` is the position of thedata argument, starting with 1.For data we use a string representation of the data value converted into avalid python identifier. If ``data.__name__`` exists, we use that instead.For each method decorated with ``@file_data('test_data.json')``, thedecorator will try to load the test_data.json file located relativeto the python file containing the method that is decorated. It will,for each ``test_name`` key create as many methods in the list of valuesfrom the ``data`` key."""for name, func in list(cls.__dict__.items()):if hasattr(func, DATA_ATTR):for i, v in enumerate(getattr(func, DATA_ATTR)):test_name = mk_test_name(name, getattr(v, "__name__", v), i)test_data_docstring = _get_test_data_docstring(func, v)if hasattr(func, UNPACK_ATTR):if isinstance(v, tuple) or isinstance(v, list):add_test(cls,test_name,test_data_docstring,func,*v)else:# unpack dictionaryadd_test(cls,test_name,test_data_docstring,func,**v)else:add_test(cls, test_name, test_data_docstring, func, v)delattr(cls, name)elif hasattr(func, FILE_ATTR):file_attr = getattr(func, FILE_ATTR)process_file_data(cls, name, func, file_attr)delattr(cls, name)return cls
def unpack(func):"""Method decorator to add unpack feature."""setattr(func, UNPACK_ATTR, True)return func
def data(*values):"""Method decorator to add to your test methods.Should be added to methods of instances of ``unittest.TestCase``."""global index_lenindex_len = len(str(len(values)))return idata(values)
由此可看出,ddt的方法是unittest的TestCase的繼承類(lèi),所以ddt是要跟unittest結(jié)合使用的。把ddt這個(gè)裝飾器定義在測(cè)試類(lèi)上,然后在每個(gè)測(cè)試方法上定義data這個(gè)裝飾器,由此每個(gè)上面定義了data裝飾器的方法都會(huì)創(chuàng)建多個(gè)與data里數(shù)據(jù)組數(shù)一樣多的方法與每一組的數(shù)據(jù)相匹配。舉個(gè)例子,如果你有一組數(shù)據(jù):[a,b,c],那么會(huì)分別創(chuàng)建與三個(gè)與a、b、c對(duì)應(yīng)的方法。每一個(gè)方法里面的代碼內(nèi)容是一樣的,但是用到的數(shù)據(jù)會(huì)不一樣。
代碼實(shí)操
import unittest
import ddt"""
使用ddt步驟
1.導(dǎo)入ddt:import ddt
2.在需要使用ddt的class上面加上裝飾器:@ddt.ddt
3.在需要準(zhǔn)備測(cè)試用例數(shù)據(jù)的方法上加上裝飾器:@ddt.data(*data_name) 其中data_name可以是事先定義的列表、字典等,也可以是
通過(guò)openpyxl獲取的excel數(shù)據(jù)
4.方法中傳入變量用于接收數(shù)據(jù)
"""
def login(username=None, password=None):"""登錄"""if (not username) or (not password):# 用戶(hù)名或者密碼為空return {"msg": "empty"}if username == 'yuz' and password == '123456':# 正確的用戶(hù)名和密碼return {"msg": "success"}return {"msg": "error"}#通過(guò)導(dǎo)入excel_handler類(lèi)的get_data方法獲取excel中的數(shù)據(jù)
from interface_test.excel_handler import excel_handler
data = excel_handler("cases.xlsx").get_data("login")#在測(cè)試類(lèi)上加上裝飾器:@ddt.ddt
@ddt.ddt
class test_login(unittest.TestCase):#在需要使用數(shù)據(jù)的方法上加上裝飾器:@ddt:data@ddt.data(*data)def test_login(self,cases):#由于excel中的數(shù)據(jù)是字符串類(lèi)型的,所以需要把字符串類(lèi)型轉(zhuǎn)換成列表datas = eval(cases["data"])#準(zhǔn)備測(cè)試數(shù)據(jù)username = datas["mobile_phone"]password = datas["pwd"]#調(diào)用接口返回實(shí)際結(jié)果actual_result = login(username,password)#預(yù)期結(jié)果expected_result = '{"msg":"success"}'#預(yù)期結(jié)果及實(shí)際結(jié)果進(jìn)行斷言self.assertTrue(actual_result,expected_result)
執(zhí)行結(jié)果: