开发文档 - KPTest框架开放资料

Skip to content

# KPTest定制化用例开发指南

## 0. KPTest框架介绍

> 框架介绍

KPTest框架是专为鲲鹏生态打造的服务器自动化测试框架，基于Python3 构建的企业级测试架构。KPTest框架以高度解耦的层次化设计为核心，实现测试逻辑与测试数据分离、统一接口与底层实现分离，有效提升测试场景适配灵活性。框架采用开放性架构设计，支持系统、连接驱动及测试命令模块的快速横向扩展。通过API 驱动模式，提供标准化的产品操作接口，屏蔽软硬件差异，覆盖服务器产品各种测试场景，并兼容多类型的测试设备、系统和工具。

基于KPTestOpenFramework开放架构，测试团队可快速开发自动化用例，通过AW扩展满足定制化场景需求，无缝对接iTestSmart 智能测试平台，形成端到端测试闭环。该框架贯穿测试用例开发、调试、调度与执行全流程，为复杂服务器场景提供高效、可靠的一体化测试支持，构建起自动化质量保障体系，提升鲲鹏生态产品的测试效率与交付质量。

## 1. 快速开始

### 1.1 搭建本地开发环境(以PyCharm为例)

> Python版本

1. 推荐使用python版本：3.7.9 (64位) 下载链接：https://mirrors.huaweicloud.com/python/3.7.9/
2. 不支持32位python版本

> 获取项目

使用git从openUBMC仓库中下载项目

项目地址：https://gitcode.com/openUBMC/QA.git

点击Clone按钮，按照提示进行操作

> 项目结构

在PyCharm中打开QA项目，测试用例的开发在 Test_Code/KPTestOpenFramework 目录下进行。

KPTestOpenFramework中主要有extension、src两个部分，分别负责AW和用例开发、测试框架核心配置。

为进行开发，需要将KPTestOpenFramework文件夹设置为Sources Root

(右键单击KPTestOpenFramework → Mark Directory as -> Sources Root)

> 配置python解释器

> 安装框架核心代码

    1
    2
    3

    cd [KPTest的whl文件所在路径]
    pip install KPTestCase-x.x.x-py3-none-any.whl
    pip install KPTestLibrary-x.x.x-py3-none-any.whl

> 执行器配置

- 通过编辑Pycharm IDE中的Run/Debug配置，作为执行入口
- 设置Script path为`[KPTestOpenFramework地址]\src\bin\kprun.py`
- 设置Parameters为`-c main_config.xml`
- 设置Python解释器为上文中配置的解释器
- 设置Working directory为`[KPTestOpenFramework地址]\src\bin`
- 保存后通过点击运行或调试开始执行

其中：

- 可先执行`[KPTestOpenFramework地址]\src\bin\kprun.py`脚本，再在执行器配置中设置Parameters，方便配置
- `main_config.xml`为主配置文件，详情可见1.2节

### 1.2 license获取

在进行自动化测试之前，需要先获取license

在iTestSmart客户端的设置->license管理页面可以到获取本机ESN，将ESN发送给华为方支撑人员申请license

将申请到的license文件放置于src/bin/config目录下

与iTestSmart使用相同的license，如果已经申请过，直接使用即可

### 1.3 测试用例执行配置

框架配置文件位于`src/bin`目录下，主要包含主配置文件`main_config.xml`、测试床`test_bed_info.xml`、测试套`test_set_info.xml`

#### 主配置文件

> 概念

用于设定框架运行的各项参数，如: 运行日志等级&存放位置、测试床测试套文件位置等。

> 使用方法

在`src\bin`目录新建或修改主配置示例文件`main_config.xml`，主配置文件示例内容如下所示：

其中`test_set_file`、`test_bed_file`标签属性为测试床测试套文件所在位置

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24

    <?xml version="1.0" encoding="UTF-8" standalone="no" ?>
    <optdoc_type="MAIN_CFG"version="1">
       <!-- 指定本地日志的日志存放路径 -->
       <local_base_log_path>logs</local_base_log_path>
       <!-- 指定测试执行的存放路径 -->
       <local_execution_log_path>execution_logs</local_execution_log_path>

       <!-- 指定测试套文件的位置 -->
       <test_set_file>config/test_set_info.xml</test_set_file>
       <!-- 指定测试床文件的位置 -->
       <test_bed_file>config/test_bed_info.xml</test_bed_file>

       <!-- 指定测试实例ID -->
       <test_instance_id>124</test_instance_id>
       <!-- 指定执行参数 -->
       <execution_parameters>
            <!-- 发生错误后执行策略: 1 OR 0.
                1 - 如果发生错误，则停止进一步执行
                0 - 即使发生错误，也继续执行 -->
            <param name="stop_on_error" value="0"/>
            <!-- 指定屏幕上显示的信息的日志记录级别 -->
            <param name="logging_level" value="DEBUG"/>
        </execution_parameters>
    </opt>

#### 测试床

> 概念

用于指定运行自动化测试用例的被测设备，提供一些连接信息。测试床配置文件主要定义了所有设备的全局环境信息、定义服务器列表，此外还可以定义服务器系统和交换机等信息。

> 使用方法

本地搭建测试环境时，可从iTestSmart客户端导出测试床，并置于`src\bin\config`目录路径中，详情可见3.2节测试床配置文件导入

#### 测试套

> 概念

用于指定运行在被测设备上的用例脚本，可修改每个用例或用例集的一些参数配置。

> 使用方法

在`src\bin\config`目录新建或修改测试套示例文件`test_set_info.xml`，其中`test`标签中`location`为`TestCaseDemo.py`示例脚本位置，将在1.4节创建

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27

    <?xml version="1.0" encoding="UTF-8" standalone="no" ?>
    <opt doc_type="TC_CFG" version="2">
        <test_set name="TestSet">
            <test_set_parameters>
                <parameter name="duration" value="168H"/>
                <parameter name="parallel" value="True"/>
            </test_set_parameters>
            <identities>
            </identities>
            <!--
                测试用例配置：
                test标签中的测试用例名称为测试用例文件中的类名;
                identity标签中的name为测试用例的中文标签，id为测试用例文件中的类名
                location中写入的为测试用例模块路径，测试用例文件的相对路径，包含文件名(不含后缀)；
            -->
            <tests>
                <test type="case" name="TestCaseDemo">
                    <identities>
                        <identity name="用例测试" id="TestCaseDemo"/>
                    </identities>
                    <location>
                        extension/case/TestCaseDemo
                    </location>
                </test>
            </tests>
        </test_set>
    </opt>

### 1.4 测试脚本的执行&结果查看

> 示例测试脚本

在`extension/case`目录新建 `TestCaseDemo.py`，示例脚本代码如下所示：

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46

    #!/usr/bin/python
    # -*- coding: UTF-8 -*-

    from kpautos.engine.case import Case
    from kptest.config.enum import DeviceType, HostType, DeviceUtility

    class TestCaseDemo(Case):
        """
            Name:
                TestCaseDemo
            Label:
                测试用例demo
            PreCondition:

            TestStep:
                1) API查询BMC版本信息
            ExpectedResult:
                A) 查询得到正确的BMC版本信息
            Parameters:

            Author:
                XXXXXX
            Last Modified:
                xxxx-xx-xx
            Remark:

        """

        # 测试执行前配置等准备操作
        def pre_test_case(self):
            self.logger.info("Pre Test Case Start.... ")
            # get_device 实例化Device对象
            self.device_dut = self.resource.get_device(device_type=DeviceType.Server, utility=DeviceUtility.DUT)
            # get_api 实例化Api对象
            self.bmc_cli_api = self.device_dut.get_api(ns_name="BmcCli")

        # 测试步骤
        def procedure(self):
            self.log_step("Step1: API查询BMC版本信息")
            version_from_cli = self.bmc_cli_api.cli_query_device_version()
            self.logger.info(version_from_cli)

        # 测试结束后恢复环境
        def post_test_case(self):
            pass

> 执行测试脚本

完成以上所有配置环节后，执行配置好的`kprun`执行器(快捷键`shift+F10`)，即可本地执行测试脚本。

> 执行结果查看

执行完成后可通过两种方法查看结果和日志：

a) Pycharm运行/调试窗口

b) `main_config.xml`中`local_execution_log_path`标签指定了测试执行结果的存放相对或绝对路径，其中以时间戳命名的文件夹即为测试结果和日志目录

如果测试用例运行失败，可以在对应时间的文件夹下查看测试结果`result.html`，如下图示例：

## 2. 框架概念介绍

KPTest框架中存在几个重要概念，将面向华为服务器设备的自动化测试流程层级进行了定义与封装，其中包括Device、Host、Command、Api、Wrapper等。

> Device

Device是实体被测对象的抽象封装。

所有的业务都是在设备上开展的，目前Device的类型主要有管理板、服务器、存储、交换机等设备。

> Host

Host是被测设备中具体系统的抽象封装。

通过配置连接信息将直接操作的操作系统或测试相关辅助系统实例为对象，框架中实际具体下发操作依赖此实例化对象。

目前`HostType`包含:

> Command

Command模块是执行机（运行测试框架代码的机器）与被测设备建立连接、下发操作的封装。 通常基于第三方库实现执行机用不同协议登录、鉴权并连接被测设备，如：SSH、Telnet等。

> Api

API模块为测试用例或者为业务组件提供底层的执行单元，屏蔽了硬件软件环境的差异，是KPTestLibrary统一提供的对外接口。

> Wrapper

Wrapper模块提供了被测对象的最基础操作(如发送命令行字符串等)，是底层操作的封装；同时也提供了返回值的展示优化或解析结果。

KPTest框架各层级之间的关系如图所示，其中：

- 通过Device可以调用Host、Api对象，而Api通过绑定Host，限制能够执行当前Api的Host类型。
- Host绑定多种可执行Command类型，用于连接实际设备执行相关命令。
- Api作为开放接口，与底层Wrapper对应。Wrapper通过调用所绑定的Command通道来执行相关命令。

常见Device、Host、Command层级Type：

DeviceType | 说明 | HostType | 说明 | CommandType | 说明
--- | --- | --- | --- | --- | ---
Management | 管理板 | BMC |  | SSH |
Server | 服务器 | HMM |  | BMCSSH |
Storage | 存储 | HostOS |  | SERSSH |
Switch | 交换机 | GuestOS |  | TELNET | 普通telnet
|  | StorController |  | SERIALClI | 串口服务器
|  | Local |  | ETH_SWITCH | 网络交换机
|  | EthernetSwitch |  | FIBRE_SWITCH | 光纤交换机
|  | FibreSwitch |  | APC | 电源盒
|  | PowerBox |  | LOCAL_IPMISOL |
|  | HOAT |  | LOCAL_SNMP |
|  | Tesgine |  | LOCAL_IPMICLI |
|  | SwitchManager |  | HOSTOS_IPMICLI |
|  |  |  | SSH_UREST | 用于Linux上下发uRest命令
|  |  |  | RPC_UREST | 用于Windows上下发uRest命令
|  |  |  | PSCLI | 用于Windows连接——PowerShell
|  |  |  | RPC | 用于Windows连接——RPC

## 3. 编写测试用例

### 3.1 测试用例模板

#### 测试用例命名注释规范

在1.1节中，解压zip包获取项目模板代码，其中可自定义开发用例位于extension/case文件夹中，其注释模板如下所示：

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30

    # case_001.py // a
    from kpautos.engine.case import Case

    class Case001(Case): // b
        """
            Name:
                Case001 // c
            Label:
                <用例中文名称1>
            PreCondition:
                <前置条件>
            TestStep:
                1) <步骤1>，有结果A)
                2) <步骤2>，有结果B)
                3) <步骤3>，有结果C)
            ExpectedResult:
                A) <期望1>
                B) <期望2>
                C) <期望3>
            Parameters:
                1) name: repeat_times; value: 3; type: int; label: 测试重复次数; description: 该用例每次执行需要重复测试的次数
                2) name: command; value: uname; type: text; label: 执行命令; description: 该用例每次执行所需要执行的命令
            Author:
                 XXXXXX
            Last Modified:
                 xxxx-xx-xx
            Remark:

        """

> 测试用例命名规范和注意点

用例文件名称(a)建议采用小写加下划线命名法。用例类名(b)、注释用例名(c)保持一致，统一格式为：<公司名缩写><模块/部门/领域名><用例用途><编号>，采用大驼峰命名法，比如：`HwComputeCpuModel001`

注意：

1. 开发定制化用例需要继承基类Case。定制化用例中需要重写方法pre_test_case、procedure、post_test_case，分别负责“测试执行前配置等准备操作”、“执行测试步骤”、“测试执行完成后清理等操作”。测试人员需要通过调用KPTest框架所开放接口及其方法，指定测试操作和流程。
2. 在注释中填写用例相关信息。如上图中所示，包括Name、Label、PreCondition、TestStep、ExpectedResult、Parameters等。
3. 为了实现自动打包，“Parameters:” 部分需要严格按照 "i) name: xxx; value: xxx; type: xxx; label: xxx; description: xxx“ 的格式编写。

> 支持自定义目录结构

在`extension/case`测试用例目录下，如果希望为测试用例分类以自定义目录结构，比如创建了`fan`目录：

需要在`fan`目录中的__init__.py文件中添加自定义目录结构中文标签说明，例如：

    1
    2

    # 用例目录中文标签
    label = "风扇测试"

注意：如果创建了多级目录结构，每一级目录都需要___init__.py文件中并添加对应的目录中文标签说明。

> Parameters参数介绍

Parameters的每个参数可定义其name、value、type、label和description。

其中，对用例的Parameters参数类型type进行定义，决定了参数类型以及在iTestSmart客户端中的渲染方式。

参数的类型 “type” 可以是以下枚举值之一：int（整型）和text（文本类型）等。

Parameters参数的值可在导入测试用例后，在用例"设置检查项"中对参数的取值进行配置，如下图所示：

在用例中，可通过以下方式实现某个参数"param_name"的值获取，例如：

    1
    2
    3
    4
    5
    6

        ...
        self.param_dict = {each['name']: each['value'] for each in self.custom_param}
        # 获取参数的值
        param_value = self.param_dict.get("param_name")
        # repeat_times = self.param_dict.get("repeat_times")
        # command = self.param_dict.get("command")

#### 测试用例各环节介绍

定制化用例中需要重写方法pre_test_case、procedure、post_test_case，分别负责“测试执行前配置等准备操作”、“执行测试步骤”、“测试执行完成后清理等操作”。一般地：

1. `pre_test_case`部分可以完成Device、Host、Api等对象初始化。 self.resource在Case基类中根据测试床的信息完成初始化，调用`get_device`方法即可实现Device对象的实例化，具体可见3.3节初始化device对象部分。 Host、Api对象的实例化可分别通过Device对象调用`get_host`和`get_api`方法来实现，具体可见3.3节初始化api对象部分。
2. `procedure`部分可以完成测试步骤的执行。 包括调用Host方法、Api方法以及日志打印、结果处理等操作的实现。
3. `post_test_case`部分可以在测试执行完成后清理等操作。

### 3.2 测试床&测试套配置

在本地调试自定义开发用例，还需要配置测试床和测试套，分别用于指定运行自动化测试用例的被测设备、指定运行在被测设备上的用例脚本，具体概念介绍可参考1.3节。

#### 测试床配置文件导入

选择设备之后，通过iTestSmart生成测试床文件`test_bed_info.xml`导入`src\bin\config`目录下

#### 测试套配置文件修改

测试套，用于指定运行在被测设备上的用例脚本，可修改用例的一些参数配置。

对于自定义用例可修改如下图所示的测试用例信息部分中的test标签，分别修改其中`test`标签的name、id和location(用例路径)，比如本开发指南示例用例name/id为`Case001`，location为`extension\case\fan\Case001`。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    <?xml version="1.0" encoding="UTF-8" standalone="no" ?>
    <opt doc_type="TC_CFG" version="2">
        <test_set name="TestSet">
            <test_set_parameters>
                <parameter name="duration" value="168H"/>
                <parameter name="parallel" value="False"/>   <!-- 是否并行执行， True: 并行   False: 串行 -->
            </test_set_parameters>
            <!--
                测试用例配置：
                test标签中的测试用例名称为测试用例文件中的类名;
                identity标签中的name为测试用例的中文标签，id为测试用例文件中的类名
                location中写入的为测试用例模块路径，测试用例文件的相对路径，包含文件名(不含后缀)；
            -->
            <tests>
                <test type="case" name="Case001">
                    <identities><identity name="测试用例1" id="Case001"/></identities>
                    <location>
                        extension\case\fan\Case001
                    </location>
                </test>
            </tests>
        </test_set>
    </opt>

### 3.3 调用KPTest框架所开放接口

KPTest框架存在接口可供调用，详细见开放接口文档，主要步骤为：初始化device对象、初始化api对象、调用api方法

#### 初始化device对象

执行测试操作前，需要初始化设备device对象，一般在pre_test_case中进行对象的实例化，示例代码如下所示：

    1
    2
    3
    4

    def pre_test_case(self):
        self.logger.info("Pre Test Case Start.... ")
        # # get_device 实例化Device对象
        self.device_dut = self.resource.get_device(device_type=DeviceType.Server, utility=DeviceUtility.DUT)

self.resource在Case基类中根据测试床的信息完成初始化，调用get_device方法即可完成device对象初始化。

其中：

参数1为device_type(设备类型)，可表示管理板、服务器、存储、交换机等设备，可取值为DeviceType.Management、DeviceType.Server、DeviceType.Storage、DeviceType.Switch，详情可见

参数2为utility(用途)，可取值为DeviceUtility.DUT和DeviceUtility.DST，分别表示被测设备、测试辅助设备。

#### 调用os接口

当需要指定具体系统host类型，或者调用其方法时，需要先初始化host对象，一般在pre_test_case或者procedure中进行api对象的实例化，如下示例代码所示：

    1
    2
    3
    4
    5
    6
    7
    8

        ……
        # get_host 实例化Host对象，需要指定host_type
        self.os_host = self.device_dut.get_host(host_type=HostType.HostOS)

        # 根据接口文档，调用操作系统对应的方法接口，直接调用其方法，详见接口文档
        res = self.os_host.xxx(*params, **kwargs)
        # 根据接口文档，调用OS模块对应的方法接口，需要指定module，详见接口文档
        res = self.os_host.module.xxx(*params, **kwargs)

实例化Host对象后，可实现：1. 根据接口文档，调用host对应的方法接口；2. 用于指定后续api示例所绑定的host

#### 调用api接口

当需要调用api及其方法时，需要先初始化api对象，一般在pre_test_case或者procedure中进行api对象的实例化，如下示例代码所示：

    1
    2
    3
    4
    5
    6
    7
    8
    9

        ……
        # get_api 实例化Api对象
        self.bmc_cli_api = self.device_dut.get_api(ns_name="BmcCli")
        self.ipmi_api = self.device_dut.get_api(ns_name="Ipmi")

        # get_host 实例化Host对象，需要指定Api对象所对应host_type
        self.bmc_host = self.device_dut.get_host(host_type=HostType.BMC)
        # 调用自定义api
        api = self.device_dut.get_api("TestCli", dispatcher=self.bmc_host, api='kptest_extension_api')

所有框架开放api对象和自定义api对象需要通过device对象(本处示例为device_dut)，调用get_api方法来使用，get_api方法所需参数和用法如下所示：

    1
    2
    3
    4
    5
    6
    7

    def get_api(self, ns_name, api=None, dispatcher=None, **kwargs):
        """
        获取对外Api实例
        Args:
            ns_name (str): API实例名称
            api (str): 注册的api族，默认使用框架自身的kptest_api
            dispatcher  (Host): 获取到的API可指定绑定的host，不指定时使用默认dispatcher(正序匹配系统ns.dispatchertype中的第一个)

其中：

参数1为ns_name(API实例/模块名称)必填项，即表明所创建api对象为哪个API模块的实例;

参数2为api(注册的api族)非必填项，目前可取值为“kptest_api”和“kptest_extension_api”,分别表示KPTest框架开放api模块、自定义api模块。入参为None时默认使用“kptest_api”;

参数3为dispatcher(Host)非必填项：获取到的API可指定绑定的host。不指定时使用默认dispatcher，正序匹配规则：a.初始化时会把测试床中Host类型与该列表匹配，默认dispatcher为匹配到的第一个，如BMC；b.顺序匹配时Host中不存在，则进行搜索第二个，如HMM在Host中是否存在；c.get_api时可以指定使用对应的Host。

如果需要指定对应的host(dispatcher)，需要初始化对应的host对象(即图中self.bmc_host)。比如需要指定dispatcherType为HostType.HostOS，则需要通过device.get_host()初始化一个hostos对象，再在get_api时指定使用。注意：所指定dispatcher需要满足对应api子类的可绑定dispatcherType的要求，具体可见2.1节API子类介绍。

> 调用api方法

根据接口文档，包括api方法所属api可以调用api对象所对应的api方法，获取返回结果，以帮助执行测试流程，比如对返回结果进行处理、与期望结果对比等。

## 4. 定制化AW开发

目前，KPTest框架层级中可支持扩展的层级主要有Api、Wrapper等，下面将介绍如何实现定制化开发。

### 4.1 开发框架底层执行单元Api

> API：框架底层执行单元，统一对外接口

API为测试用例或者为业务组件提供底层的执行单元，屏蔽了硬件软件环境的差异，是统一提供的对外接口。

> 可定制化开发API所在目录层级：extension\api

API由三个部分组成：

a) API模块：不同种类功能的模块定义，是一个Python Package模块；

b) API子类：同类型功能的子功能划分封装，易于框架严谨维护，调用API方法时无感知，如：命令行功能的网络管理、用户管理就应划分为独立的子类封装。归属于对应API模块的package目录层级下。

c) API方法：实现具体功能统一对外封装的函数，函数处于对应API子类的方法层级。

以下会详细说明如何在框架中开发API方法，并提供部分样例代码。

#### API模块

API模块是一个独立的Python Package，具有完整的Package目录结构，API模块开发将在Package的__init__.py文件中进行。

因框架设计架构和后续调用使用规范，每个独立的API模块都有一一对应的NameSpace（命名空间）来初始化命名空间绑定

> API模块开发步骤：

在创建一个模块Python Package并在NameSpace中新增和API模块对应的ns

1. 在Package的__init__.py文件中定义NameSpace

2. 在Package的__init__.py文件中新增init_module(api)方法

3. 调用import_sub_dir方法(方法位于工具类中)将同目录下的API子类递归动态导入

4. 将定义好的NameSpace注册到参数的api中

以开发一个TestCli模块为例：

> 创建目录结构

> 示例：

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15

    #!/usr/bin/python
    # -*- coding: UTF-8 -*-
    import os

    from kptest.api.namespace import NameSpace

    from extension.utils.importer import import_sub_dir

    test_ns = NameSpace("TestNs")  # 声明和testns对应的ns    # 步骤1

    def init_module(api): # 步骤2
        base_dir = os.path.dirname(os.path.abspath(__file__))
        import_sub_dir(base_dir) # 步骤3
        api.add_namespace(test_ns) # 步骤4

#### API子类

同种API业务接口根据具体业务细分为不同分工模块的子模块

> API子类开发步骤：

1. 在对应API模块目录下新增一个继承自ApiBase基类（kptest.api.apibase.ApiBase）的API子类

2. 为子类增加对应API模块-命名空间的dispatchertype类装饰器（用于绑定可下发当前API的Host范围/通道）

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13

    from kptest.api.apibase import ApiBase
    from kptest.config.enum import HostType

    from extension.api.testns import test_ns

    @test_ns.dispatchertype(HostType.BMC, HostType.HMM) # 步骤2：绑定了下发此子类中的API的Host为BMC和管理板Host
    class TestCli(ApiBase):                             # 步骤1

        def __init__(self):
            # 初始化
            super(TestCli, self).__init__()
            ...

dispatchertype使用规则：

a. 初始化时会把测试床中Host类型与该列表匹配，默认dispatcher为匹配到的第一个，如BMC；b. 顺序匹配时Host中不存在，则进行搜索第二个，如HMM在Host中是否存在；c. get_api时可以指定使用对应的Host

> 须知：

1. dispatchertype参数、Host对象的host_type、Wrapper子类的host_type，三者有相互关联关系；若冲突会导致API方法调用失败。

2. 此子类已在上述API模块初始化中已动态引入，上层调用时无感知

#### API方法

业务操作实际实现方式，解耦软件硬件差别，屏蔽操作差异，是框架提供的对外接口

> API方法开发步骤：

1. 根据实际方法以及接口文档，逻辑定义必选参数和可选默认参数
2. 通过已有的Host对象（self.dispatcher）执行封装的通用方法或dispatch调用底层Wrapper方法组成业务操作实现（此处的Host实例化对象，即是由上述dispatchertype类装饰器指定）

    1
    2
    3
    4

    def cli_query_operation_logs(self, page=1):    # 步骤1：根据业务定义参数，也可以使用 *args, **kwargs
        params = {"page": page}
        result = self.dispatcher.dispatch("cli_query_operation_logs", params=params)[0]["parser"]    # 步骤2：Host对象调用Wrapper
        return result

【注意】对于稍加复杂的实现，需通过适配在方法中屏蔽软硬件差异，确保输入参数和返回值的一致性，参数和返回值的结构（列表、字典键名）也要求保持一致

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32

    def get_all_physical_drive_info(self, **kwargs) -> list:    # 步骤1
        params = {}
        params.update(kwargs)

        # 步骤3：通过distributor方法区分硬件分支，达到屏蔽不同硬件下发命令不同的差异

        @self.distributor(raid_tool='hiraidadm')
        def get_all_physical_drive_info_raid():
            pd_list =self.dispatch('get_controller_pdlist_hiraidadm', params)[0].get('parser')
            # 步骤3：返回值归一处理，屏蔽不同硬件回显不同的差异
            res = [self.get_one_physical_drive_info(eid=pd['eid'], sid=pd['sid']) for pd in pd_list]
            return res

        @self.distributor(raid_tool='storcli64')
        def get_all_physical_drive_info_raid():
            pd_info =self.host.dispatch('ctr_get_pd_cfg_storcli64', params)[0].get('parser')
            merged_pd_info =list()
            dosometing(merged_pd_info)
            # 步骤3：返回值归一处理，屏蔽不同硬件回显不同的差异
            res = self._format_output(merged_pd_info, 'storcli64')
            return res

        @self.distributor(raid_tool='arcconf')
        def get_all_physical_drive_info_raid():
            pd_info =self.host.dispatch('ctr_get_pd_cfg_arcconf', params)[0].get('parser')
            pd_list =list()
            dosometing(pd_list)
            # 步骤3：返回值归一处理，屏蔽不同硬件回显不同的差异
            res =self._format_output(pd_list, 'arcconf')
            return res

        return get_all_physical_drive_info_raid()

注意： API方法为框架Library统一对外接口封装，故API方法名唯一，在不同API模块或API子类中的API方法名不能重复

### 4.2 开发框架底层操作封装wrapper

> Wrapper：被测对象最底层操作的封装

Wrapper模块提供了被测对象的最基础操作（如命令行字符串、http url拼接等），是底层操作的封装；同时也提供了返回值的展示优化或解析结果。

> 可定制化开发Wrapper所在框架目录层级：extension\wrapper

Wrapper由三个部分组成：

a) Wrapper类：统一定义不同种类功能的封装，用于承载功能子模块和Wrapper方法。

b) Wrapper方法：实现具体底层操作的封装函数。

c) Parser方法：解析底层操作返回值的函数，用于将返回值解析为字典或列表。

以下会详细说明如何在框架中开发Wrapper底层封装，并提供部分样例代码。

#### Wrapper类

> Wrapper类开发步骤：

1. 新增一个继承自Wrapper基类（kptest.wrapper.wrapperbase.WrapperBase）的功能子类
2. 指定类中host_types属性与commadn_types属性的值（此值用于初始化绑定可以执行此底层操作的系统或连接方式通道，为元组类型）
3. 指定类中modules属性的值（基础功能子类能根据业务细分为多个子模块，此值用于初始化在Wrapper子类动态加载子模块，为列表或元组类型）

> 示例：

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13

    from kptest.config.enum import HostType, CommandType
    from kptest.wrapper.wrapperbase import WrapperBase

    from extension.wrapper.test import parser

    class TestWrapper(WrapperBase):                                     # 步骤1
        host_types = (HostType.BMC, HostType.HMM)                       # 步骤2：绑定BMC和管理板的Host通道，只有此类型的Host可以调用此
        command_types = (CommandType.BMCSSH, CommandType.SERIALClI)     # 步骤2：绑定BMCSSH和串口通道，只有使用此连接类型的(与Host相关)可以调用此Wrapper
        modules = [
            "extension.wrapper.test.sub_wrapper"
        ]   # 步骤3
        ...

#### Wrapper方法

> Wrapper方法：在基础功能子类或者其子模块中定义的封装具体操作的函数。

具体操作包括不限于命令行拼接输入、http url拼接输入，以当前Wrapper子类绑定的host type和command type为准

> Wrapper方法开发步骤：

1. 确认具体操作所属的基础Wrapper类或子模块，在对应的文件中创建新Wrapper方法
2. 方法固定参数`(self, params=None)`，其中params参数类型为dict
3. 根据实际具体操作及传参判断，拼接一个实际需要输入的cmd列表、url或其他command type所需的具体操作返回
4. 在上述具体操作返回中增加对应的parser解析方法，可对操作实际返回值进行初步解析（见Parser方法）

> 示例1：

    1
    2
    3
    4
    5
    6
    7
    8

    """wrapper子模块示例"""
    from extension.wrapper.test import parser

    def test_sub_wrapper_method(self):  # 步骤1
        cmd_str = "ipmcget -d faninfo"  # 步骤3
        cmd = cmd_str.split(" ")
        return [{"cmdline": cmd, "parser": parser.parse_fan_info}] # 步骤4：增加parser.parse_fan_info解析方法

> 示例2：

    1
    2
    3
    4
    5
    6

    from kptest.utils.type_check import validate_dict
    from kptest.xxx import parser

    def get_controll_foreign_storcli64(self, params=None):    # 步骤1 步骤2
        cmd = ['storcli64', '/c%s' % params.get('adapter_id'), '/fall', 'show', 'all', 'J']    # 步骤3
        return [{'cmdline': cmd, 'parser': parser.parse_get_cfg}]    # 步骤4：增加parser.parse_get_cfg解析方法

#### Parser方法

> Parser方法：解析Wrapper原始输出为更简格式的方法封装。

常用于将命令行操作的返回字符串解析为框架代码中更便于操作的dict/list

> Parser方法开发步骤：

1. 在对应Wrapper方法子类目录下parser文件中创建新Parser方法

2. 方法固定参数`(self, raw_output)`，其中raw_output参数类型为list，为wrapper方法中具体操作返回值逐行分隔的列表

3. 以实际业务逻辑对raw_output进行解析

4. 返回解析值，一般转化为dict/list；若返回None则说明解析失败，并在后续的流程中打印报错

> 示例：

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16

    import re

    def parse_fan_info(self, raw_output):   # 步骤1 步骤2
        """解析FAN信息"""
        ret_info = {}
        for line in raw_output:             # 步骤3
            if line.strip() == '':
                continue
            if 'Current mode:' in line:
                ret_info['mode'] = line.strip().split(':')[1]
            if 'Manual fan level' in line and (re.search(r'\d+', line) is not None):
                ret_info['fan_level'] = re.search(r'\d+', line).group()
            if 'Fan' in line:
                ret_info['fan_level'] = re.search(r'Fan(\d):(\W)(\d+)', line).group(3)

        return ret_info         # 步骤4：若解析失败可直接返回None

## 5. iTestSmart客户端运行测试用例

在本地用例调通测试用例后，可将测试用例导入iTestSmart客户端用于测试等目的，主要步骤包括用例信息填写以及打包、导入测试用例。

### 5.1 用例信息填写以及打包

为了在iTestSmart导入测试用例并让iTestSmart客户端读取到用例的基本信息，需要填写case.json文件并打包成zip格式压缩包，可以选择手动或者自动打包两种方式。

> 1、手动填写+手动打包

为了记录测试用例的详细信息，请在 `extension/case` 目录下的 `case.json` 文件中填写一个包含用例信息的 JSON 数组。每个测试用例的信息将由一个字典来表示。以下是 `case.json` 文件中每个用例字典字段的详细说明：

字段名 | 类型 | 描述 | 必填/非必填/可为空
--- | --- | --- | ---
name | 文本型 | 用例名称。按照3.1节用例名称规范命名。 | 必填
label | 文本型 | 用例标签名。可用中文描述 | 必填
preset | 整数型 | 是否为预置用例。预置用例为1，自定义用例为0。这里统一为0 | 可为空 (0)
description | 文本型 | 用例的位置。可用于对用例的细节信息进行描述 | 可为空
preCondition | 文本型 | 用例的前置条件。可用于对用例前置需要具备的条件进行描述 | 可为空
testStep | 文本型 | 用例的测试步骤。 | 可为空
expectedResult | 文本型 | 用例的期望结果。 | 可为空
parameters | 字典 | 用例的参数信息。每个参数将由一个字典来表示，按照3.1节用例规范填写 | 可为空 ([])
location | 文本型 | 用例目录路径(extension/case后面部分)。例如，"fan"、"fan/power"等 | 可为空
path | 文本型 | 用例在ItestSmart客户端中的层次目录标签。例如，"风扇测试" | 可为空
duration | 整数型 | 用例大约的持续时间。单位为s(秒) | 可为空 (0)

其中示例case.json内容如下所示：

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17

    [
        {
            "name": "Case001",
            "label": "风扇测试1",
            "preset": 0,
            "description": "",
            "preCondition": "前置条件",
            "testStep": "1) <步骤1>，有结果A)\n            2) <步骤2>，有结果B)\n            3) <步骤3>，有结果C)",
            "expectedResult": "A) <期望1>\n            B) <期望2>\n            C) <期望3>",
            "parameters": [],
            "location": "fan",
            "path": "风扇测试",
            "duration": 0
        },
        ...,
        ...
    ]

随后手动通过压缩工具（Winrar等）将src目录下的extension文件夹打包成zip压缩包

> 2、一键生成json并打包

按照3.1节注释模板填写用例信息，通过运行项目目录下的`setup.py`脚本，将在`项目目录/build`路径下生成`extension.zip`压缩包

### 5.2 导入并执行测试用例

在本地用例调通测试用例后，便可以通过iTestSmart客户端自动化地执行测试用例，主要步骤包括：导入测试用例、选择设备、执行。

> 导入测试用例

> 选择设备

> 执行测试用例