---
title: hytest 框架
---




## 声明

hytest 自动化软件测试框架 永久 开源免费，请大家放心使用。

<a href='https://github.com/jcyrss/hytest' target='_blank'>点击这里打开项目 Github 网址</a>，学完hytest框架如果觉得不错，请帮我在 GitHub 上 点赞加一颗星

## hytest 简介


`hytest`  适合做 黑盒系统测试自动化。


它有如下优点：

- 上手非常简单

  hytest 让大家直接用 Python 来写测试用例。
  
  如果你有 Python编程经验，1小时就可以上手，1天就可以灵活使用。
  


- 操作直观易懂 

  测试用例以 目录文件结构存放，清晰明了

  `初始化清除` 机制清晰灵活 

  可以灵活地 **挑选** 要执行的测试用例

- 漂亮的测试报告



## 安装、执行

安装 hytest 非常简单，执行如下命令即可：

```
pip install hytest 
```

注意： hytest 需要 Python 3.9 或者 更高版本


执行hytest 自动化之前，需要新建一个 `项目目录` ， 里面创建一个名为   `cases`  的子目录用来存放测试用例代码即可。

运行hytest自动化测试，非常简单，只需要

- 打开命令行窗口

- 进入自动化代码根目录，也就是  `cases 的上层目录` 

- 运行 hytest

运行命令  `hytest` 实际上就是执行如下的命令

```
python -m hytest
```


如果你是苹果Mac系统，可以执行命令

```
python3 -m hytest
```

执行后的命令行窗口界面输出，展示了执行每个用例的结果。

执行自动化，会产生一个 `log` 目录。里面有详细的 `测试日志` 和 `测试报告`。

`测试日志` 是 txt文件。

`测试报告` 是HTML格式的，会自动在浏览器中打开，可以通过右上角的浮动菜单：

- 切换 精简、详细 两种模式
  
  方便浏览内容
  
- 跳转到下一个、上一个错误
  
  方便在出现问题时，快速定位到问题所在点


测试报告的语言缺省使用操作系统语言，如果要强制指定，只需要加上 参数 `--lang` 

比如

```py
hytest  --lang=en

或者

python -m hytest  --lang=en
```


如果你不希望执行完测试后，自动打开测试报告，可以使用参数  `--auto_open_report=no` 

如下

```py
hytest --auto_open_report=no
```

关于hytest 命令行的参数使用，后面会有详细讲解，以后如果忘了某个命令参数可以执行  `hytest -h`  来查看参数说明。


以后真正做项目自动化的时候，通常还需要创建一些其他的目录，比如

-  `lib`  目录

   用来存放 测试用例 需要使用的共享代码库。


-  `doc`  目录

   存放 一些说明文档


可以使用命令参数  `--new` 来创建一个 hytest 的自动化项目目录，里面会包含一个cases目录和一个示例代码文件。

比如，执行 

```py
hytest --new auto1
```

就会在当前目录下创建名为   `auto1`  的 项目目录。



## 用例目录结构


我们先了解一下用例目录的 结构

  
-  hytest 的 `自动化测试用例`  是 写在 python 文件 中的 一个 python 类

   对应一个测试用例文档里面的用例

- 一个 代码文件  可以存放 多个用例
   
- 多个代码文件可以用目录组织起来
  



`cases` 目录下面的 每个 `目录` 和 `py` 文件 都 被称之为 `测试套件(suite)`  

 `测试套件`  是 `测试用例的集合` ， 通俗的说，就是 `一组用例` 。 

为例方便管理，我们把功能相关的测试用例组合起来放在一起，成为某个测试套件。


- 包含 `测试用例类` 的 python 文件 称之为一个  `套件文件` 
   
- 包含 套件文件的 目录 称之为  `测试套件目录` 



## 用例类的定义
 

用例文件格式如下：

文件里面每个类对应一个用例。

- 类的 `name`  属性 指定 用例名
 
  如果没有 `name` 属性，那么类名就会被当做是用例名

- 类的  `teststeps`  方法 对应 测试步骤 代码

  测试步骤代码 就是自动化地 一步步执行测试用例 的程序。

  所以一个类 **必须要有 teststeps 方法**，才会被 hytest 当做是一个测试用例类。

比如  

```py
# 建议：类名 对应 用例编号
class UI_0101:
    # 测试用例名字，也建议以用例编号结尾，方便 和 用例文档对应
    # 也方便后面 根据用例名称 挑选执行
    name = '管理员首页 - UI-0101'

    # 测试用例步骤
    def teststeps(self):
```



为了使我们的测试日志和报告 更加清晰的展示执行的过程， 我们可以调用 hytest 的一些库函数，输出 执行步骤、提示信息 、检查点信息。


代码最前面加上 如下代码 ，导入 hytest 库里面 的常用 函数 

```py
from hytest import STEP, INFO, CHECK_POINT

class UI_0101:
      
    def teststeps(self):
        
        STEP(1,'打开浏览器')
        var1 = 'sdf'
        INFO(f'var1 is {var1}')
        CHECK_POINT('打开成功', var1.startswith('1sd') ) 

        
        STEP(2,'登录')        
        CHECK_POINT('检查登录是否成功', True) 
    
        STEP(3,'查看菜单') 
        CHECK_POINT('检查菜单是否正确', True)  
```



- STEP 函数 
   
   用来声明每个测试步骤，这样日志报告更清晰

- INFO 函数 

   用来打印一些信息在日志和报告中，方便出现问题时定位。

   当然，如果你在开发调试阶段也可以直接使用print，在终端查看内容

- CHECK_POINT 函数

   用来声明测试过程中的每个检查点，任何一个检查点不通过，整个测试用例就被认为不通过。
      
   第1个参数是 检查点描述；
   
   第2个参数是 检查点表达式，比如 `result["retcode"] == 0`；
   
   第3个参数是 检查点不通过后是否继续执行该用例后面的代码。 
   
   缺省情况下，一个检查点不通过，该用例 teststeps 后面的测试代码就不会继续执行。

   如果你希望 某个检查点即使不通过，teststeps 后续代码仍然继续执行，可以使用参数  `failStop=False` ，如下所示

   ```py   
    def teststeps(self):

        CHECK_POINT('即使不通过也不中止1', False, failStop=False)

        CHECK_POINT('即使不通过也不中止2', False, failStop=False)
   ```



## 一个例子

我们使用  `白月SMS系统`   作为被测系统。

针对该系统，现在有一批测试用例，准备自动化。

我们先完成用例  `UI-0101` 的自动化，用例描述如下

```
- 用例类别
  
    管理员登录

- 前置条件

    系统中存在管理员：

    账号为 byhy
    密码为 88888888	


- 测试步骤

    1. 使用正确的管理员账号、密码登录白月SMS系统

    2. 检查左侧菜单


- 预期结果

    1. 登录成功

    2. 前三项菜单名称分别为：
    
    客户
    药品
    订单
```



对应的 hytest 测试用例 参考代码如下


```py
from hytest import *
from selenium import webdriver
from selenium.webdriver.common.by import By

class UI_0101:

    # 用例名
    name =  '检查操作菜单 UI_0101'

    # 测试步骤
    def teststeps(self):

        STEP(1,'登录网站')

        wd = webdriver.Chrome()
        wd.implicitly_wait(10)

        wd.get('http://127.0.0.1/mgr/sign.html')

        wd.find_element(By.ID, 'username').send_keys('byhy')
        wd.find_element(By.ID, 'password').send_keys('88888888')

        wd.find_element(By.TAG_NAME, 'button').click()

        STEP(2,'获取左侧菜单信息')

        eles = wd.find_elements(By.CSS_SELECTOR, '.sidebar-menu li span')

        menuText = [ele.text  for ele in eles]

        INFO(menuText)

        STEP(3,'检查菜单栏')

        CHECK_POINT('左侧菜单检查', menuText[:3] == ['客户','药品', '订单'])

        wd.quit()
```



## 初始化、清除


仔细分析，上面用例要 打开浏览器登陆

如果有100个这样的用例，就执行100次登录的操作。

这些用例 重点其实不在登录，而是后面的操作。

它们后面的操作 需要的初始环境是一样的 ：   `打开浏览器并且登录`  的环境

能不能共享执行环境?

让这些用例开始执行的时候，就处于 打开浏览器并且登录 的环境

就是我们用例执行时，就获取一个 WebDriver对象， 对应  管理员账号已经登录状态 的浏览器，后面的代码直接就可以使用这个 WebDriver对象 执行操作。


怎么 让自动化 用例执行的时候，就有一个 打开浏览器并且登录 的 `初始环境`  呢？

这就需要  `初始化` （英文叫  `setup`  ）操作

初始化 就是： 为 一个或者多个测试用例执行时，构建所需要的数据环境。

与初始化正好相反的操作就是 `清除` （英文叫 `teardown` ）。

初始化 是 创建环境，清除 是  `还原（或者说销毁）` 环境

为什么需要 `清除` 来 还原环境？

因为 执行完测试用例后 可能会对数据环境产生改变，这种改变后的数据环境，可能会影响 其它用例的执行

所以一个原则是： 

 `谁` 做的 `初始化` 操作 对环境产生了 `什么改变` ， 
 
 `谁` 就应该在 `清除` 操作里面做什么样的 `还原` 。 


hytest 的初始化/清除 支持 `3种方式` 

- 单个用例的初始化、清除
- 整个用例文件的初始化、清除
- 整个用例目录的初始化、清除


### 单个用例

首先看第一种：

单个用例的初始化、清除 是在 用例对应的类里面添加setup、teardown 方法


```py
class c0101:
    name = '管理员首页 - UI-0101'

    # 初始化方法
    def setup(self):
        open_browser()
        mgr_login()

    # 清除方法
    def teardown(self):
        wd = GSTORE['wd']
        wd.quit()

    # 测试用例步骤
    def teststeps(self):        
```



hytest 执行用例时 

- 先执行 setup 里面的代码

- 再执行 teststeps 里面的代码

- 最后再执行 teardown 里面的代码。

而且

如果 setup 执行失败（有异常）， 就不会再执行 teststeps 和 teardown 里面的代码了。

如果 teststeps 执行失败， 仍然会执行 teardown ， 确保环境被 清除



### 用例文件



精明的读者肯定已经发现，上面这种**单个用例**的初始化、清除，并没有解决我们前面说的 **多个用例** 共享数据环境的问题。

这时，我们可以使用  `整个用例文件的初始化、清除` 


就是在 文件中 添加全局函数  `suite_setup`  和  `suite_teardown`  

如下所示

```py
from hytest  import *
from lib.webui import  *
from time import sleep

def suite_setup():
    INFO('suite_setup')
    open_browser()
    mgr_login()

def suite_teardown():
    INFO('suite_teardown')
    wd = GSTORE['wd']
    wd.quit()

class c0101:
    # 测试用例名字
    name = '管理员首页 - UI-0101'

    def teststeps(self):
    # 此处省略 测试用例步骤代码    


class c0102:
    name = '管理员首页 - UI-0102'

    def teststeps(self):
    # 此处省略 测试用例步骤代码    

```



如果一个 用例文件 既有 suite_setup、suite_teardown  ，用例里面也有 setup、teardown , 执行的次序是

- 执行用例文件的 suite_setup， 
  
- 执行文件里面各个 用例 的 setup， teststeps， teardown，

- 最后执行用例文件的 suite_teardown



### 套件目录


刚才我们做到了让一个用例文件里面，所有的用例都共享初始化、清除操作。

如果 多个用例文件里面 的用例 都需要相同的初始化清除操作 怎么办？

这时，我们可以使用  `整个用例目录的初始化、清除` 

那么怎么设置一个目录共同的初始化呢？

就是 在这个目录下面创建名为  `__st__.py`  的文件。

和套件文件一样，套件目录的 的初始化、清除 也是在 文件中 添加全局函数 suite_setup、suite_teardown。 



如果 套件目录有 suite_setup、suite_teardown，  用例文件也有 suite_setup、suite_teardown  ，用例里面也有 setup、teardown , 执行的次序是：

- 执行用例目录的 suite_setup，

- 对该目录下的每个用例文件：
  
    - 执行用例文件的 suite_setup， 
      
    - 执行文件里面各个 用例 的 setup， teststeps， teardown，

    - 执行用例文件的 suite_teardown

- 执行用例目录的 suite_teardown




### 缺省初始化、清除

`用例文件`   除了 可以使用 suite_setup、suite_teardown 对整个套件进行初始化清除，还支持另外一种初始化清除： `缺省初始化、清除` 

就是定义 名称为  `test_setup`  和  `test_teardown` 的全局函数。

如果在 用例文件 中定义了 全局函数 `test_setup` ， 该文件中某个用例  `本身没有初始化` 方法， 执行自动化的时候就会 使用这个 `test_setup` 来初始化

如果在 用例文件 中定义了 全局函数 `test_teardown` ，该文件中某个用例  `本身没有清除` 方法， 执行自动化的时候就会 使用这个 `test_teardown` 来清除





## 数据关联

### 普通用法

在初始化操作里面，经常会创建一些数据，后面执行的用例中要用到这些数据。

这对于 用例类内部的初始化数据方法 setup 来说，很简单， 因为测试步骤和清除方法的都是同一个类的。

hytest框架执行时，会为该类创建实例。 所以只需要放入实例属性即可



对于 用例文件 或者 套件目录 的初始化函数， 怎么把  `suite_setup` 函数  产生的数据给里面的用例使用呢？ 

可以使用hytest内置的对象  `GSTORE`


可以使用字典式的赋值和取出元素，比如 

```py
from hytest import GSTORE

def suite_setup():
    GSTORE['环境1产品id'] = createProduct()
    GSTORE['driver'] = webdriver.Chrome()

def suite_teardown():
    deleteProduct(GSTORE['环境1产品id'])
    GSTORE['driver'].quit()


class c00303:
    name = '添加订单 - API-0303'

    def teststeps(self):
        createOrder(productid=GSTORE['环境1产品id'])
```



GSTORE 也可以进行属性赋值和取值，比如


```py
def suite_setup():
    GSTORE.productId = createProduct()
    GSTORE.driver = webdriver.Chrome()

def suite_teardown():
    deleteProduct(GSTORE.productId)
    GSTORE.driver.quit()


class c00303:
    name = '添加订单 - API-0303'

    def teststeps(self):
        createOrder(productid=GSTORE.productId)
```

这样写起来更简单，但是属性名要符合python的变量名规则，就不能有空格、加减号 之类的字符了。


### 依赖注入

如果觉得代码中使用  `GSTORE.data1` 或者  `GSTORE['data1']` 这样的写法太麻烦，可以使用 **依赖注入** 来解决

直接把你需要的 GSTORE里面的对象名称作为参数，传递给 `teststeps`方法，或者 `初始化清除方法/函数` 。

hytest 框架在调用这些方法的时候，会检查参数，自动把 GSTORE里面的同名对象赋值给该参数。

比如

```py
def suite_setup():
    GSTORE.productId = createProduct()
    GSTORE.driver = webdriver.Chrome()

# 依赖注入 GSTORE.driver 
def suite_teardown(driver):
    deleteProduct(GSTORE.productId)
    driver.quit()


class c00303:
    name = '添加订单 - API-0303'

    # 依赖注入 GSTORE.productId 
    def teststeps(self, productId):
        createOrder(productid=productId)    
```


## 数据驱动

举个例子：

某系统 登录功能的测试，有一批测试用例，其执行的步骤几乎都是一样的，只是使用的测试参数不同。

比如：

- 不输入用户名，输入正确密码

- 输入比正确用户名后面少一个字符，输入正确密码

- 输入比正确用户名后面多一个字符，输入正确密码

- 输入比正确用户名前面少一个字符，输入正确密码

- 输入比正确用户名前面多一个字符，输入正确密码

- 输入正确用户名，不输入密码

- 输入正确用户名，输入比正确密码后面多一个字符


这种情况可以使用 hytest用例 的 数据驱动格式，只需如下定义即可

```py
class c00003x:
    # ddt_cases 里面每个字典元素 定义一个用例的数据
    # 其中： name是该用例的名称， para是用例的参数
    ddt_cases = [
        {
            'name' : '登录 - 000031',
            'para' : ['user001','888888']
        },
        {
            'name' : '登录 - 000032',
            'para' : ['user0012','888888']
        },
        {
            'name' : '登录 - 000033',
            'para' : ['ser001','888888']
        }
    ]
    
    # hytest 框架执行时，会自动创建出3个用例实例
    # 并且在调用 teststeps时，把每个用例的参数设置在 self.para 中
    # 用例代码 可以直接从 self.para 中获取参数  
    def teststeps(self):
        # 取出参数
        username, password = self.para
        
        # 下面是登录测试代码
```

这样，我们就不需要定义那么多的测试用例类了， 而且测试数据也可以集中存放。

特别要注意的是： 一旦使用数据驱动定义了用例，那么这些用例的名字就不是类名（比如上面代码里面的 `UI_000x`），而是驱动里面定义的名称了（比如 `登录 UI_0001` ， `登录 UI_0002` ， `登录 UI_0003` ）。

所以，在执行测试的时候， 通过命令行参数 挑选上面的所有用例， 就应该是 `hytest --test 登录*`, 而不是 `hytest --test UI_000x` 



### 动态产生驱动数据


hytest 的驱动数据通常是固定的数据，当然也可以产生一些动态的数据

比如

```py
from hytest import *

class UI_000x:

    ddt_cases = []
    for i in range(10):
        ddt_cases.append({
            'name': f'登录 UI_000{i+1}',
            'para': [None, f'{i+1}'*8,'请输入用户名']
        })
 
    def teststeps(self):
        INFO(f'{self.para}')
```



hytest 的运行分为两个阶段

- 收集测试用例

  这个阶段会搜集所有的用例目录下面的代码中的用例类，
  
  把这些类实例化， 从而创建  `用例实例对象` 

- 执行测试用例

  依次执行 上一步中创建的 `用例实例对象`



所以第一步中  **搜集创建用例对象，是在 执行用例之前** 的

所以ddt_cases 里面  **不能使用 用例运行时 才会产生的数据** 。

比如

```py
from hytest import *
 
# 套件初始化
def suite_setup():
    GSTORE['ddt_cases_UI_000x'] = []    
    for i in range(10):
        GSTORE['ddt_cases_UI_000x'].append({
            'name': f'登录 UI_000{i+1}',
            'para': [None, f'{i+1}'*8,'请输入用户名']
        }) 
  
class UI_000x:
    # 想使用套件初始化 设置的GSTORE数据？？不行❌ 
    # 因为 ddt_cases 收集阶段就会执行，这时还没有执行suite_setup()
    # 这时 GSTORE里面还是空的，所以会报错
    ddt_cases = GSTORE['ddt_cases_UI_000x']

    def teststeps(self):
        INFO(f'{self.para}')
```





## 挑选用例执行 - 名称方式


执行自动化测试的时候，我们往往并不需要执行 `全部的` 测试用例。

比如：冒烟测试，只需要测试冒烟测试的那些用例。 

或者调试自己写的某个用例的自动化，就只需要执行那一个用例。

hytest  可以灵活的挑选要执行的测试用例。


我们可以通过  `--test`  或者  `--suite`  命令行参数 来指定执行哪些用例或者套件，而且还支持用通配符的方式。

```py
--test testA               # 执行名为 testA 的用例
--test testA --test testB  # 执行名为 testA 和  testB 的用例
--test test*               # 执行名字以 test 开头的用例
--suite 订单管理            # 执行 名为 订单管理 的套件
```


比如，我们想只测试  `药品管理`   这个套件

```
hytest --suite  药品管理  
```


比如，我们想只测试  `界面 - UI-0101`   这个用例

```
hytest --test  "界面 - UI-0101"  
```

因为用例名中有空格，所以必须用双引号包起来。

通常，我们的用例名后面会包含用例的 ID，  这样就可以很方便的根据用例ID，来选择用例了

比如 这样

```
hytest --test  *0101  
```

就可以挑选我们 练习中的 用例名为  `界面 - UI-0101` 的用例执行




假如，你的测试领导，要求做冒烟测试， 挑选出来的用例编号为以下这些：

```
UI-0301
UI-0302
UI-0303
UI-1401
UI-1402
```

我们就可以这样执行 

```
hytest --test *0301  --test *0302 --test *0303 --test *1401 --test *1402
```

大家自然会想到，如果要执行的用例太多，比如 1000 个，命令行参数岂非太长了？


这时我们可以使用参数文件，可以把所有的参数都放在参数文件中，比如，创建一个名字为 `args` 的参数文件，内容如下

```
--test *0301
--test *0302
--test *0303
--test *1401
--test *1402
```

一行一个参数

然后， 我们的命令就只需要   `hytest -A args`  就可以了



## 挑选用例执行 - 标签方式

hytest  还有一种选择测试用例的方法：根据用例的 **标签** 来挑选用例



### 给用例添加标签


我们可以给测试用例打上标签（tag），这样在运行的时候，可以通过标签 指定要运行哪些用例。

标签 就是 用例的 属性特征描述

测试用例可以有多个标签描述它的属性特征， 比如一个登录测试的用例， 可以有3个标签： 登录功能、冒烟测试、UI测试

hytest 可以根据设置的标签 选择执行该用例。 


给用例添加标签有如下几种方式

- 全局变量 force_tags

如果我们在测试用例文件 定义了一个名为 force_tags 的全局变量，格式如下

```py
force_tags = ['登录功能','冒烟测试','UI测试']
```

那么该文件里面所有测试用例都具有了这些标签。

标签一定要放在列表中，即使只有一个标签


如果 **测试套件目录** 的 `__st__.py` 里面，定义了 force_tags , 那么 **该目录里面所有测试用例** 都具有了这个force_tags里面指定的标签


- 测试用例类的 tags 属性
  
如果我们在测试用例类 定义了一个名为 tags 属性，格式如下

```py
class c00001:
    name = '添加订单 - 00001'
    # 用例标签，可选   
    tags = ['登录功能','冒烟测试','UI测试']
```

那么本测试用例就具有了这些标签。


### 根据标签挑选


在执行自动化的时候，我们可以通过命令行参数指定标签，从而挑选要执行的测试用例

比如：

```py
# 执行包含 标签 '冒烟测试' 的用例. 
--tag 冒烟测试  


# 执行不包含标签 '冒烟测试' 的用例.
--tagnot 冒烟测试 


# 执行 同时有 冒烟测试、UITest 两个标签的用例
--tag "'冒烟测试' and 'UITest'"


# 执行 有 冒烟测试 或者 UITest 标签 的用例
--tag 冒烟测试   --tag UITest


# 执行标签格式为 A*B 的用例，比如 A5B， AB， A444B
--tag A*B    
```





## 挑选用例执行 - 指定目录和文件



如果我们只想执行 某个  `测试套件目录`  下面的用例，或者某个具体的测试用例文件，怎么办？

可以直接指定该目录或者文件的路径：

```py
hytest cases/order cases/user
```

就是执行  `cases/order` 和 `cases/user`  目录下面的文件里面的所有测试用例



也可以直接指定某些测试用例文件

```py
hytest cases/a/c/t1.py  cases/a/c/t2.py cases/a/b/t3.py
```

就是执行  `cases/a/c/t1.py` 、 `cases/a/c/t2.py` 、 `cases/a/b/t3.py`  这3个测试用例文件里面的所有测试用例


当然，也可以混合使用 目录和文件

```py
hytest cases/a/c/t1.py  cases/f cases/a/b cases/a/c/t2.py
```




如果指定的目录或者文件不存在， hytest 会报错并且退出。

<br>

如果指定的目录包含另外的目录或者文件，会直接去掉被包含的目录或者文件

比如

```
hytest cases/a cases/a/c/t1.py
```

就等于直接执行  

```
hytest cases/a
```

因为， `cases/a` 目录已经包含了  `cases/a/c/t1.py` 文件
## 其他功能


### 指定测试报告标题


使用参数  `--report_title` 指定测试报告标题

比如


```py
hytest --report_title 回归测试第2轮报告
```

<br>

如果标题中有需要有空格， 加上双引号，如下所示


```py
hytest --report_title "回归测试第2轮 报告"
```

<br>

注意，该功能是  `hytest v0.8.2`  新加入的



### 报告中加入图片
在用 selenium做 web自动化、Appium 做手机自动化 时， 可以使用hytest库中的函数  `SELENIUM_LOG_SCREEN` 进行截屏并写入测试报告中。

如下所示：

```py
from hytest import *

class c1:
    name = 'web-lesson-0001'

    def teststeps(self):
        self.driver = webdriver.Chrome()
        self.driver.get('http://192.168.56.103/sign.html')

        # 第1个参数是 webdriver 对象
        # width 参数为可选参数， 指定图片显示宽度
        SELENIUM_LOG_SCREEN(driver, width='70%') 
```

<br>

如果你想 直接在报告中 插入 已经产生的图片，可以使用hytest库中的函数  `LOG_IMG` 

如下所示：

```py
from hytest import *

class c1:
    name = 'web-lesson-0001'

    def teststeps(self):

        # 第1个参数是 图片路径，可以是网络url 
        LOG_IMG('http://www.byhy.net/xxx.png')
        # 也可以是相对报告文件的本地路径
        LOG_IMG('imgs/abc.png')
        # width 参数为可选参数， 指定图片显示宽度
        LOG_IMG('imgs/abc.png', width='70%')
```


### 在Jenkins中触发运行


当 执行 hytest 有参数  `--report_url_prefix`， 

比如： `hytest --report_url_prefix http://192.168.5.156` 

执行完测试，就会把测试报告拷贝到 自动化项目的 `reports` 目录里面

并且测试结束会打印出 类似这样的一句话

```
测试报告 : http://192.168.5.156/report_20230108_180546.html
```

<br>


所以，在执行前，我们应该启动查看测试报告的web服务
  
比如 在自动化测试项目目录下面执行：  
  
```py
python -m http.server 80 --directory reports
```

就会把当前目录下的  `reports`  作为web访问的根目录

如果，执行测试的主机IP为 192.168.5.156 

就可以通过  `http://192.168.5.156`  查看测试报告


<br>

假设要执行的 hytest 自动化项目目录路径为  `d:/my/elife_autotest` 

Windows环境下，要在 Jenkins 控制器主机 直接执行 包含 hytest 自动化测试 的 Pipeline时

我们可以这样设置 Pipeline 的  `Jenkinsfile` ：


```java
pipeline {
    agent any

    stages {
        stage('Build') {
            steps {
                echo 'build release'
            }
        }
        
        
        stage('Test') {
            steps {
                dir("d://my//elife_autotest") {
                    bat "hytest --report_url_prefix http://192.168.5.156"
                }
            }
        }
    }    
    
}
```

<br>
 
Jenkins 运行后，会在 build 结果中包含 hytest 的 控制台输出内容，也就包含了测试结果和报告链接，方便从Jenkins 视图查看。





### 和其他系统集成



在测试执行过程中， hytest 会发出  `测试开始执行、每个用例执行结果、测试执行完毕`  等事件。

开发者 可以 自己写代码， 处理这些事件，从而和测试使用的其他系统集成。

比如，你想 在测试的过程中，每执行完一个用例，就把测试结果写入到 测试记录系统中。 

这个测试记录系统可能是一个Excel 文件，也可能是 JIRA 之类的 测试管理系统。 

<br>

这些代码可以定义在 hytest 用例根目录的  `__st__.py`  文件中。 

<br>

下面的代码示例，展示如何 在测试过程中， 动态的把 测试结果写入 Excel测试用例执行 记录文件。

```py
class MySignalHandler:
    TEST_RET_COL_NO = 6 # 测试结果在用例excel文件中的列数

    def __init__(self):
        self.caseNum2Row = {} # 用例编号->行数 表
        self.getCaseNum2RowInExcel()
        
        # 执行 pip install pypiwin32 确保安装了 win32com 所在的库
        import win32com.client
        self.excel = win32com.client.Dispatch("Excel.Application")
        self.excel.Visible = True
        workbook = self.excel.Workbooks.Open(r"h:\tcs-api.xlsx")
        self.sheet = workbook.Sheets(1)

    def getCaseNum2RowInExcel(self):
        """
        得到Excel 中用例 编号对应的行数，方便填写测试结果
        """
        import xlrd
        book = xlrd.open_workbook(r"h:\tcs-api.xlsx")
        sheet = book.sheet_by_index(0)
        caseNumbers = sheet.col_values(colx=1)
        print(caseNumbers)

        for row, cn in enumerate(caseNumbers):
            if '-' in cn:
                self.caseNum2Row[cn] = row + 1

        print(self.caseNum2Row)

    def case_result(self, case):
        """
        case_result 是 每个用例执行结束 ，会调用的函数

        @param case: 用例类 实例
        """
        
        # 找到对应的测试用例在excel中的行数
        caseNo = case.name.split(' - ')[-1].strip()
        cell = self.sheet.Cells(self.caseNum2Row[caseNo], self.TEST_RET_COL_NO)
        # 翻动滚动条，保证当前测试结果单元格可见
        self.excel.ActiveWindow.ScrollRow = self.caseNum2Row[caseNo]-2

        if case.execRet == 'pass':
            cell.Value = 'pass'
            cell.Font.Color =  0xBF00 # 设置为绿色 
        else:
            cell.Font.Color =  0xFF   # 设置为红色 
            if case.execRet == 'fail':
                cell.Value = 'fail'
            elif case.execRet == 'abort':
                cell.Value = 'abort'

    def test_end(self, runner):
        """
        test_end 是 整个测试执行完 ，会调用的函数

        @param runner :  hytest runner 对象
               runner.case_list:  列表，里面包含了所有用例类实例
        """
        for case in runner.case_list:
            print(f'{case.name} --- {case.execRet}')

# 注册这个类的实例 为一个 hytest 信号处理对象
from hytest import signal
signal.register(MySignalHandler())
```
