我们在用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('\nTime Elapsed: %s' % (self.stopTime-self.startTime), file=sys.stderr)
return result

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

test(result)

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

我们来写一个类试试:

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__()方法里面是怎么处理的。因为test是TestSuite类的实例,所以我们看看TestSuite类是怎么实现__call__()的

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

在unittest\suite.py中,TestSuite里面有一个run()方法,并没有实现__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中,确实有__call__(),并且也有run()方法,而__call__()实际又调用了self.run()方法。

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

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

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

在HTMLTestRunner的run()方法中,把TestSuite类的实例当成函数调用,这是由于TestSuite类的父类BaseTestSuite实现了魔术方法__call__(),而__call__()里面是调用self.run(),所有本质上调用的则是TestSuite类的run()方法,继续来看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类实例,经过枚举及for循环得到的test是TestCase实例

然后下面又是熟悉的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__()魔术方法,所以在HTMLTestRunner的run()方法中
  • 把TestSuite类实例当成函数调用,达到调用TestSuite类的run()方法的目的,在TestSuite的run()方法里遍历出TestCase对象,而又由于TestCase类也实现了__call__()魔术方法,把TestCase对象当成函数调用,实际执行的是TestCase的run(),最终完成测试得到结果。

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

如何理解 HTMLTestRunner 中 test (result)?UnitTest是如何运行的?的更多相关文章

  1. Python 同一文件中,有unittest不执行“if __name__ == '__main__”,不生成HTMLTestRunner测试报告的解决方案

    1.问题:Python中同一个.py文件中同时用unittest框架和HtmlReport框架后,HtmlReport不被执行. 2.为什么?其实不是HtmlReport不被执行,也不是HtmlRep ...

  2. 在实际工作中使用requests+unittest进行接口测试

    之前学习python做接口测试时,用的时requests+excel的方式来进行接口测试,后来在工作中也用unittest来做了一个项目的接口测试,接口测试用例完全基于unittest来编写,把大致步 ...

  3. 如何理解javaSript中函数的参数是按值传递

    本文是我基于红宝书<Javascript高级程序设计>中的第四章,4.1.3传递参数小节P70,进一步理解javaSript中函数的参数,当传递的参数是对象时的传递方式. (结合资料的个人 ...

  4. 深入理解Java中的String

    一.String类 想要了解一个类,最好的办法就是看这个类的实现源代码,来看一下String类的源码: public final class String implements java.io.Ser ...

  5. 简单理解Struts2中拦截器与过滤器的区别及执行顺序

    简单理解Struts2中拦截器与过滤器的区别及执行顺序 当接收到一个httprequest , a) 当外部的httpservletrequest到来时 b) 初始到了servlet容器 传递给一个标 ...

  6. 全面理解JavaScript中的闭包的含义及用法

    1.什么是闭包 闭包:闭包就是能够读取其他函数内部变量的函数;闭包简单理解成“定义在一个函数内部的函数”. 闭包的形式:即内部函数能够使用它所在级别的外部函数的参数,属性或者内部函数等,并且能在包含它 ...

  7. 理解 Python 中的可变参数 *args 和 **kwargs:

    默认参数:  Python是支持可变参数的,最简单的方法莫过于使用默认参数,例如: def getSum(x,y=5): print "x:", x print "y:& ...

  8. 深入理解koa中的co源码

    阅读目录 一:理解Generator 二:理解js函数柯里化 三:理解Thunk函数 四:理解CO源码 回到顶部 一:理解Generator 在看co源码之前,我们先来理解下Generator函数.G ...

  9. 理解ES7中的async/await

    理解ES7中的async/await 优势是:就是解决多层异步回调的嵌套 从字面上理解 async/await, async是 "异步"的含义,await可以认为是 async w ...

随机推荐

  1. VRChat之转移地图缓存

    我的电脑是win10,win10的缓存地址和名称可能和win7的名字有所不同. win10缓存路径:C:\Users\Administrator\AppData\LocalLow\VRChat\VRC ...

  2. LeetCode 1 Two Sum——在数组上遍历出花样

    本文始发于个人公众号:TechFlow   今天是周末,和大家一起来看一道算法题.这道题是大名鼎鼎的LeetCode的第一题,也是面试当中非常常见的一道面试题.题目不难,但是对于初学者来说应该还是很有 ...

  3. JVM内存结构探秘及编码实战

    了解JVM内存结构的目的 在Java的开发过程中,因为有JVM自动内存管理机制,不再需要像在C.C++开发那样手动释放对象的内存空间,不容易出现内存泄漏和内存溢出的问题.但是,正是由于把内存管理的权利 ...

  4. DEVOPS技术实践_22:根据参数传入条件控制执行不同stage

    前面学习了参数的传递和调用,下面研究一下根据参数作为条件执行不同的stage 使用叫when 和expression控制某一个stage的运行, 运行场景例如写了多个stage,这个pipeline脚 ...

  5. 只用这 6 个字符,就可以写出任意 JavaScript 代码!

    你可能在网上见过有人用 几个不同的字符写的各种稀奇古怪的 JavaScript 代码,虽然看起来奇怪,但是能正常运行!比如这个: (!(~+[])+{})[--[~+""][+[] ...

  6. 洛谷$P2523\ [HAOI2011]\ Problem\ c$ $dp$

    正解:$dp$ 解题报告: 传送门$QwQ$ 首先港下不合法的情况.设$sum_i$表示$q\geq i$的人数,当且仅当$sum_i>n-i+1$时无解. 欧克然后考虑这题咋做$QwQ$. 一 ...

  7. 详解js的bind、call、apply

    详解js的bind.call.apply 说明 虽然bind.call.apply都是js很基础的一块知识,但是我从未认真总结过这三者的区别. 由于公司后端是用的微服务架构,又没有中间层对接,导致前端 ...

  8. C# 添加文本、图片到PDF文档(基于Spire.Cloud.PDF.SDK)

    Spire.Cloud.PDF.SDK提供了接口PdfTextApi及PdfImagesApi用于添加文本和图片到PDF文档,添加文本时,可格式化文本样式,包括文本字体类型.字号.字体样式.文本颜色. ...

  9. 《算法笔记》之基础C/C++进阶

    这一次主要讲C++不同于C的地方:类. 1.类的定义 定义一个类,本质上是定义一个数据类型的蓝图.这实际上并没有定义任何数据,但它定义了类的名称意味着什么,也就是说,它定义了类的对象包括了什么,以及可 ...

  10. 删除centos自带的openjdk

    [wj@master hadoop]$ rpm -qa | grep javajava-1.7.0-openjdk-1.7.0.191-2.6.15.5.el7.x86_64python-javapa ...