# hytestProject **Repository Path**: mathmost/hytestProject ## Basic Information - **Project Name**: hytestProject - **Description**: hytest demo示例以及笔记 - **Primary Language**: Python - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-07-25 - **Last Updated**: 2021-07-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # hytestProject hytest demo示例以及笔记 # 1 简介 - `hytest` (黑羽test)是白月黑羽自己研发的自动化测试框架,它非常适合 做 系统测试 自动化, 而相比之下,pytest、unittest 更适合白盒的单元测试、集成测试 - 优点 - 上手非常简单 - 操作直观易懂: - 测试用例以目录文件结构存放, 清晰明了 - 初始化清除机制清晰灵活 - 可以灵活地挑选要执行的测试用例 - 漂亮的测试报告 - 安装(python需要3.6或者以上版本) pip install hytest hytest -h - 运行 测试用例放在cases目录下 进入该目录,运行 hytest # 2 用例定义和用例目录 - 用例目录结构 - cases` 目录下面的 每个目录 和 py 文件 都 被称之为 `测试套件(suite) `测试套件` 是 `测试用例的集合` , 通俗的说,就是 `一组用例` 为例方便管理,我们把功能相关的测试用例组合起来放在一起,成为某个测试套件 - 包含 测试用例类 的 python 文件 称之为一个 `套件文件` - 包含 套件文件的 目录 称之为 `测试套件目录` - 用例类的定义 - 文件中的每个类对应一个用例 - 类的name属性 指定用例名称 - 如果没有name属性,那么类型就会被当作用例名称 - 类的 teststeps方法 对应测试步骤代码 - 测试步骤代码 就是自动化地 一步步执行测试用例 的程序 - 所以一个类 **必须要有 teststeps 方法**,才会被 hytest 当做是一个测试用例类 - ```python # 建议:类名 对应 用例编号 class UI_0101: # 测试用例名字,也建议以用例编号结尾,方便 和 用例文档对应 # 也方便后面 根据用例名称 挑选执行 name = '管理员首页 - UI-0101' # 测试用例步骤 def teststeps(self): pass ``` - hytest封装的一些库函数,输出执行步骤 提示信息 检查点信息等 ```python from hytest import STEP, INFO, CHECK_PIONT class UI_001: name = "test demo" def teststeps(self): STEP(1, '打开浏览器') var = 'sm' INFO('var is {}'.format(var)) CHECK_PIONT('打开成功', var.startwith('s')) STEP(2, '登陆') CHECK_PIONT('检查登陆是否成功', True) STEP(3, '查看菜单') CHECK_PIONT('检查菜单栏是否成功', True) ``` - STEP >>> 用来声明每个测试用例的步骤,这样的日志报告更加清晰 - INFO >>> 用来打印一些信息在日志和报告中,方便出现问题定位 - CHECK_PIONT >>> 用来生命测试过程中的每个检查点,也可以被称为断言 # 3 一个用例的自动化 ```python from hytest import * from selenium import webdriver class UI_0101: name = '检查操作菜单 UI_0101' def teststeps(self): STEP(1,'登录网站') options = webdriver.ChromeOptions() options.add_experimental_option('excludeSwitches', ['enable-logging']) wd = webdriver.Chrome(options=options) 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() ``` # 4 公共代码放入库中 - ```python class UI_0102: name = '添加客户 UI_0102' def teststeps(self): STEP(1, '登录网站') options = webdriver.ChromeOptions() options.add_experimental_option('excludeSwitches', ['enable-logging']) wd = webdriver.Chrome(options=options) 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, '点击左侧客户菜单') # 先找到上层节点,缩小查找范围 sidebarMenu = wd.find_element_by_class_name('sidebar-menu') # 再找到内部元素 elements = sidebarMenu.find_elements_by_tag_name('span') # 第一个span对应的菜单是 客户,点击它 elements[0].click() STEP(3, '添加客户') # 点击添加客户按钮 wd.find_element_by_class_name('glyphicon-plus').click() # form-contorl 对应3个输入框 inputs = wd.find_elements_by_css_selector('.add-one-area .form-control') # 输入客户姓名 inputs[0].send_keys('南京中医院') # 输入联系电话 inputs[1].send_keys('2551867858') # 输入客户描述 inputs[2].send_keys('江苏省-南京市-秦淮区-汉中路-16栋504') # 第1个 btn-xs 就是创建按钮, 点击创建按钮 wd.find_element_by_css_selector('.add-one-area .btn-xs').click() # 等待1秒 sleep(1) STEP(4, '检查添加信息') # 找到 列表最上面的一栏 item = wd.find_elements_by_class_name('search-result-item')[0] fields = item.find_elements_by_tag_name('span')[:6] texts = [field.text for field in fields] INFO(texts) # 预期内容为 expected = [ '客户名:', '南京中医院', '联系电话:', '2551867858', '地址:', '江苏省-南京市-秦淮区-汉中路-16栋504' ] CHECK_POINT('客户信息和添加内容一致 ', texts == expected) wd.quit() ``` - 检查代码可以发现用例1和用例2的登陆代码完全一致,为了去除冗余代码,则需要将登陆代码放到一个单独的库中维护 - 比如,新建lib目录,并在其目录下心间 webui.py ```python from selenium import webdriver from hytest import * def open_browser(): INFO('打开浏览器') options = webdriver.ChromeOptions() options.add_experimental_option('excludeSwitches', ['enable-logging']) wd = webdriver.Chrome(options=options) wd.implicitly_wait(10) GSTORE['wd'] = wd def mgr_login(): wd = GSTORE['wd'] 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() ``` - 注意: 我们在创建 WebDriver 对象后,把它存到了 hytest 全局存储对象 GSTORE 中。 方便其他的代码获取 - 然后修改测试用例, 使用公共脚本 ```python from lib.webui import * class UI_0102: # 测试用例步骤 def teststeps(self): STEP(1, '登录网站') open_browser() mgr_login() # 下面是业务代码 ... ``` # 5 用例初始化、清除 ## 5.1 单个用例 单个用例的初始化、清除 是在 用例对应的类里面添加setup、teardown 方法 ```python class c0101: name = '管理员首页 - UI-0101' # 初始化方法 def setup(self): open_browser() mgr_login() # 清除方法 def teardown(self): wd = GSTORE['wd'] wd.quit() # 测试用例步骤 def teststeps(self): pass ``` hytest 执行用例时 - 先执行 setup 里面的代码 - 再执行 teststeps 里面的代码 - 最后再执行 teardown 里面的代码 而且如果 setup 执行失败(有异常), 就不会再执行 teststeps 和 teardown 里面的代码了, 如果 teststeps 执行失败, 仍然会执行 teardown , 确保环境被清除 ## 5.2 用例文件 在 文件中 添加全局函数 `suite_setup` 和 `suite_teardown` ```python 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): pass ``` 如果一个用例文件既有suite_setup、suite_teardown ,用例里面也有 setup、teardown,执行顺序如下 ​ ![image](http://cdn1.python3.vip/imgs/gh/36257654_64908999-0942ef00-d739-11e9-8016-6bd8264ee81d.png) ## 5.3 套件目录 多个用例文件里面的用例都需要相同的初始化清除操作,可以使用 `整个用例文件的初始化、清除` 在这个目录下面创建名为 `__st__.py` 的文件,和套件文件一样,套件目录的 的初始化、清除 也是在 文件中 添加全局函数 suite_setup、suite_teardown,如果 套件目录有 suite_setup、suite_teardown, 用例文件也有 suite_setup、suite_teardown ,用例里面也有 setup、teardown , 执行的顺序如下所示 ![image](http://cdn1.python3.vip/imgs/gh/36257654_64909469-2a0e4300-d73f-11e9-9436-3fd3afa5f841.png) # 6 数据驱动 (1)概念:如果有一批测试用例,具有 `相同的测试步骤` ,只是 `测试参数数据不同` ,自动化测试时,把测试数据从用例代码中 `分离` 开来,以后增加新的测试用例,只需要修改数据 (2)举个例子 ​ 某系统 登录功能的测试,有一批测试用例,其执行的步骤几乎都是一样的,只是使用的测试参数不同 - 不输入用户名,输入正确密码 - 输入比正确用户名后面少一个字符,输入正确密码 - 输入比正确用户名后面多一个字符,输入正确密码 - 输入比正确用户名前面少一个字符,输入正确密码 - 输入比正确用户名前面多一个字符,输入正确密码 - 输入正确用户名,不输入密码 - 输入正确用户名,输入比正确密码后面多一个字符 (3)示例代码 ```python from time import sleep from lib.webui import * class UI_000x: ddt_cases = [ { 'name': '登录 UI_0001', 'para': [None, '88888888','请输入用户名'] }, { 'name': '登录 UI_0002', 'para': ['byhy', None, '请输入密码'] }, { 'name': '登录 UI_0003', 'para': ['byh', '88888888','登录失败 : 用户名或者密码错误'] } ] def teststeps(self): wd = GSTORE['wd'] wd.get('http://127.0.0.1/mgr/sign.html') username, password, info = self.para if username is not None: wd.find_element_by_id('username').send_keys(username) if password is not None: wd.find_element_by_id('password').send_keys(password) wd.find_element_by_tag_name('button').click() sleep(2) notify = wd.switch_to.alert.text CHECK_POINT('弹出提示', notify == info) wd.switch_to.alert.accept() def teardown(self): wd = GSTORE['wd'] wd.find_element_by_id('username').clear() wd.find_element_by_id('password').clear() ``` # 7 挑选测试用例-根据名字 hytest 可以灵活的挑选要执行的测试用例 我们可以通过 `--test` 或者 `--suite` 命令行参数 来指定执行哪些用例或者套件,而且还支持用通配符的方式 ```python --test testA # 执行名为 testA 的用例 --test testA --test testB # 执行名为 testA 和 testB 的用例 --test test* # 执行名字以 test 开头的用例 --suite 订单管理 # 执行 名为 订单管理 的套件 ``` 比如,我们想只测试 `药品管理` 这个套件 ```python hytest --suite 药品管理 ``` 比如,我们想只测试 `界面 - UI-0101` 这个用例 ```python hytest --test "界面 - UI-0101" ``` 如果要执行的用例太多,比如 1000 个,命令行参数岂非太长了?这时我们可以使用参数文件,可以把所有的参数都放在参数文件中,比如,创建一个名字为 args 的参数文件,内容如下 ```python --test *0301 --test *0302 --test *0303 --test *1401 --test *1402 ``` 一行一个参数,然后我们的命令就只需要 `hytest -A args` 就可以了 # 8 挑选测试用例-根据标签 给用例添加标签有如下几种方式 - 全局变量 force_tags 如果我们在测试用例文件 定义了一个名为 force_tags 的全局变量,格式如下 ```python force_tags = ['登录功能','冒烟测试','UI测试'] ``` 那么该文件里面所有测试用例都具有了这些标签 标签一定要放在列表中,即使只有一个标签 如果我们在测试套件目录初始化文件__st__.py定义了一个这样的 force_tags 全局变量, 那么该目录里面所有测试用例都具有了该tag - 测试用例类的 tags 属性 如果我们在测试用例类 定义了一个名为 tags 属性,格式如下 ```python class c00001: name = '添加订单 - 00001' # 用例标签,可选 tags = ['登录功能','冒烟测试','UI测试'] ``` 在执行自动化的时候,我们可以通过命令行参数,指定标签,从而挑选要执行的测试用例 ```python # 执行包含 标签 '冒烟测试' 的用例. --tag 冒烟测试 # 执行不包含标签 '冒烟测试' 的用例. --tagnot 冒烟测试 # 执行 同时有 冒烟测试、UITest 两个标签的用例 --tag "'冒烟测试' and 'UITest'" # 执行 有 冒烟测试 或者 UITest 标签 的用例 --tag 冒烟测试 --tag UITest # 执行标签格式为 A*B 的用例,比如 A5B, AB, A444B --tag A*B ``` # 9 测试报告中插入截图 在用 selenium做 web自动化、Appium 做手机自动化 时, 可以使用hytest库中的函数 `SELENIUM_LOG_SCREEN` 进行截屏并写入测试报告 ```python 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%') ``` 如果你想 直接在报告中 插入 已经产生的图片,可以使用hytest库中的函数 `LOG_IMG` ```python 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%') ``` # 10 自动化测试结果集成到excel中 注意:下面的功能是 hytest 0.7.1 以后版本才支持 在测试执行过程中, hytest 会发出 `测试开始执行、每个用例执行结果、测试执行完毕` 等事件 开发者 可以 自己写代码, 处理这些事件,从而和测试使用的其他系统集成 比如,你想 在测试的过程中,每执行完一个用例,就把测试结果写入到 测试记录系统中 这个测试记录系统可能是一个Excel 文件,也可能是 Test Request、禅道、JIRA 之类的 测试管理系统 这些代码可以定义在 hytest 用例根目录的 `__st__.py` 文件中 代码示例: 展示如何 在测试过程中, 动态的把 测试结果写入 Excel测试用例执行 记录文件 ```python class MySignalHandler: TEST_RET_COL_NO = 6 # 测试结果在用例excel文件中的列数 def __init__(self): self.caseNum2Row = {} # 用例编号 >>> 行数 表 self.getCaseNum2RowInExcel() 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()) ``` # 11 API自动化demo