目录
一、runner简介
官方文档的说明是:
A basic test runner implementation that outputs results to a stream.
一个基本的runner组件是用来将结果输出到数据流。
根据它的解释,runner的最终作用是得到一个result对象,即结果,并将其反馈给用户。在上一章中,我们了解到runner的本质是实例化的TextTestRunner类,我们最终调用该实例的run(test)方法执行测试并得到结果,本章我们来分析该类的源码来了解这一过程如何实现。
二、TextTestRunner源码解析
TextTestRunner的位置在runner.py文件中
1. 类的实例化
展示__init__方法的源码:
class TextTestRunner(object):
resultclass = TextTestResult # 为resultclass属性赋初始值
def __init__(self, stream=None, descriptions=True, verbosity=1,
failfast=False, buffer=False, resultclass=None, warnings=None,
*, tb_locals=False):
if stream is None:
stream = sys.stderr
self.stream = _WritelnDecorator(stream)
self.descriptions = descriptions
self.verbosity = verbosity
self.failfast = failfast
self.buffer = buffer
self.tb_locals = tb_locals
self.warnings = warnings
if resultclass is not None: # 如果实例化时指定了该参数,则覆盖它的初始值
self.resultclass = resultclass
都是简单的属性赋值,各属性含义可查看笔者手译的unittest.TextTestRunner部分。这里需要注意self.resultclass属性,代码中在__init__()方法之外,为该属性赋了一个默认值。如果用户在实例化时的参数中给定了该参数,则会覆盖这个值。
2. run方法
源码:
def _makeResult(self):
# 创建result对象
return self.resultclass(self.stream, self.descriptions, self.verbosity)
def run(self, test):
"Run the given test case or test suite."
result = self._makeResult() # 创建result对象
registerResult(result) # 为control-c操作绑定这个result对象
result.failfast = self.failfast # 是否遇到失败或错误时就停止测试
result.buffer = self.buffer
result.tb_locals = self.tb_locals
with warnings.catch_warnings():
if self.warnings:
# warning过滤器
warnings.simplefilter(self.warnings)
if self.warnings in ['default', 'always']:
warnings.filterwarnings('module',
category=DeprecationWarning,
message=r'Please use assert\w+ instead.')
startTime = time.time()
startTestRun = getattr(result, 'startTestRun', None)
if startTestRun is not None:
startTestRun() # test运行之前被调用的方法
try:
test(result) # 这里的test是TestSuite类实例,调用它时等用于调用它的run方法
finally:
stopTestRun = getattr(result, 'stopTestRun', None)
if stopTestRun is not None:
stopTestRun() # test完全运行完之后被调用的方法
stopTime = time.time()
timeTaken = stopTime - startTime # 计算测试耗时
result.printErrors()
if hasattr(result, 'separator2'):
self.stream.writeln(result.separator2)
run = result.testsRun # 测试用例的数量
self.stream.writeln("Ran %d test%s in %.3fs" %
(run, run != 1 and "s" or "", timeTaken)) # 打印运行的测试总数
self.stream.writeln() #打印空行
expectedFails = unexpectedSuccesses = skipped = 0
try:
results = map(len, (result.expectedFailures,
result.unexpectedSuccesses,
result.skipped)) # 统计括号中3种情况的次数
except AttributeError:
pass
else:
expectedFails, unexpectedSuccesses, skipped = results
infos = []
if not result.wasSuccessful():
# 如果测试存在失败或错误,在结果信息中添加错误或失败的数量
# 如果测试成功,显示"OK"
self.stream.write("FAILED")
failed, errored = len(result.failures), len(result.errors)
if failed:
infos.append("failures=%d" % failed)
if errored:
infos.append("errors=%d" % errored)
else:
self.stream.write("OK")
# 统计跳过、预期的失败及非预期的成功的数量
if skipped:
infos.append("skipped=%d" % skipped)
if expectedFails:
infos.append("expected failures=%d" % expectedFails)
if unexpectedSuccesses:
infos.append("unexpected successes=%d" % unexpectedSuccesses)
if infos:
self.stream.writeln(" (%s)" % (", ".join(infos),))
else:
self.stream.write("\n")
return result
在run方法中,先用self._makeResult方法创建result对象。在with warning.catch_warnings代码块中执行测试可以测试用例执行过程中的警告进行过滤(因为unittest中有很多废弃的曾用名,可能会引发警告,也可以自定义过滤规则,详情可查看官方文档warning模块)。执行测试的关键代码是test(result),这里的test是一个TestSuite类的实例对象,这里以函数的方式调用等同于执行test.run(result)。
剩余代码是用于打印信息,就只在源码中作标注。
三、总结
runner.py模块的内容相当简短,测试的底层实现并没有展现在该模块中,但是result对象是在它这里创建的,之后测试结果都将存放于该对象中,该类的实例是用于存放并打印结果。这里子类中重写的各中方法的作用是为了在发生相应的情况(例如:error、failure、skip等)时打印相关信息,如下图中的标注处。
下期我们会具体介绍result对象,因为他是执行test.run(result)方法时必须要用到的参数。