案例4 自动化用例收集、重命名、生成
案例需求
假设你们有一套基于qtaf的多人合作测试框架,已经积累了很多测试用例,但是随着用例的增多,发现一些问题:
- 需要统计下每个模块、每个人的用例数量
- 最开始的用例优先级规划较为混乱,需要重新规划,需要你把当前所有用例整理出来(Excel或CSV)
- 有些用例脚本文件名和其中的测试类名不统一
- 有些尚未实现功能的用例脚步需要在测试框架中标记出来
- 有些用例文件名带✅/❌等执行状态,需要清理掉
- 用例生成:在功能测试结束后,需要根据Excel中的功能用例,编写规范格式的自动化用例
已知,所有用例都在测试框架的testcases目录下,目录结构如下:
testcases/
模块1
__init__.py
子模块1-1
__init__.py
aaaa.py # 用例脚本
bbbb.py # 用例脚本
子子模块1-1-1
__init__.py
cccc❌.py # 用例脚本
dddd✅.py # 用例脚本
模块2
....
已知一个脚本仅一个测试用例,格式参考如下
# 文件名demo.py
from testbase import TestCase
class DemoTestCase(TestCase): # 用例名(一个类一个用例)
"""基于qtaf的用例示例""" # 用例标题
desc = '' # 用例描述(可能没有该属性)
owner = 'superhin' # 归属人
priority = TestCase.EnumPriority.Low # 优先级
status = TestCase.EnumStatus.Ready # 用例状态
tags = "demo", "contract-manage" # 用例标签(可能没有该属性, 可能是str, tuple或set类型)
timeout = 1 # 超时时间
def pre_test(self): # setup步骤(可能没有)
""" # 预置条件描述(可能没有)
1. 预置条件1
1. 预置条件2
"""
# ..具体步骤实现
def run_test(self): # 固定用例运行方法
""" # 测试步骤及期望结果描述(可能没有)
测试步骤:
1. 测试步骤1
2. 测试步骤2
3. 测试步骤3
期望结果
2. 期望结果2
3. 期望结果3
"""
self.start_step('测试步骤1') # 每个步骤的开始(可能没有)
# ...步骤具体实现
self.start_step('测试步骤2')
# ...步骤具体实现
self.start_step('测试步骤3')
# ...步骤具体实现
if __name__ == '__main__':
DemoTestCase().debug_run()
请实现以下功能
- 编辑一个脚本,统计出每个模块用例数,每个人的用例数量。
- 编写一个脚本,批量移除脚本后的✅或❌,执行状态标记。
- 如果测试用例脚本中类名与脚本名不一致,修改代码类名与脚本名一致,由脚本名(蛇型命名)转为类命名(大驼峰法)
- 编写一个脚本,如果当前脚本状态为实现中(TestCase.EnumStatus.Implement),且文件名不以
_implement.py
结尾,则将脚本文件名xxx.py
改为xxx_implement.py
- 根据excel用例,在某个子模块下指定目录,下批量生成待实现(status =TestCase.EnumStatus.Implement)的测试用例脚本(标准格式参考上面的demo.py)
- 编写一个脚本,导出所有的用例成Excel或CSV, 包括 '用例名称(测试类名), '标题'(测试类描述), '描述'(desc属性), '标签'(tags), '状态' (status), '优先级' (priority), '归属人' (owner), '模块' (testcases下一级子目录), '子模块' (模块下一级子目录), '路径'(相对于testcases的脚本路径或模块路径) , '步骤' (步骤描述)等。
用例ID | 用例标题 | 目录 | 优先级 | 预置条件 | 测试步骤 | 期望结果 | 归属人 | 创建时间 |
---|---|---|---|---|---|---|---|---|
1000023 | 基于qtaf的用例示例 | sub_dir1/demo | 低 | 1.预置条件1\n2.预置条件2 | 1.测试步骤1\n2.测试步骤2\n3.测试步骤3 | 2.期望结果2\n3.期望结果3 | 临渊 | 2022.12.12 12:00:00 |
提示
练习重点
文件及目录相关操作:
open()
# 读写.py(纯文本)文件os.walk()
: 参考 # 目录遍历os.rename()
: 参考 # 重命名文件os.makedirs()
: 参考 # 生成目录及子目录os.path
: 参考 # 文件路径相关]os.path.dirname()
# 上级路径os.path.basename()
# 文件名os.path.join()
# 路径连接os.path.exists()
# 是否存在os.path.isfile()
# 是否文件os.path.isdir()
# 是否目录
sort()
及itertools.groupby()
: 参考 # 分组统计(按模块分组/按用例归属人分组/按优先级分组)- csv或excel读写
难点
- 目录遍历
os.walk()
的使用及路径组装 - 生成用例时代码模板的组装,可以逐个部分(步骤等)组装,也可以使用Jinja2模板引擎(支持{% for ...%}循环)
- 代码洞察:从代码中提取类名、类注释(docstring)、步骤描述等
- 正则匹配
提示
使用inspect
代码洞察的实现方法参考
已知: 1. 用例目录testcases下所有的模块、子模块、子目录等都以包的形式存在(即目录下都有__init__.py),可以按路径导入模块
2. 所有模块、子模块、子目录名称都是合法的模块名(仅包含字母数字下划线)
os.walk()
时,组装出脚本相对于testcases的完整路径 (os.path.join()
路径组装)- 根据脚本
testcases/module1/sub_module1_1/demo.py
得到模块导入路径testcases.module1.sub_module1_1.demo
(字符串替换) - 导入模块,得到模块对象
import testcases.module1.sub_module1_1.demo as test_module # 给个别名方便使用
或使用Python内置库importlib
import importlib
test_module = importlib.import_module('testcases.module1.sub_module1_1.demo')
- 使用Python内置库
inspect
获取模块中所有的类名
import importlib
import inspect
# 模块对象
test_module = importlib.import_module('testcases.module1.sub_module1_1.demo')
classes = [] # 新建一个列表,保存改模块中的所有类名
cls_members = inspect.getmembers(test_module, inspect.isclass) # test_module是导入的模块对象
for (cls_name, cls) in cls_members: # cls_name为类名,cls为类对象,包含导入的类
classes.append(cls) # todo 过滤导入的类
test_class = classes[0] # 这里取第一个类名,不严谨
- 根据类对象获取类名、类注释、类属性
test_class_doc = test_class.__doc__ # 获取类注释(即用例标题)
status = getattr(test_class, 'status') # 获取类status属性(即用例状态,必填属性也可以直接用test_class.status获取)
priority = getattr(test_class, 'priority') # 用例优先级
owner = getattr(test_class, 'owner') # 用例归属人
desc = getattr(test_class, 'desc', '') # 用例描述,获取不到时,设置默认值为空字符串
# ...
- 获取测试方法对象注释、代码等
test_method = getattr(test_class, 'run_test') # 获取测试方法对象(已知,类中的测试方法名为固定的run_test方法)
test_method_doc = test_method.__doc__ # 测试方法注释(步骤描述、预期结果描述等,可能为空)
test_method_code = inspect.getsource(test_method) # 得到方法源代码,测试方法注释中步骤描述为空时,考虑从源代码通过正则匹配self.start_step()中的内容,得到所有步骤描述
# 判断及parse_steps