我們在用Unittest框架時(shí),生成html格式的報(bào)告一般都是用HTMLTestRunner.py這個(gè)第三方庫,大概使用方法如下:
with open(config.report_file, 'wb') as fp:
HTMLTestRunner(stream=fp,
title='[{}] 接口測試報(bào)告'.format(date),
verbosity=2,
description='每日定時(shí)接口測試,監(jiān)控線上接口情況').run(suite)
我們實(shí)例化一個(gè)HTMLTestRunner類的對象,并調(diào)用該類的run()方法,傳入的是unittest.TestSuite類的對象suite,執(zhí)行測試用例(測試套件)生成測試結(jié)果并寫入html模板中,生成網(wǎng)頁報(bào)告。在我們使用的時(shí)候,只要調(diào)用HTMLTestRunner().run()就行了,那么HTMLTestRunner().run()內(nèi)部做了哪些事呢?
在HTMLTestRunner.py中有一個(gè)類HTMLTestRunner,該類有一個(gè)方法run,該方法如下:
# HTMLTestRunner.py
class HTMLTestRunner(Template_mixin):
......
......
def run(self, test):
"Run the given test case or test suite."
result = _TestResult(self.verbosity)
test(result)
self.stopTime = datetime.datetime.now()
self.generateReport(test, result)
print('\nTime Elapsed: %s' % (self.stopTime-self.startTime), file=sys.stderr)
return result
run()接收一個(gè)參數(shù)test,根據(jù)注釋我們知道該參數(shù)代表test case 或者 test suite,也就是我們寫的測試用例或由測試用例組成的測試套件,那么test實(shí)際上就是TestSuite類或者TestCase類的實(shí)例對象,然后其中有一行代碼:
test(result)
這時(shí)候就納悶了,test是一個(gè)TestSuite或TestCase類的實(shí)例對象,把實(shí)例對象當(dāng)成函數(shù)調(diào)用是什么意思?
我們來寫一個(gè)類試試:
class Human:
def __init__(self, name):
self.name = name
def eat(self):
return '%s is eat' % self.name
one = Human('CJ')
one()
# TypeError: 'Human' object is not callable
定義一個(gè)Human類,然后實(shí)例化一個(gè)對象one,把one當(dāng)作函數(shù)直接調(diào)用,即one(),結(jié)果會報(bào)錯(cuò)TypeError: 'Human' object is not callable,看來我們的代碼存在問題。
這時(shí)候,我們引入一個(gè)東西,魔術(shù)方法__call__()
python中一切皆對象,函數(shù)也是對象,同時(shí)也是可調(diào)用對象(callable)。關(guān)于可調(diào)用對象,我們平時(shí)自定義的函數(shù)、內(nèi)置函數(shù)和類都屬于可調(diào)用對象,但凡是可以把一對括號
()應(yīng)用到某個(gè)對象身上都可稱之為可調(diào)用對象,判斷對象是否為可調(diào)用對象可以用函數(shù)callable一個(gè)類實(shí)例要變成一個(gè)可調(diào)用對象,只需要實(shí)現(xiàn)一個(gè)特殊方法
__call__()。
修改上面的例子:
class Human:
def __init__(self, name):
self.name = name
def eat(self):
return '%s is eat' % self.name
def __call__(self, *args, **kwargs):
print('object is callable')
one = Human('CJ')
one()
# object is callable
再次調(diào)用one實(shí)例對象后,輸出了__call()__方法里面打印的內(nèi)容,這說明,在類里面實(shí)現(xiàn)了__call__()魔術(shù)方法后,可以把類的實(shí)例化對象當(dāng)成函數(shù)調(diào)用,而實(shí)際調(diào)用的就是__call__()方法。
那么再回到上面的框架里面,既然可以寫成test(result),那說明test這個(gè)對象的類里面,一定實(shí)現(xiàn)了__call__()方法,所以我們再去看看__call__()方法里面是怎么處理的。因?yàn)?code>test是TestSuite類的實(shí)例,所以我們看看TestSuite類是怎么實(shí)現(xiàn)__call__()的
# unittest\suite.py
class TestSuite(BaseTestSuite):
......
def run(self, result, debug=False):
......
return result
在unittest\suite.py中,TestSuite里面有一個(gè)run()方法,并沒有實(shí)現(xiàn)__call__()方法,但是TestSuite是繼承自BaseTestSuite,我們再看看BaseTestSuite
# unittest\suite.py
class BaseTestSuite(object):
......
def run(self, result):
for index, test in enumerate(self):
if result.shouldStop:
break
test(result)
if self._cleanup:
self._removeTestAtIndex(index)
return result
def __call__(self, *args, **kwds):
return self.run(*args, **kwds)
在BaseTestSuite中,確實(shí)有__call__(),并且也有run()方法,而__call__()實(shí)際又調(diào)用了self.run()方法。
這里需要注意,由于TestSuite類是繼承自BaseTestSuite,并且兩者都實(shí)現(xiàn)了run()方法,那么實(shí)際執(zhí)行的時(shí)候,如果該類是屬于TestSuite,那么最終實(shí)際執(zhí)行的是TestSuite().run(),而不是BaseTestSuite().run()
那么,此時(shí),梳理出來的步驟就是:
我們初始化一個(gè)HTMLTestRunner類的實(shí)例,調(diào)用類方法run(),傳入的參數(shù)是TestSuite類的實(shí)例,
在HTMLTestRunner的run()方法中,把TestSuite類的實(shí)例當(dāng)成函數(shù)調(diào)用,這是由于TestSuite類的父類BaseTestSuite實(shí)現(xiàn)了魔術(shù)方法__call__(),而__call__()里面是調(diào)用self.run(),所有本質(zhì)上調(diào)用的則是TestSuite類的run()方法,繼續(xù)來看TestSuite里面run()做了些什么東西
# unittest\suite.py
class TestSuite(BaseTestSuite):
def run(self, result, debug=False):
topLevel = False
if getattr(result, '_testRunEntered', False) is False:
result._testRunEntered = topLevel = True
for index, test in enumerate(self):
if result.shouldStop:
break
if _isnotsuite(test):
self._tearDownPreviousClass(test, result)
self._handleModuleFixture(test, result)
self._handleClassSetUp(test, result)
result._previousTestClass = test.__class__
if (getattr(test.__class__, '_classSetupFailed', False) or
getattr(result, '_moduleSetUpFailed', False)):
continue
if not debug:
test(result)
else:
test.debug()
if self._cleanup:
self._removeTestAtIndex(index)
if topLevel:
self._tearDownPreviousClass(None, result)
self._handleModuleTearDown(result)
result._testRunEntered = False
return result
for index, test in enumerate(self):
self代表的是TestSuite類實(shí)例,經(jīng)過枚舉及for循環(huán)得到的test是TestCase實(shí)例
然后下面又是熟悉的test(result),那么跟上面的原理一樣,TestCase類或者它的父類里面肯定實(shí)現(xiàn)了__call__()方法,并且__call__()里面實(shí)際調(diào)用的是TestCase().run()方法,所以這里真正執(zhí)行的就是TestCase().run(),TestCase().run()里面則實(shí)現(xiàn)的是每個(gè)測試用例的執(zhí)行過程,最終得到執(zhí)行的結(jié)果。
源碼也印證了確實(shí)如此:
class TestCase(object):
....
....
def run(self, result=None):
....
return result
def __call__(self, *args, **kwds):
return self.run(*args, **kwds)
至此,完整的流程應(yīng)該是:
- 我們初始化一個(gè)
HTMLTestRunner類的實(shí)例,調(diào)用類方法run(),傳入的參數(shù)是TestSuite類的實(shí)例 - 由于
TestSuite類實(shí)現(xiàn)了__call__()魔術(shù)方法,所以在HTMLTestRunner的run()方法中 - 把
TestSuite類實(shí)例當(dāng)成函數(shù)調(diào)用,達(dá)到調(diào)用TestSuite類的run()方法的目的,在TestSuite的run()方法里遍歷出TestCase對象,而又由于TestCase類也實(shí)現(xiàn)了__call__()魔術(shù)方法,把TestCase對象當(dāng)成函數(shù)調(diào)用,實(shí)際執(zhí)行的是TestCase的run(),最終完成測試得到結(jié)果。
by 慢熱