前言
Unittest是Python自带的自动化测试框架,提供了基本的控制结构和模型概念。 由于Unittest功能较为基础,因此在实际框架实战中往往需要对其功能进行扩充。 比如: 1. 生成HTML报告 2. 多线程并发(并且报告不混乱) 3. 自动重试出错用例 4. 为用例提供tags标签和level等级 等,往往需要我们对Unittest框架进行二次开发和扩展,由于Unittest框架清晰的API,扩展和定制也非常方便。 > Unittest各个模块的API课参考:Unittest官方文档翻译
unittest.TestResult类简介
TestResult类一般在TestRunner类中实例化,并穿梭于每个执行的测试套件和测试用例中用于记录结果。 TestResult对象常用的属性有: - stream:用于输出测试信息的IO流,一般是终端或文本文件。 - descriptions:描述信息。 - verbosity:显示详细级别。 - buffer:默认为False,用例中的print信息立即输出,buffer为True时将用例中的print信息统一收集并集中输出。 - tb_locals: 在报错异常信息中显示用例中的局部变量(即tackback_locals)。 - failfast:默认为False, 用例失败后继续运行,为True时,任何一条用例失败时立即停止。 - _mirrorOutput:是否重定向输出流状态标志
unittest.TestResult类提供了以下几种方法: - 运行开始/结束 - startTestRun: 执行开始时调用,参考unittest.TextTestRunner中的run方法。 - stopTestRun: 所有用例执行结束后调用 - startTest:单个用例执行开始时调用,参考unittest.TestCase类中的run方法。 - stopTest:单个用例执行结束后调用。 - 注册用例结果 - addSuccess:单个用例执行成功时调用,来注册结果,默认为空。 - addFailure:用例失败时在stopTest前调用。 - addError:用例异常时在stopTest前调用。 - addSkip:用例跳过时在stopTest前调用。 - addExpectedFailure:用例期望失败时在stopTest前调用。 - addUnexpectedSuccess:用例非期望成功时在stopTest前调用。 - 重定向和恢复系统输出流 - _setupStdout:重定向输出流,默认self.buffer为True时生效 - _restoreStdout:恢复系统输出流
用例失败Failure和用例异常Error的区别: 用例中的断言错误(期望结果和实际结果不一致)引发的AssertionError异常被视为用例失败,其他异常视为用例异常Error。
ExpectedFailure和UnexpectedSuccess: 期望失败指我们期望这条用例执行失败,即用例失败了才是符合预期的,而没有失败即UnexpectedSuccess,这是一种反向用例,如果失败了其实是通过,而成功了反而是失败。
TestResult类定制目标
- 在result中增加整体的运行开始时间start_at,持续时间duration和每条用例的开始时间,执行时间
- 存储用例中的print信息及异常信息,以供生成HTML使用
- 为已知异常提供失败原因
- 提供结构化和可序列化的summary和详情数据
- 探测每个用例code,以为审视用例代码提供方便
- 增加运行平台platform信息和运行时的环境变量信息
- 将print信息改为使用log记录,增加日志时间,方便追溯。
- 提供用例的更多的信息,如tags,level, id, 描述等信息。
实现步骤
测试结果summary格式规划
测试结果result类提供一个summary属性,格式如下(参考了httprunner的summary格式):
name: result结果名称
success: 整个测试结果是否成功
stat: # 结果统计信息
testsRun: 总运行数
successes: 成功数
failures: 失败数
errors: 异常数
skipped: 跳过的用例数
expectedFailures: 期望失败数
unexpectedSuccesses: 非期望成功数
time:
start_at: 整个测试开始时间(时间戳)
end_at: 增高测试结束时间(时间戳)
duration: 整个测试执行耗时(秒)
platform:
platform: 执行平台信息
system: 执行操作系统信息
python_version: Python版本信息
# env: 环境变量信息(信息中可能包含账号等敏感信息)
details: # 用例结果详情
- ... # 单个用例结果
单个用例结果格式规划
# 执行前可获取的信息
name: 用例名称或用例方法名
id: 用例完整路径(模块-类-用例方法名)
decritpion: 用例描述(用例方法docstring第一行)
doc: 用例方法完整docstring
module_name: 用例模块名
class_name: 用例类名
class_id: 用例类路径(模块-类)
class_doc: 用例类docstring描述
tags: 用例标签
level: 用例等级
code: 用例代码
# 执行后可获取的信息
time:
start_at: 用例执行开始时间
end_at: 用例结束时间
duration: 用例执行持续时间
status: 用例执行状态success/fail/error/skipped/xfail/xpass
output: 用例中的print输出信息
exc_info: 用例异常追溯信息
reason: 用例跳过,失败,出错的原因
读者也可以根据自己的需要添加其他额外的信息,如timeout用例超时时间配置,order用例执行顺序,images用例中的截图,link用例中的链接等信息。
以上的tags和level通过在用例方法的docstring中注入”tag:smoke”及”level:1”等样式为用例添加标签和等级,然后配合定制的loader用例加载器去收集指定标签或等级的用例,下节会详细讲解。
用例tags和level的实现
每个框架都会有自己约定格式,这里我采用在docstring注入特定格式描述的方式为用例添加tags和level信息,用例格式如下。
import unittest
class TestDemo(unittest.TestCase):
def test_a(self):
"""测试a
tag:smoke
tag:demo
level:1
"""
print('测试a')
对于每个用例对象,可以使用test._testMethodDoc来获取其完整的docstring字符串,然后通过正则匹配来匹配出用例的tags列表和level等级,实现方法如下。
import re
TAG_PARTTEN = 'tag:(\w+)'
LEVEL_PARTTEN = 'level:(\d+)'
def get_case_tags(case: unittest.TestCase) -> list:
"""从用例方法的docstring中匹配出指定格式的tags"""
case_tags = None
case_doc = case._testMethodDoc
if case_doc and 'tag' in case_doc:
pattern = re.compile(TAG_PARTTEN)
case_tags = re.findall(pattern, case_doc)
return case_tags
def get_case_level(case: unittest.TestCase):
"""从用例方法的docstring中匹配出指定格式的level"""
case_doc = case._testMethodDoc
case_level = None # todo 默认level
if case_doc:
pattern = re.compile(LEVEL_PARTTEN)
levels = re.findall(pattern, case_doc)
if levels:
case_level = levels[0]
try:
case_level = int(case_level)
except:
raise ValueError(f'用例中level设置:{
case_level} 应为整数格式')
return case_level
根据测试方法对象获取用例代码
def inspect_code(test):
test_method = getattr(test.__class__, test._testMethodName)
try:
code = inspect.getsource(test_method)
except Exception as ex:
log.exception(ex)
code = ''
return code
单个用例结果类的实现
由于单个用例结果信息较多,我们可以在整个TestResult类中使用一个嵌套字典格式存储,也可以单独定制一个用例结果类,参考如下。
class TestCaseResult(object):
"""用例测试结果"""
def __init__(self, test: unittest.case.TestCase, name=None):
self.test = test # 测试用例对象
self.name = name or test._testMethodName # 支持传入用例别名,unittest.TestCase自带属性方法
self.id = test.id() # 用例完整路径,unittest.TestCase自带方法
self.description = test.shortDescription() # 用例简要描述,unittest.TestCase自带方法
self.doc = test._testMethodDoc # 用例docstring,,unittest.TestCase自带属性方法
self.module_name = test.__module__ # 用例所在模块名
self.class_name = test.__class__.__name__ # 用例所在类名
self.class_id = f'{
test.__module__}.{
test.__class__.__name__}' # 用例所在类完整路径
self.class_doc = test.__class__.__doc__ # 用例所在类docstring描述
self.tags = get_case_tags(test) # 获取用例tags
self.level = get_case_level(test) # 获取用例level等级
self.code = inspect_code(test) # 获取用例源代码
# 用例执后更新的信息
self.start_at = None # 用例开始时间
self.end_at = None # 用例结


被折叠的 条评论
为什么被折叠?



