如何理解 HTMLTestRunner 中 test (result)?UnitTest是如何運(yùn)行的?

我們在用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è)TestSuiteTestCase類的實(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í)例,

HTMLTestRunnerrun()方法中,把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)得到的testTestCase實(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ù)方法,所以在HTMLTestRunnerrun()方法中
  • TestSuite類實(shí)例當(dāng)成函數(shù)調(diào)用,達(dá)到調(diào)用TestSuite類的run()方法的目的,在TestSuiterun()方法里遍歷出TestCase對象,而又由于TestCase類也實(shí)現(xiàn)了__call__()魔術(shù)方法,把TestCase對象當(dāng)成函數(shù)調(diào)用,實(shí)際執(zhí)行的是TestCaserun(),最終完成測試得到結(jié)果。

by 慢熱

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容