Skip to content
📔 阅读量:

标签化管理

标签说明

根据现有业务需要,用例需要添加的标签有:

  • 脚本 ID :自动化用例脚本/函数 ID
  • *PMS用例ID :PMS 上对应的用例 ID(用例库 ID)
  • 用例级别 :对应 PMS 上用例级别,分别用 L1、L2、L3、L4 表示
  • 用例类型 :FUNC(功能)、PERF(性能)、STR(压力)、SEC(安全)、CTS(兼容性)、API(接口)、BASELINE(基线-预留)
  • *设备类型 :PPL(依赖外设的用例)、COL(依赖主控机的用例)
  • 一二级bug自动化 :BUG(由 Bug 转的用例)
  • *上线对象 :CICD,表示上线到 CICD 流水线的用例
  • 跳过原因 :skip-XXX,用于控制用例是否执行
  • 确认修复 :fixed-XXX,用于标记用例的修复状态
  • 废弃用例 :removed-已废弃,用于标记已经废弃的用例,此用例标签不会被添加,也不会被执行

示例:

脚本ID*PMS用例ID用例级别用例类型*设备类型一二级bug自动化*上线对象跳过原因确认修复废弃用例...
679537679537L1FUNCPPLBUGCICDskip-XXXfixed-XXXremoved-已废弃...

操作步骤

  1. 在子项目目录下新建 csv 文件,用于保存用例标签,以 用例脚本的 py 文件去掉首字符串 "test_" ,去掉用例序号后的字符串,取中间的名称作为 csv 文件的文件名 。

    例如: 相册的用例文件为 test_album_xxx.py,xxx 表示用例的ID(也可以是自定义的数字代表用例序号),此时 csv 文件名就应为 album.csv

    对于用例规模比较大的应用,比如文件管理器,建议分模块,每个模块建立一个 csv 文件,所有 csv 文件建议放在一个 tags 目录下。

    是否分模块维护 csv 取决于应用的用例复杂度,同时我们应该充分考虑后期的可维护性,csv 文件太多了也是一个很糟糕的事情。

  2. 第一列为脚本 ID,从第二列开始及之后的列,每一列都是一个用例标签;后续需要新增用例标签,可以直接在 csv 文件里面添加对应的列即可;用例标签可以无序。

跳过用例

传统跳过用例的方式是在用例脚本里面给用例添加装饰器(@pytest.mark.skip),解除跳过时将装饰器代码删掉,这种方式需要修改用例代码,而通过 csv 文件来管理跳过用例则会方便很多;

将跳过用例操作也整合进入用例标签,在 csv 文件中新增一列为“跳过原因”;

1. 固定跳过

示例:

脚本ID...(各种用例标签)跳过原因
679537...skip-受到某新需求影响
  • 如果应用受到新需求影响需要跳过,则在此列备注具体的跳过原因。跳过的原因统一标签开头为 “skip-XXX”;
  • 用例执行时判断 csv 文件里面跳过原因列是否存在跳过标签,存在跳过标签则用例也不会被执行,最终的用例状态会被标签为 SKIPED

2. 条件判断跳过

示例:

脚本ID...(各种用例标签)跳过原因
001...skipif_platform-aarch64&mips64
  • 某些用例会因为不同的环境判断用例是否执行,常见的场景为在不同架构上判断是否执行,跳过的原因标签为 “skipif_platform-” 加架构名,多个架构之间使用 “&” 拼接;
  • 以上例子为用例执行时,判断当前架构是否为 arrch64 或者 mips64,若是,则跳过用例不执行,若否则执行用例;

在项目目录路径下存在文件 setting/skipif.py,所有条件判断跳过的函数写在此文件中。

python
#!/usr/bin/env python3
# _*_ coding:utf-8 _*_
# SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
# SPDX-License-Identifier: GPL-2.0-only

"""
此配置文件用于标签化管理方案实现条件判断跳过的配置项;
在CSV文件中“跳过原因”列填入函数名和参数,即可实现条件判断跳过;
比如:
在CSV文件中“跳过原因”列填入:
    skipif_platform-aarch64
    函数名称为:skipif_platform,比如是此文件中定义了的函数;
    参数为:aarch64,多个参数用 & 符号连接;
    函数名与参数之间用 - (中横线)连接;
"""
import os

from setting.globalconfig import GlobalConfig


def skipif_platform(args: str):
    """平台跳过
    skipif_platform-aarch64
    """
    _skip_key = args.split("&")
    for key in _skip_key:
        if GlobalConfig.SYS_ARCH == key:
            return True
    return False


def skipif_not_platform(args: str):
    """平台不跳过
    skipif_not_platform-aarch64
    """
    return not skipif_platform(args)


def skipif_xdg_type(args: str):
    """skipif wayland or x11
    skipif_xdg_type-wayland
    """
    _skip_key = args.split("&")
    for key in _skip_key:
        if GlobalConfig.DISPLAY_SERVER == key:
            return True
    return False


def skipif_cpu_name(args: str):
    """skipif cpu name
    使用 sudo dmidecode -s system-product-name 查看机器的cpu型号
    剔除中横线和&符号,比如:KLVV-W5821,标签记录为 KLVVW5821
    skipif_cpu_name-KLVVW5821
    """
    _skip_key = args.split("&")
    for key in _skip_key:
        if (
                os.popen(
                    f"echo '{GlobalConfig.PASSWORD}'| "
                    "sudo -S dmidecode -s system-product-name | awk '{print $NF}'"
                )
                        .read()
                        .split("\n")[0]
                        .replace("-", "")
                        .replace("&", "")
                == key
        ):
            return True
    return False


def skipif_not_cpu_name(args: str):
    """skipif not cpu name
    使用 sudo dmidecode -s system-product-name 查看机器的cpu型号
    剔除中横线和&符号,比如:KLVV-W5821,标签记录为 KLVVW5821
    skipif_not_cpu_name-KLVVW5821
    """
    return not skipif_cpu_name(args)


def skipif_os_version(args: str):
    """
    系统版本跳过
    skipif_os_version-1060
    """
    _skip_key = args.split("&")
    for key in _skip_key:
        if key == GlobalConfig.version_cfg.get("MinorVersion"):
            return True
    return False


def skipif_not_os_version(args: str):
    """
    系统版本不跳过
    skipif_not_os_version-1060
    """
    return not skipif_os_version(args)


if __name__ == '__main__':
    a = os.popen(
        f"echo '{GlobalConfig.PASSWORD}'| "
        "sudo -S dmidecode -s system-product-name | awk '{print $NF}'"
    ).read().split("\n")[0]
    print(a)

方法编写规范:

  • 方法名必须以 skipif 开头;
  • 方法必须有返回结果并且为布尔值(True 代表跳过,False 代表不跳过);
  • 方法只能有一个入参;

csv 文件跳过原因一栏中填写为 “{函数名}-{参数}”,例如:skipif_platform-aarch64;在用例收集阶段会以第一个 “-” 进行分割,截取的左侧字符串作为函数名,在 skipif.py 文件中查找是否有同名函数,并将截取的右侧作为参数传递给该函数,通过获取该函数返回的布尔值,返回 True,则用例不执行,返回 False,则执行该用例。

重要

  • 若函数需要多个参数,可自定义多个参数之间的连接符,连接符号不可使用下划线和逗号,推荐统一使用 & 符号;

  • 若需要多个 skipif 条件判断组合,使用 && 符号将两个方法分开,比如:skipif_platform-aarch64&&skipif_xdg_type-wayland

确认修复

针对于某些用例修复后,但不能立即删除跳过原因(skip-XXX)的用例,新增一列标签名为 “确认修复”,作为标记该用例是否已经修复,固定填入字段为 “fixed-已修复”。这样这条用例即使同时标记了 skip-XXX 也会正常执行。

示例:

用例ID...(各种用例标签)跳过原因确认修复
679537...skip-受到某新需求影响fixed-已修复

【同时标记了skipfixed,但仍然想要跳过用例】

当 “跳过原因” 和 “确认修复” 中同时填入后,命令行传递参数 --ifixed yes,则代码不会执行该条用例。

shell
python3 manage.py run --ifixed yes

看到这里有些同学可能要问了,我想恢复跳过执行,直接把 skip-XXX 这一列标签删掉不就好了,还搞什么确认修复干啥?

这里给各位看官稍微解释一下:

(以下流水线指的是每日构建的流水线,跑 AT 的全量用例)

首先,流水线上跑的是 AT 历史 Tag,跳过用例的标签(skip-XXX) 是在最新的代码上提交的,我们采用最新的 csv 文件覆盖历史 csv 文件的设计来实现了对历史 Tag 上用例的跳过;

然后,在日常跳过用例的过程中,同时也在修复一些用例,修复后的这些用例在本地调试的时候我们不希望继续跳过,但是此时,修复的这些用例可能还不稳定,不适合马上放到流水线去跑,也就是说流水线上我们是希望他继续跳过的,因此,咱不能直接把 skip-XXX 干掉;

这里就矛盾了,一个需求是想修复了立马解除跳过,另一个需求又不想修复了立马解除跳过,怎么办呢?

我们使用“确认修复”来标记这条用例已经修复了,这样你本地调试用例的时候这条已修复的用例是会执行的,同时在流水线上将 --ifixed yes 参数加上,那么流水线上执行时这条用例仍然是跳过的状态,后续你打 Tag 的时候,把 “跳过原因” 和 “确认修复” 中的标签全部删掉就可以了。

这就是“确认修复”这个标签的背景,需要各位看官稍微品一品。

废弃用例

针对某些用例,由于需求变更,环境影响或评估不再适用于自动化测试时,用例需要废弃,则新增一列标签名为 “废弃用例”,该列存在 “removed-{废弃原因}”,则用例不会执行。

用例ID...(各种用例标签)跳过原因确认修复废弃用例
679537...skip-受到某新需求影响fixed-已修复removed-已废弃

设计思路

上面介绍 Pytest 框架提供的标签功能 mark,使用时需要为每一个用例添加标签装饰器,则操作复杂,可维护性差,其根本问题就是标签分散在每一条用例的装饰器上,难以集中维护;于是乎将所有标签使用 csv 文件进行集中管理,并通过 Pytest 的钩子函数,读取 csv 文件,动态添加标签到用例中。

CSV文件格式

此配置文件需要维护大量的标签数据,且要方便能使用 Excel 打开进行编辑查看,更重要的是我们不想引入三方依赖,CSV 文件几乎是唯一能满足所有的要求的文件格式。