五、UI自动化测试05--PyTest框架
目录
- 一、PyTest 框架
- 2. 特点
- 2. 安装步骤
- 3. 基本使⽤
- 3.1 测试函数形式
- 3.2 执⾏⽅式
- 3.3 测试类形式
- 3.4 执⾏⽅式
- 3.5 另⼀种执⾏⽅式: 主函数执⾏
- 3.6 特殊⽅法: 函数级别
- 3.7 特殊⽅法: 类级别
- 3.8 特殊⽅法: 函数级别和类级别同时使⽤
- 4. pytest 配置⽂件
- 4.1 选项字段获取
- 4.2 编写步骤
- 4.3 使⽤步骤
- 4.4 默认设置
- 4.5 ⾃定义规则
- 4.6 扩展: 指定单个⽂件/类/⽅法执⾏
- 二、 pytest 常⽤插件
- 1. HTML 报告插件
- 1.1 安装步骤
- 1.2 使⽤步骤
- 2. 控制⽅法执⾏顺序插件
- 2.1 安装步骤
- 2.2 使⽤步骤
- 3. 失败重试插件
- 3.1 安装步骤
- 3.2 使⽤步骤
- 三、pytest ⾼级功能
- 1. 跳过操作
- 2. 参数化操作
- 2.1 单个参数
- 2.2 多个参数
- 3. 扩展: 断⾔⽅法的使⽤
- 3.1 Python ⾃带的断⾔⽅法
- 3.2 pytest 框架中使⽤断⾔
- 四、PO 设计模式
- 1. v0 版本
- 2. 账户不存在脚本
- 3. 密码错误脚本
一、PyTest 框架
- 说明: pytest 是 Python 的⼀种单元测试框架,同⾃带的 UnitTest 测试框架类似,相⽐ UnitTest 框架使⽤起来更简洁,效率更⾼。(在⾃动化测试中同样充当测试执⾏的功能! 并且可以跟 UnitTest 互换)
2. 特点
-
- ⾮常容易上⼿,⼊⻔简单,⽂档丰富,⽂档中有很多实例可以参考
- 官⽅⽂档地址: https://docs.pytest.org/en/6.2.x/
-
- ⽀持简单的单元测试和复杂的功能测试
-
- ⽀持参数化: UnitTest 需要通过插件扩展参数化功能!
-
- 执⾏测试过程中可以将某些测试跳过,或者对某些预期失败的 Case 标记成失败
-
- ⽀持重复执⾏失败的 Case : 通过安装插件实现
-
- ⽀持运⾏由 Nose,UnitTest 编写的测试 Case : pytest 框架的脚本在 UnitTest 下⽆法执⾏
-
- 具有很多第三⽅插件,并且可以⾃定义扩展
- 插件获取⽹站:
- https://docs.pytest.org/en/latest/reference/plugin_list.html
-
- ⽅便的和持续集成⼯具集成
2. 安装步骤
说明: 与 UnitTest 不同的是, pytest 框架需要先安装才能使⽤!
- 安装:
- pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pytest
- 查看: pip show pytest
- 确认版本: pytest --version
安装与查看版本步骤
3. 基本使⽤
3.1 测试函数形式
# 函数形式
def test_func(): # 要求函数名以 test 开头"""测试函数"""print('我是测试函数')
3.2 执⾏⽅式
3.3 测试类形式
# 测试类形式
class TestDemo(object): # 正常定义类, 但是测试类名必须以 Test 开头"""测试示例类"""def test_method1(self): # 正常定义⽅法, 但是测试⽅法名必须以
test 开头"""测试⽅法1"""print('测试⽅法1')def test_method2(self):"""测试⽅法2"""print('测试⽅法2')
3.4 执⾏⽅式
3.5 另⼀种执⾏⽅式: 主函数执⾏
说明: 使⽤主函数, 可以实现在代码⽂件内, ⿏标右键菜单执⾏
- 测试函数形式下使⽤
# 测试函数形式
import pytestdef test_func(): # 要求函数名以 test 开头"""测试函数"""print('我是测试函数')if __name__ == '__main__':# 语法: pytest.main(['-s', '⽂件名.py'])pytest.main(['-s', 'hm03_pytest_basic_03.py'])
- 测试类形式下使⽤
# 测试类形式
import pytestclass TestDemo(object): # 正常定义类, 但是测试类名必须以 Test 开头"""测试示例类"""def test_method1(self): # 正常定义⽅法, 但是测试⽅法名必须以 test 开头"""测试⽅法1"""print('测试⽅法1')def test_method2(self):"""测试⽅法2"""print('测试⽅法2')if __name__ == '__main__':# 语法: pytest.main(['-s', '⽂件名.py'])pytest.main(['-s', 'hm04_pytest_basic_04.py'])
3.6 特殊⽅法: 函数级别
- 说明: 函数级别特殊⽅法的执⾏逻辑与 UnitTest 中的 Fixture 的⽅法级别的执⾏逻辑⼀致!
"""
特殊⽅法: 函数级别
"""
import pytestclass TestDemo(object):"""测试示例类"""# 说明: 特殊⽅法名写法固定, 没有代码提示, 需要⼿写!# 注意: 函数级别执⾏顺序: # 先 setup() -> 测试⽅法1 -> teardown() ⽅法, 再 setup() -> 测试⽅法2 -> teardown() ⽅法def setup(self):"""开始⽅法"""print('函数 -> 开始')def teardown(self):"""结束⽅法"""print('函数 -> 结束')def test_method1(self):"""示例测试⽅法"""print('测试⽅法1')def test_method2(self):"""示例测试⽅法"""print('测试⽅法2')if __name__ == '__main__':pytest.main(['-s', 'hm05_pytest_basic_05.py'])
3.7 特殊⽅法: 类级别
- 说明: 类级别特殊⽅法的执⾏逻辑与 UnitTest 中的 Fixture 的类级别的执⾏逻辑⼀致!
"""
特殊⽅法: 类级别
"""
import pytest
class TestDemo(object):"""测试示例类"""# 说明: 特殊⽅法名写法固定, 没有代码提示, 需要⼿写!# 注意: 类级别执⾏顺序:# 先 setup_class() -> 测试⽅法1 -> 测试⽅法2 ->
teardown_class() ⽅法def setup_class(self):"""开始⽅法"""print('类 -> 开始')def teardown_class(self):"""结束⽅法"""print('类 -> 结束')def test_method1(self):"""示例测试⽅法"""print('测试⽅法1')def test_method2(self):"""示例测试⽅法"""print('测试⽅法2')if __name__ == '__main__':pytest.main(['-s', 'hm06_pytest_basic_06.py'])
3.8 特殊⽅法: 函数级别和类级别同时使⽤
"""
特殊⽅法: 函数级别和类级别同时使⽤
"""
import pytestclass TestDemo(object):"""示例测试类"""# 执⾏顺序: 1 -> 3 -> 5 -> 4 -> 3-> 6 -> 4 -> 2def setup_class(self): # 1print('类级别 ->> 开始')def teardown_class(self): # 2print('类级别 ->> 结束')def setup(self): # 3print('函数级别 -> 开始')def teardown(self): # 4print('函数级别 -> 结束')def test_method1(self): # 5"""测试⽅法1"""print('测试⽅法1')def test_method2(self): # 6"""测试⽅法2"""print('测试⽅法2')if __name__ == '__main__':pytest.main(['-s', 'hm07_pytest_basic_07.py'])
4. pytest 配置⽂件
- 说明: 使⽤配置⽂件后可以快速的使⽤配置的项来选择执⾏哪些测试模块
- 注意:
-
- 在 Windows 系统下, pytest 配置⽂件中, 不允许写注释信息
-
- ⼀个⼯程内只需要⼀个 pytest 配置⽂件, 并且需要保证⽂件名正确
-
- ⼀般情况, 只需要将 pytest 配置⽂件, 置于⼯程根⽬录下
-
- 配置有 pytest 配置⽂件的⼯程, 只需要打开命令⾏, 输⼊ pytest 指令, 即可执⾏测试
-
4.1 选项字段获取
4.2 编写步骤
4.3 使⽤步骤
- 说明: 打开命令⾏ -> 输⼊ pytest -> 执⾏即可
4.4 默认设置
- 说明: 测试⽤例⽂件名/测试类名/测试⽅法名均为 Test/test 开头
[pytest]
testpaths = ./case
addopts = -s
python_files = test*.py
python_classes = Test*
python_functions = test*
4.5 ⾃定义规则
- 说明: 测试⽤例⽂件名/测试类名/测试⽅法名, 需要根据具体项⽬进⾏设置, 以下以 Hm/hm为例
[pytest]
testpaths = ./case
addopts = -s
python_files = hm*.py
python_classes = Hm*
python_functions = hm*
4.6 扩展: 指定单个⽂件/类/⽅法执⾏
[pytest]
testpaths = ./case
addopts = -s
python_files = demo_case1.py
python_classes = DemoDemo1
python_functions = demo_method1
二、 pytest 常⽤插件
1. HTML 报告插件
1.1 安装步骤
- ⽅式1 -> 安装: pip install pytest-html
- ⽅式2 -> PyCharm 中安装
1.2 使⽤步骤
-
- 在 pytest 配置⽂件中, 增加命令选项
- 选项: --html=./报告路径/报告⽂件名.html --self-contained-html
- 说明: --self-contained-html 将 CSS ⽂件内嵌到报告⽂件中
-
- 执⾏ pytest 指令, 运⾏测试即可
-
添加命令选项执⾏
-
查看⽣成的报告内容
2. 控制⽅法执⾏顺序插件
2.1 安装步骤
- ⽅式1 -> 安装: pip install pytest-ordering
- ⽅式2 -> PyCharm 中安装
2.2 使⽤步骤
- 说明: 直接在测试类或测试⽅法上⽅添加 @pytest.mark.run(order=序号)
"""
pytest 控制⽅法执⾏顺序插件
"""
import pytest@pytest.mark.run(order=2)
class TestDemo(object):"""示例测试类"""# 语法: @pytest.mark.run(order=序号)# 注意: run(order=序号) 没有代码提示, 需要⼿写!# @pytest.mark.run(order=-3)def test_method1(self):"""测试⽅法1"""print('测试⽅法1')# @pytest.mark.run(order=1)def test_method2(self):"""测试⽅法2"""print('测试⽅法2')# @pytest.mark.run(order=2)def test_method3(self):"""测试⽅法3"""print('测试⽅法3')# 扩展: 序号⽀持正数和负数, 以及正负混合# 1. 纯正数: 数越⼩, 优先级越⾼[掌握]# 2. 纯负数: 数越⼩, 优先级越⾼[了解]# 3. 正负混合: 正数先按照顺序执⾏, 负数最后执⾏[了解]# 注意: 控制⽅法执⾏顺序对测试类同样有效!
@pytest.mark.run(order=1)
class TestDemo2(object):"""测试类2"""def test_method(self):print('测试类2 -> 测试⽅法')
3. 失败重试插件
3.1 安装步骤
- ⽅式1 -> 安装: pip install pytest-rerunfailures
- ⽅式2 -> PyCharm 中安装
3.2 使⽤步骤
-
- 在 pytest 配置⽂件中, 增加命令选项
- 选项: --reruns 次数
-
- 执⾏ pytest 指令, 运⾏测试即可
- 执⾏ pytest 指令, 运⾏测试即可
三、pytest ⾼级功能
1. 跳过操作
- 说明: 对于完成的代码或版本不对应的代码, 可以设置跳过使之不参与测试执⾏
"""
跳过功能
"""
import pytestversion = 25 # 模拟软件版本号变量class TestDemo1(object):"""示例测试类"""def test_method1(self):"""测试⽅法"""print('测试⽅法1')# 语法: @pytest.mark.skipif(符合的条件, reason='跳过的原因')# 说明: 如果满⾜条件, 以下⽅法或测试类执⾏跳过, 不执⾏!# 注意: reason= 不能省略, 否则报错!# @pytest.mark.skipif(version >= 25, 'xxx') # 错误样例@pytest.mark.skipif(version >= 25, reason='当前版本不执⾏') #
正确样例def test_method2(self):"""测试⽅法"""print('测试⽅法2')# 说明: 同样可以跳过测试类
@pytest.mark.skipif(version >= 25, reason='当前版本不执⾏')
class TestDemo2(object):"""示例测试类2"""def test_method(self):print('测试类2 -> 测试⽅法')if __name__ == '__main__':pytest.main(['-s', 'hm10_pytest_skip.py'])
2. 参数化操作
- 说明: 与 UnitTest 框架不同, pytest 框架⾃带参数化功能, 调⽤对应⽅法并传⼊数据, 即可完成参数化实现
2.1 单个参数
"""
pytest 参数化功能: 单个参数
"""
import pytestclass TestDemo(object):"""示例测试类"""# 语法: @pytest.mark.parametrize('参数变量', ['数值1', '数值
2', ...])@pytest.mark.parametrize('name', ['⼩明', '⼩刚', '⼩红'])def test_method(self, name):"""测试⽅法"""print('获取的名字是:', name)if __name__ == '__main__':pytest.main(['-s', 'hm11_pytest_para1.py'])
2.2 多个参数
"""
pytest 参数化: 多个参数
"""
import pytestclass TestDemo(object):"""示例测试类"""# 语法: @pytest.mark.parametrize('参数1, 参数n', [(数据1-1, 数
据1-2), (数据2-1, 数据2-2), ...])# 注意:# 1. 多个参数必须置于同⼀个字符串内!# 2. 数据格式必须是: [(), ()] 或者 [[], []]# 扩展: 另⼀种写法# @pytest.mark.parametrize(('name', 'pwd'), [('admin',
123456), ('test', 654321), ('xxx', 'yyy')])@pytest.mark.parametrize('name, pwd', [('admin', 123456),
('test', 654321), ('xxx', 'yyy')])def test_method(self, name, pwd):"""测试⽅法"""print('账号:{} 密码:{}'.format(name, pwd))if __name__ == '__main__':pytest.main(['-s', 'hm12_pytest_para2.py'])
3. 扩展: 断⾔⽅法的使⽤
- 说明: 与 UnitTest 框架不同的是, pytest 框架使⽤ Python ⾃带的断⾔⽅法实现断⾔操作的
3.1 Python ⾃带的断⾔⽅法
# 语法: assert 表达式
# 说明: 断⾔后接的表达式的结果为 Ture(断⾔通过), 为 False(断⾔失败)
# 预期相等:
assert 1 == 1
# 预期包含:
assert 'admin' in '欢迎 admin 归来!'
3.2 pytest 框架中使⽤断⾔
"""
pytest 断⾔
"""
import pytestdef add_func(num1, num2):"""加法函数"""return num1 + num2class TestDemo(object):"""示例测试类"""def test_method(self):"""加法测试⽅法"""# 调⽤被测函数result = add_func(1, 2)# 断⾔判断结果assert 4 == resultif __name__ == '__main__':pytest.main(['-s', 'hm14_pytest_assert.py'])
四、PO 设计模式
1. v0 版本
- 特点: 对于同⼀个待测模块, 对应的测试代码步骤完全⼀致, 只有测试数据存在差异, 因此对于反复出现的相同逻辑的测试代码, 需要考虑通过封装的思路对⻬进⾏整理, 以提⾼测试脚本的编写效率.
- 注意: 通过直接定位元素并操作元素的⽅式实现的⾃动化脚本, 已经算是 UI ⾃动化的实现形式了!
2. 账户不存在脚本
"""
账号不存在测试⽤例
"""
from time import sleep
from selenium import webdriverdriver = webdriver.Chrome()
driver.get('http://127.0.0.1/')
driver.maximize_window() # 窗⼝最⼤化
driver.implicitly_wait(10) # 隐式等待# 1. 点击⾸⻚的‘登录’链接,进⼊登录⻚⾯
driver.find_element_by_link_text('登录').click()# 2. 输⼊⼀个不存在的⽤户名
driver.find_element_by_id('username').send_keys('13811110001')
# 3. 输⼊密码
driver.find_element_by_id('password').send_keys('123456')
# 4. 输⼊验证码
driver.find_element_by_id('verify_code').send_keys('8888')
# 5. 点击登录按钮
driver.find_element_by_name('sbtbutton').click()# 6. 获取错误提示信息
# 获取元素⽂本值: 元素对象.text
msg = driver.find_element_by_class_name('layui-layercontent').text
print('错误信息为:',
msg)sleep(3)
driver.quit()
3. 密码错误脚本
"""
密码错误测试⽤例
"""
from time import sleep
from selenium import webdriverdriver = webdriver.Chrome()
driver.get('http://127.0.0.1/')
driver.maximize_window() # 窗⼝最⼤化
driver.implicitly_wait(10) # 隐式等待# 1. 点击⾸⻚的‘登录’链接,进⼊登录⻚⾯
driver.find_element_by_link_text('登录').click()# 2. 输⼊⽤户名
driver.find_element_by_id('username').send_keys('13800001111')
# 3. 输⼊错误密码
driver.find_element_by_id('password').send_keys('error')
# 4. 输⼊验证码
driver.find_element_by_id('verify_code').send_keys('8888')
# 5. 点击登录按钮
driver.find_element_by_name('sbtbutton').click()# 6. 获取错误提示信息
# 获取元素⽂本值: 元素对象.text
msg = driver.find_element_by_class_name('layui-layercontent').text
print('错误信息为:',
msg)sleep(3)
driver.quit()