如何理解 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('\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是如何运行的?的更多相关文章
- Python 同一文件中,有unittest不执行“if __name__ == '__main__”,不生成HTMLTestRunner测试报告的解决方案
1.问题:Python中同一个.py文件中同时用unittest框架和HtmlReport框架后,HtmlReport不被执行. 2.为什么?其实不是HtmlReport不被执行,也不是HtmlRep ...
- 在实际工作中使用requests+unittest进行接口测试
之前学习python做接口测试时,用的时requests+excel的方式来进行接口测试,后来在工作中也用unittest来做了一个项目的接口测试,接口测试用例完全基于unittest来编写,把大致步 ...
- 如何理解javaSript中函数的参数是按值传递
本文是我基于红宝书<Javascript高级程序设计>中的第四章,4.1.3传递参数小节P70,进一步理解javaSript中函数的参数,当传递的参数是对象时的传递方式. (结合资料的个人 ...
- 深入理解Java中的String
一.String类 想要了解一个类,最好的办法就是看这个类的实现源代码,来看一下String类的源码: public final class String implements java.io.Ser ...
- 简单理解Struts2中拦截器与过滤器的区别及执行顺序
简单理解Struts2中拦截器与过滤器的区别及执行顺序 当接收到一个httprequest , a) 当外部的httpservletrequest到来时 b) 初始到了servlet容器 传递给一个标 ...
- 全面理解JavaScript中的闭包的含义及用法
1.什么是闭包 闭包:闭包就是能够读取其他函数内部变量的函数;闭包简单理解成“定义在一个函数内部的函数”. 闭包的形式:即内部函数能够使用它所在级别的外部函数的参数,属性或者内部函数等,并且能在包含它 ...
- 理解 Python 中的可变参数 *args 和 **kwargs:
默认参数: Python是支持可变参数的,最简单的方法莫过于使用默认参数,例如: def getSum(x,y=5): print "x:", x print "y:& ...
- 深入理解koa中的co源码
阅读目录 一:理解Generator 二:理解js函数柯里化 三:理解Thunk函数 四:理解CO源码 回到顶部 一:理解Generator 在看co源码之前,我们先来理解下Generator函数.G ...
- 理解ES7中的async/await
理解ES7中的async/await 优势是:就是解决多层异步回调的嵌套 从字面上理解 async/await, async是 "异步"的含义,await可以认为是 async w ...
随机推荐
- Django 中配置MySQL数据库
在Django的项目中会默认使用sqlite的数据库 配置MySQL需要在setting.py 里加入以下设置: 配置数据库 DATABASES = { 'default': { 'ENGINE': ...
- ant design 的Table组件固定表头时不对齐
现在有一个表格,里面的列数是不固定的(可以重复写入数据),且列数行数都可能很多,就带来一个问题: 必须要固定表头,但是antd 的表格组件设置了固定表格 scroll={{x:1000,y:300}} ...
- [译文] 为什么你在 C# 里总是应该使用 "var" 关键字
[译文] Why You Should Always Use the 'var' Keyword in C# (为什么你总是应该在 C# 里使用 "var" 关键字) Using ...
- 用Tasker实现收到Android手机短信自动转发到邮箱
发送短信到邮箱的原理与 <用Tasker实现收到Android手机短信自动转发到邮箱>有些类似. 发送短信到邮箱是利用Ifttt这个服务将短信转发到邮箱中.Ifttt服务的可扩展性很强, ...
- alpha week 2/2 Scrum立会报告+燃尽图 06
此作业要求参见:https://edu.cnblogs.com/campus/nenu/2019fall/homework/9803 小组名称:“组长”组 组长:杨天宇 组员:魏新,罗杨美慧,王歆瑶, ...
- 洛谷$P3308\ [SDOI2014]LIS$ 网络流
正解:网络流 解题报告: 传送门$QwQ$ 恩先不考虑关于那个附加属性的限制,考虑这题怎么做? 首先这题从名字开始就让人忍不住联想起网络流24题里的那个最长不下降子序列?于是同样考虑预处理一个$f$呗 ...
- VS/Xamarin Android入门一
一.安装和配置(以Visual Studio Pro 2015为例) Visual Studio2015直接提供了这个插件的选择项,稍微提示一下,如果要安装的话,最好准备好十个小时的打算,而且是网速不 ...
- Java类成员之属性
属性含义:对应类中的成员变量. 语法格式:修饰符 数据类型 属性名 = 初始化值; 1.修饰符常用的有权限修饰符(private.default.protected.public) 以及其他修饰符(s ...
- MariaDB的备份与主从、高可用实践
1.编写脚本,支持让用户自主选择,使用mysqldump还是xtraback全量备份. [root@test-centos7-node1 scripts]# cat chose_backup_mysq ...
- 在A卡下的 Matlab 运行C/C++混编的GPU程序
首先将你的.MEX文件和matlab脚本放在一个文件夹下开始运行 如果出错查看是那个.MEX文件出错 用depends这个软件查看他的依赖dll文件下载对应文件 放到当前文件夹下,运行成功.