unittest之TestResult类详解

TestResult

测试结果类,用来处理测试用例或测试集执行过程中的所有信息并最终输出,比如代码错误、异常、断言失败、skip等等。所以如果想要增加一些个性化的输出,可以通过或者此类或者基类(TestResult),扩展HTMLTestRunner的大神就是扩展了基类TestResult,增加了一下几个重要的统计属性和重写了基类的一些方法。大家可以去阅读一下它的源码。

本来想讲TextTestResult的,但发现其实TextTestResult也是扩展了TestResult类的,所以干脆就直接分析TestResult了,我们先看一下基类提供了哪些方法可供我们扩展:

  1. printErrors
def printErrors(self):
    "Called by TestRunner after test run"

注释已经说明了,在测试用例执行完成后才应该被TestRuner调用,我们可以看一下这个方法在哪个地方有调用过:
unittest之TestResult类详解_第1张图片
我们可以看到有两处调用了这个方法:第一处我们可以不看,因为并不是unittest框架里面的,我们直接看第二处,可以看到是在TextTestRunner的run方法中有调用,我们看看是在run方法中的什么位置调用了这个方法

class TestRunner(unittest.TextTestRunner):
    def run(self, test, skipped):
        "Run the given test case or test suite."
        # Same as unittest.TextTestRunner.run, except that it reports
        # skipped tests.
        result = self._makeResult()
        startTime = time.time()
        test(result)
        stopTime = time.time()
        timeTaken = stopTime - startTime
        result.printErrors()
        self.stream.writeln(result.separator2)
        run = result.testsRun
        if _unavail: #skipped:

从代码我们可以看出,result.printErrors()方法是在test(result)之后才调用的,所以这符合printErrors方法的调用场景了吧

  1. startTest
def startTest(self, test):
    "Called when the given test is about to be run"
    self.testsRun += 1
    self._mirrorOutput = False
    self._setupStdout()

从注释说明中可以看出,此方法是在用例准备执行之前会被调用一次,我们还是来看一下此方法的调用情况,同样可以看到在TestCase类中的run方法中有调用此方法:

def run(self, result=None):
    orig_result = result
    if result is None:
        result = self.defaultTestResult()
        startTestRun = getattr(result, 'startTestRun', None)
        if startTestRun is not None:
            startTestRun()

    self._resultForDoCleanups = result
    result.startTest(self)

    testMethod = getattr(self, self._testMethodName)
    if (getattr(self.__class__, "__unittest_skip__", False) or
        #省略下面的代码
        ...

可以看出确实是在testMethod执行前调用了一次。

  1. startTestRun
def startTestRun(self):
    """Called once before any tests are executed.

    See startTest for a method called before each test.
    """

跟startTest类似,用例执行之前调用,依然在TestCase类中的run方法可以看到,沿用上面startTest贴的run部分的代码,可以看到在执行startTest之前会去判断我们传入的result是否为None,如果为None,才会去初始化result,并判断result是否有startTestRun方法,如果有则执行startTestRun方法

  1. stopTest
def stopTest(self, test):
    """Called when the given test has been run"""
    self._restoreStdout()
    self._mirrorOutput = False

在用例被执行之后才会执行此方法,依然还是在TestCase类中的run方法可以看到,不过此方法有两处调用,我们来看一下代码:

第一处调用:

def run(self, result=None):
    #省略startTestRun和startTest部分代码
    ...

    testMethod = getattr(self, self._testMethodName)
    if (getattr(self.__class__, "__unittest_skip__", False) or
        getattr(testMethod, "__unittest_skip__", False)):
        # If the class or method was skipped.
        try:
            skip_why = (getattr(self.__class__, '__unittest_skip_why__', '')
                        or getattr(testMethod, '__unittest_skip_why__', ''))
            self._addSkip(result, skip_why)
        finally:
            result.stopTest(self)
        return
    try:
        success = False
        #省略下方代码
        ...

我们可以看到,此段代码在分析TestCase类的时候讲过,这是在判断我们编写的TestCase类是否又被skip装饰,或者TestCase类中写的方法是否又被skip装饰,如果有,则会执行一次stopTest方法(result.stopTest(self))

第二处调用:

try:
    success = False
    try:
        self.setUp()
        #省略了setUp处理部分
        ...
    else:
        try:
            testMethod()
        #省略了testMethod的处理部分
        ...

    cleanUpSuccess = self.doCleanups()
    success = success and cleanUpSuccess
    if success:
        result.addSuccess(self)
finally:
    result.stopTest(self)
    if orig_result is None:
        stopTestRun = getattr(result, 'stopTestRun', None)
        if stopTestRun is not None:
            stopTestRun()

我们可以看到,是在用例执行完成后,不管有没有什么错误或异常都会执行一次stopTest方法(result.stopTest(self))

  1. stopTestRun
def stopTestRun(self):
    """Called once after all tests are executed.

    See stopTest for a method called after each test.
    """

在所有用例执行完成之后被调用。跟startTestRun类似,当我们传入的参数result为None时,才会去执行这个方法

针对上面第2-4个方法我们举一个例子瞧一瞧:

#unittest_prac.py
import unittest
import sys
#扩展TestResult
class MyTestResult(unittest.TestResult):

    def startTest(self, test):
        print('startTest')

    def startTestRun(self):
        print('startTestRun')

    def stopTest(self, test):
        print('stopTest')

    def stopTestRun(self):
        print('stopTestRun')

class UnitTestCase(unittest.TestCase):

    def setUp(self):
        print("setup")

    def test01(self):
        print('test01')

    def tearDown(self):
        print('teardown')

if __name__ == '__main__':
    result = MyTestResult(sys.stdout,'test result',1)
    testcase = UnitTestCase('test01')
    testcase.run(result) #注意此处我传入了result参数

执行之后我们看一下结果:

C:\software\PythonWorkspace>python unittest_prac.py\
startTest\
setup\
test01\
teardown\
stopTest

我们可以看到确实调用了startTest、stopTest,没有去调用startTestRun、stopTestRun符合我们的分析结果。

  1. 其他可扩展的方法:
    addFailure,addSuccess,addSkip,addExpectedFailure,addUnexpectedSuccess
    这几个方法是针对测试用例执行的结果处理:
    • addFailure:测试用例error的时处理
    • addSuccess:测试用例执行成功时处理
    • addSkip:测试用例跳过时处理
    • addExpectedFailure:测试用例执行跟预期结果不一样,不如assert断言
    • addUnexpectedSuccess:测试用例结果期望失败,但最后成功了。

这几个方法也都是可以在TestCase类的run方法中找到调用的地方,大家可以自己去试试分析一下

TestResult类分析差不多就到这里了,如果想了解怎么扩展,大家可以去阅读一下TextTestResult类的方法,或者阅读一下扩展HTMLTestRunner大神的代码HTMLTestRunner.py

你可能感兴趣的:(python,unittest,python,unittest,TestResult,TextTestResult)