• 如何理解 HTMLTestRunner 中 test (result)?UnitTest是如何运行的?


    我们在用Unittest框架时,生成html格式的报告一般都是用HTMLTestRunner.py这个第三方库,大概使用方法如下:

    with open(config.report_file, 'wb') as fp:
        HTMLTestRunner(stream=fp,
                       title='[{}] 接口测试报告'.format(date),
                       verbosity=2,
                       description='每日定时接口测试,监控线上接口情况').run(suite)
    

      

    我们实例化一个HTMLTestRunner类的对象,并调用该类的run()方法,传入的是unittest.TestSuite类的对象suite,执行测试用例(测试套件)生成测试结果并写入html模板中,生成网页报告。在我们使用的时候,只要调用HTMLTestRunner().run()就行了,那么HTMLTestRunner().run()内部做了哪些事呢?

    HTMLTestRunner.py中有一个类HTMLTestRunner,该类有一个方法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('
    Time Elapsed: %s' % (self.stopTime-self.startTime), file=sys.stderr)
            return result

    run()接收一个参数test,根据注释我们知道该参数代表test case 或者 test suite,也就是我们写的测试用例或由测试用例组成的测试套件,那么test实际上就是TestSuite类或者TestCase类的实例对象,然后其中有一行代码:

    test(result)

    这时候就纳闷了,test是一个TestSuiteTestCase类的实例对象,把实例对象当成函数调用是什么意思?

    我们来写一个类试试:

    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

    定义一个Human类,然后实例化一个对象one,把one当作函数直接调用,即one(),结果会报错TypeError: 'Human' object is not callable,看来我们的代码存在问题。

    这时候,我们引入一个东西,魔术方法__call__()

    python 中一切皆对象,函数也是对象,同时也是可调用对象(callable)。

    关于可调用对象,我们平时自定义的函数、内置函数和类都属于可调用对象,但凡是可以把一对括号 () 应用到某个对象身上都可称之为可调用对象,判断对象是否为可调用对象可以用函数 callable

    一个类实例要变成一个可调用对象,只需要实现一个特殊方法__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

    再次调用one实例对象后,输出了__call()__方法里面打印的内容,这说明,在类里面实现了__call__()魔术方法后,可以把类的实例化对象当成函数调用,而实际调用的就是__call__()方法。

    那么再回到上面的框架里面,既然可以写成test(result),那说明test这个对象的类里面,一定实现了__call__()方法,所以我们再去看看__call__()方法里面是怎么处理的。因为testTestSuite类的实例,所以我们看看TestSuite类是怎么实现__call__()

    # unittestsuite.py
    class TestSuite(BaseTestSuite):
        ......
        def run(self, result, debug=False):
            ......
            return result

    unittestsuite.py中,TestSuite里面有一个run()方法,并没有实现__call__()方法,但是TestSuite是继承自BaseTestSuite,我们再看看BaseTestSuite

    # unittestsuite.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中,确实有__call__(),并且也有run()方法,而__call__()实际又调用了self.run()方法。

    这里需要注意,由于TestSuite类是继承自BaseTestSuite,并且两者都实现了run()方法,那么实际执行的时候,如果该类是属于TestSuite,那么最终实际执行的是TestSuite().run(),而不是BaseTestSuite().run()

    那么,此时,梳理出来的步骤就是:

    我们初始化一个HTMLTestRunner类的实例,调用类方法run(),传入的参数是TestSuite类的实例,

    HTMLTestRunnerrun()方法中,把TestSuite类的实例当成函数调用,这是由于TestSuite类的父类BaseTestSuite实现了魔术方法__call__(),而__call__()里面是调用self.run(),所有本质上调用的则是TestSuite类的run()方法,继续来看TestSuite里面run()做了些什么东西

    # unittestsuite.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类实例,经过枚举及for循环得到的testTestCase实例

    然后下面又是熟悉的test(result),那么跟上面的原理一样,TestCase类或者它的父类里面肯定实现了__call__()方法,并且__call__()里面实际调用的是TestCase().run()方法,所以这里真正执行的就是TestCase().run()TestCase().run()里面则实现的是每个测试用例的执行过程,最终得到执行的结果。

    源码也印证了确实如此:

    class TestCase(object):
        ....
        ....
        def run(self, result=None):
            ....
            return result
            
        def __call__(self, *args, **kwds):
            return self.run(*args, **kwds)

    至此,完整的流程应该是:

    • 我们初始化一个HTMLTestRunner类的实例,调用类方法run(),传入的参数是TestSuite类的实例
    • 由于TestSuite类实现了__call__()魔术方法,所以在HTMLTestRunnerrun()方法中
    • TestSuite类实例当成函数调用,达到调用TestSuite类的run()方法的目的,在TestSuiterun()方法里遍历出TestCase对象,而又由于TestCase类也实现了__call__()魔术方法,把TestCase对象当成函数调用,实际执行的是TestCaserun(),最终完成测试得到结果。

    文章来源:https://www.jianshu.com/p/2b2e8395e17d

  • 相关阅读:
    Maven核心简析
    块/文件/对象存储对比性概述
    Java SE-基本数据类型对应包装类
    Maven+eclipse快速入门
    IaaS、PaaS、SaaS、CaaS、MaaS五者的区别
    Collections.shuffle()源码分析
    java集合继承关系图
    ArrayList和LinkedList的区别
    ArrayList的实现原理
    session以及分布式服务器session共享
  • 原文地址:https://www.cnblogs.com/liuyanhang/p/11678870.html
Copyright © 2020-2023  润新知