案例4 自动化用例收集、重命名、生成

发布时间 2023-06-05 16:36:58作者: 韩志超

案例需求

假设你们有一套基于qtaf的多人合作测试框架,已经积累了很多测试用例,但是随着用例的增多,发现一些问题:

  1. 需要统计下每个模块、每个人的用例数量
  2. 最开始的用例优先级规划较为混乱,需要重新规划,需要你把当前所有用例整理出来(Excel或CSV)
  3. 有些用例脚本文件名和其中的测试类名不统一
  4. 有些尚未实现功能的用例脚步需要在测试框架中标记出来
  5. 有些用例文件名带✅/❌等执行状态,需要清理掉
  6. 用例生成:在功能测试结束后,需要根据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.assert_equal(result, '')
        

        self.start_step('查询counter合约')
        # ...步骤具体实现
        self.log_info(result)
        self.assert_equal(result, '')


if __name__ == '__main__':
    DemoTestCase().debug_run()

请实现以下功能

  1. 编辑一个脚本,统计出每个模块用例数,每个人的用例数量。
  2. 编写一个脚本,批量移除脚本后的✅或❌,执行状态标记。
  3. 编写一个脚本,导出所有的用例成Excel或CSV, 包括 '用例名称(测试类名), '标题'(测试类描述), '描述'(desc属性), '标签'(tags), '状态' (status), '优先级' (priority), '归属人' (owner), '模块' (testcases下一级子目录), '子模块' (模块下一级子目录), '路径'(相对于testcases的脚本路径或模块路径) , '步骤' (步骤描述)等。
  4. 编写一个脚本,如果当前脚本状态为实现中(TestCase.EnumStatus.Implement),且文件名不以_implement.py结尾,则将脚本文件名xxx.py改为xxx_implement.py
  5. 根据excel用例,在某个子模块下指定目录,下批量生成待实现(status =TestCase.EnumStatus.Implement)的测试用例脚本(标准格式参考上面的demo.py)

| 用例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 |

提示

练习重点
文件及目录相关操作:

难点

  • 目录遍历os.walk()的使用及路径组装
  • 生成用例时代码模板的组装,可以逐个部分(步骤等)组装,也可以使用Jinja2模板引擎(支持{% for ...%}循环)
  • 代码洞察:从代码中提取类名、类注释(docstring)、步骤描述等
  • 正则匹配

提示
使用inspect代码洞察的实现方法参考

已知:1. 用例目录testcases下所有的模块、子模块、子目录等都以包的形式存在(即目录下都有__init__.py),可以按路径导入模块

  1. 所有模块、子模块、子目录名称都是合法的模块名(仅包含字母数字下划线)

  2. os.walk()时,组装出脚本相对于testcases的完整路径 (os.path.join() 路径组装)

  3. 根据脚本testcases/module1/sub_module1_1/demo.py 得到模块导入路径testcases.module1.sub_module1_1.demo (字符串替换)

  4. 导入模块,得到模块对象

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')
  1. 使用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 (name, _) in cls_members:
    classes.append(name)

test_class_nane = classes[0]  # 由于我们的模块中只有一个测试类,所以只去第一个,得到类名
test_class = getattr(test_module, test_class_nane)  # 根据类名从模块对象中获取类对象(类对象类似于模块对象的一个属性,属性名及类名)
  1. 根据类对象获取类名、类注释、类属性
test_cass_doc = test_class.__doc__.strip()  # 获取类注释并去除左右空格(即用例标题)
status = getattr(test_class, 'status') # 获取类status属性(即用例状态,必填属性也可以直接用test_class.status获取)
priority = getattr(test_class, 'priority')  # 用例优先级
owner = getattr(test_class, 'owner')   # 用例归属人
desc = getattr(test_class, 'desc', '')   # 用例描述,获取不到时,设置默认值为空字符串
# ...

  1. 获取测试方法对象注释、代码等
test_method = getattr(test_class, 'run_test')  # 获取测试方法对象(已知,类中的测试方法名为固定的run_test方法)
test_method_doc = test_method.__doc__.strip()  # 测试方法注释(步骤描述、预期结果描述等,可能为空)
test_method_code = inspect.getsource(test_method)  # 得到方法源代码,测试方法注释中步骤描述为空时,考虑从源代码通过正则匹配self.start_step()中的内容,得到所有步骤描述
# 判断及parse_steps