1.pytest命名规则
1. * 文件 test_开头或者_test结尾
2. * 类 Test_开头
3. * 方法/函数 test_开头
2.类级别的用例示例
class TestXXX:
"""
测试类
"""
def setup(self):
"""
资源准备
:return:
"""
pass
def teardown(self):
"""
资源销毁
:return:
"""
pass
def test_xxx(self):
"""
:return:
"""
assert self.ActualResult == self.ExpectedResult
3.用例断言
def test_a():
assert True
def test_b():
a = 1
b = 1
c = 2
assert a + b == c, f"{a}+{b}=={c}, 结果为真"
def test_c():
a = 1
b = 1
c = 2
assert 'abc' in "abcd"
import sys
def test_plat():
assert ('linux' in sys.platform), "该代码只能在 Linux 下执行"
4.pytest测试框架结构
setup_module/teardown_module 全局模块级,模块中只运行一次
setup_class/teardown_class 类级,只在类中前后运行一次
setup_function/teardown_function 函数级,在类外,每个函数执行前后
setup_method/teardown_method 方法级,类中的每个方法执行前后(与setup/teardown作用一致)
setup/teardown 在类中,运行调用方法的前后(重点)
5.pytest参数化用例
**参数化:单参数**
search_list = ['appium','selenium','pytest']
@pytest.mark.parametrize('name', ['appium','selenium'])
def test_search(name):
assert name in search_list
**参数化:多参数**
# 数据放在元组中
@pytest.mark.parametrize("test_input,expected",[("3+5",8),("2+5",7),("7+5",12)])
def test_mark_more(test_input,expected):
assert eval(test_input) == expected
# 数据放在列表中
@pytest.mark.parametrize("test_input,expected",[["3+5",8],["2+5",7],["7+5",12]])
def test_mark_more(test_input,expected):
assert eval(test_input) == expected
***参数化:笛卡尔积***
两组数据
a=[1,2,3]
b=[a,b,c]
对应有几种组合形势 ?
(1,a),(1,b),(1,c)
(2,a),(2,b),(2,c)
(3,a),(3,b),(3,c)
@pytest.mark.parametrize("b",["a","b","c"])
@pytest.mark.parametrize("a",[1,2,3])
def test_param1(a,b):
print(f"笛卡积形式的参数化中 a={a} , b={b}")
结果:
1.笛卡积形式的参数化中a=1,b=a
2.笛卡积形式的参数化中a=1,b=b
3.笛卡积形式的参数化中a=1,b=c
4.笛卡积形式的参数化中a=2,b=a
5.笛卡积形式的参数化中a=2,b=b
6.笛卡积形式的参数化中a=2,b=c
7.笛卡积形式的参数化中a=3,b=a
8.笛卡积形式的参数化中a=3,b=b
9.笛卡积形式的参数化中a=3,b=c
***用例重命名***
**通过ids参数,将别名放在列表中**
@pytest.mark.parametrize("test_input,expected",[("3+5",8),("2+5",7),("7+5",12)],
ids=['add_3+5=8','add_2+5=7','3和5相加等于12'])
def test_mark_more(test_input,expected):
assert eval(test_input) == expected
# 别名需要显示中文时,创建conftest.py 文件 ,将下面内容添加进去,运行脚本
def pytest_collection_modifyitems(items):
"""
测试用例收集完成时,将收集到的用例名name和用例标识nodeid的中文信息显示在控制台上
"""
for i in items:
i.name=i.name.encode("utf-8").decode("unicode_escape")
i._nodeid=i.nodeid.encode("utf-8").decode("unicode_escape")
6.Mark:标记测试用例
* 场景:只执行符合要求的某一部分用例 可以把一个web项目划分多个模块,然后指定模块名称执行。
* 解决: 在测试用例方法上加 @pytest.mark.标签名
* 执行: -m 执行自定义标记的相关用例
* `pytest -s test_mark_zi_09.py -m=webtest`
* `pytest -s test_mark_zi_09.py -m apptest`
* `pytest -s test_mark_zi_09.py -m "not ios"`
示例:
@pytest.mark.webtest
def test_mark_more():
print('123')
注意,需要添加pytest.ini文件,否则执行会有警告
文件内容
[pytest]
markers = webtest
apptest
ios
7.pytest 设置跳过、预期失败
## Mark:跳过(Skip)及预期失败(xFail)
* 这是 pytest 的内置标签,可以处理一些特殊的测试用例,不能成功的测试用例
* skip - 始终跳过该测试用例
* skipif - 遇到特定情况跳过该测试用例
* xfail - 遇到特定情况,产生一个“期望失败”输出
## Skip 使用场景
* 调试时不想运行这个用例
* 标记无法在某些平台上运行的测试功能
* 在某些版本中执行,其他版本中跳过
* 比如:当前的外部资源不可用时跳过
* 如果测试数据是从数据库中取到的,
* 连接数据库的功能如果返回结果未成功就跳过,因为执行也都报错
* 解决 1:添加装饰器
* `@pytest.mark.skip`
* `@pytest.mark.skipif`
* 解决 2:代码中添加跳过代码
* `pytest.skip(reason)`
示例
#执行时跳过该用例
pytest.mark.skip
def test_mark_more():
print('123')
#如果是在windows平台,则执行时跳过该用例
pytest.mark.skipif(sys.platform == 'win', reason='does not run on windows')
def test_mark_more():
print('123')
#在方法中跳过某些步骤
#如果是在windows平台,则执行时跳过if后面的语句,只打印start,不打印end
def test_function():
print('start')
if sys.platform == 'win':
pytest.skip('does not run on windows')
print('end')
## xfail 使用场景
* 与 skip 类似 ,预期结果为 fail ,标记用例为 fail
* 用法:添加装饰器`@pytest.mark.xfail`
pytest.mark.xfail
def test_mark_more():
print('123')
输出结果为xfail,没有实质性的作用,用于标记一些功能未完成,或者其他原因引起失败的用例。也可以标记xpass(用于区分开这些特殊案例和真正失败、通过的案例)
8.pytest运行多条用例
##运行多条用例
* 执行包下所有的用例:`pytest/py.test [包名]`
* 执行单独一个 pytest 模块:`pytest 文件名.py`
* 运行某个模块里面某个类:`pytest 文件名.py::类名`
* 运行某个模块里面某个类里面的方法:`pytest 文件名.py::类名::方法名`
##运行结果分析
* 常用的:fail/error/pass
* 特殊的结果:warning/deselect(后面会讲)
9.pytest测试用例调度与运行
## 命令行参数-使用缓存状态
* `--lf(--last-failed)` 只重新运行故障。
* `--ff(--failed-first)` 先运行故障然后再运行其余的测试
示例
#-vs打印详细信息
pytest --lf -vs
pytest --ff -vs
10.pytest命令行常用参数
—help
-x 用例一旦失败(fail/error),就立刻停止执行
--maxfail=num 用例达到
-m 标记用例(后面详细介绍)
-k 执行包含某个关键字的测试用例
-v 打印详细日志
-s 打印输出日志(一般-vs一块儿使用)
—collect-only(测试平台,pytest 自动导入功能 )
示例
pytest —collect-only(只收集所以用例,不执行)
pytest a.py -x
pytest a.py -k "str"(要用双引号)
pytest a.py -vs
pytest a.py --maxfail=3
11. Python 代码执行 pytest
* 使用 main 函数
if __name__ == '__main__':
# 1、运行当前目录下所有符合规则的用例,包括子目录(test_*.py 和 *_test.py)
pytest.main()
# 2、运行test_mark1.py::test_dkej模块中的某一条用例
pytest.main(['test_mark1.py::test_dkej','-vs'])
# 3、运行某个 标签
pytest.main(['test_mark1.py','-vs','-m','dkej'])
运行方式
`python test_*.py `
* 使用 python -m pytest 调用 pytest(jenkins 持续集成用到)
python -m pytest test_mark1.py
12.pytest异常处理
## 常用的异常处理方法
* try…except
* pytest.raises()
## 异常处理方法 try …except
try:
可能产生异常的代码块
except [ (Error1, Error2, ... ) [as e] ]:
处理异常的代码块1
except [ (Error3, Error4, ... ) [as e] ]:
处理异常的代码块2
except [Exception]:
处理其它异常
## 异常处理方法 pytest.raise()
* 可以捕获特定的异常
* 获取捕获的异常的细节(异常类型,异常信息)
* 发生异常,后面的代码将不会被执行
使用场景:正常的异常提示,比如计算器输入非法数字时(查验提示是否正确)
def test_raise():
with pytest.raises(ValueError, match='must be 0 or None'):
raise ValueError("value must be 0 or None")
def test_raise1():
with pytest.raises(ValueError) as exc_info:
raise ValueError("value must be 42")
assert exc_info.type is ValueError
assert exc_info.value.args[0] == "value must be 42"
13. Pytest 结合数据驱动 YAML
##yaml文件介绍
* 对象:键值对的集合,用冒号 “:” 表示
* 数组:一组按次序排列的值,前加 “-”
* 纯量:单个的、不可再分的值
* 字符串
* 布尔值
* 整数
* 浮点数
* Null
* 时间
* 日期
# 编程语言
languages:
- PHP
- Java
- Python
{languages:[PHP, Java, Python]}
book:
Python入门: # 书籍名称
price: 55.5
author: Lily
available: True
repertory: 20
date: 2018-02-17
Java入门:
price: 60
author: Lily
available: False
repertory: Null
date: 2018-05-11
{book:[{
python入门:[{
price:60
},
{
author: Lily
}
]
},
{
java入门:[{
price:55.5
},
{
author: Lily
}
]
}
]
}
##yaml文件使用
* 查看 yaml 文件
* pycharm
* txt 记事本
* 读取 yaml 文件
* 安装:`pip install pyyaml`
* 方法:`yaml.safe_load(f)`配置文件读取,返回python字典
* 方法:`yaml.safe_dump(f)`返回python生成器对象
import yaml
file_path = './my.yaml'
with open(file_path, 'r', encoding='utf-8') as f:
data = yaml.safe_load(f)
## 工程目录结构
* data 目录:存放 yaml 数据文件
* func 目录:存放被测函数文件
* testcase 目录:存放测试用例文件
.
├── data
│ └── data.yaml
├── func
│ ├── __init__.py
│ └── operation.py
└── testcase
├── __init__.py
└── test_add.py
##测试准备
* 被测对象:`operation.py`
* 测试用例:`test_add.py`
* 测试数据:`data.yaml`
# operation.py 文件内容
def my_add(x, y):
result = x + y
return result
# test_add.py 文件内容
class TestWithYAML:
@pytest.mark.parametrize('x,y,expected', [[1, 1, 2]])
def test_add(self, x, y, expected):
assert my_add(int(x), int(y)) == int(expected)
# data.yaml 文件内容
-
- 1
- 1
- 2
-
- 3
- 6
- 9
-
- 100
- 200
- 300
[[1,1,2], [3,6,9], [100,200,300]]
## Pytest 数据驱动结合 yaml 文件
# 读取yaml文件
def get_yaml():
"""
获取json数据
:return: 返回数据的结构:[[1, 1, 2], [3, 6, 9], [100, 200, 300]]
"""
with open('../datas/data.yaml', 'r') as f:
data = yaml.safe_load(f)
return data
14.Pytest 结合数据驱动 Excel
## 读取 Excel 文件
* 第三方库
* `xlrd`
* `xlwings`
* `pandas`
* openpyxl
* 官方文档: https://openpyxl.readthedocs.io/en/stable/
## openpyxl 库的操作
import openpyxl
# 获取工作簿
book = openpyxl.load_workbook('../data/params.xlsx')
# 读取工作表
sheet = book.active
# 读取单个单元格
cell_a1 = sheet['A1']
cell_a3 = sheet.cell(column=1, row=3) # A3
# 读取多个连续单元格
cells = sheet["A1":"C3"]
# 获取单元格的值
cell_a1.value
## Pytest 数据驱动结合 Excel 文件
# 读取Excel文件
import openpyxl
import pytest
def get_excel():
# 获取工作簿
book = openpyxl.load_workbook('../data/params.xlsx')
# 获取活动行(非空白的)
sheet = book.active
# 提取数据,格式:[[1, 2, 3], [3, 6, 9], [100, 200, 300]]
# 先获取第一行的数据,再获取第二行的数据,以此类推
values = []
for row in sheet:
line = []
for cell in row:
line.append(cell.value)
values.append(line)
return values
15.Pytest 结合数据驱动 csv
## csv 文件使用
* 读取数据
* 内置函数:`open()`
* 内置模块:`csv`
* 方法:`csv.reader(iterable)`
* 参数:iterable ,文件或列表对象
* 返回:迭代器,每次迭代会返回一行数据。
# 读取csv文件内容
def get_csv():
with open('demo.csv', 'r') as file:
raw = csv.reader(file)
for line in raw:
print(line)
## Pytest 数据驱动结合 csv 文件
# 读取 data目录下的 params.csv 文件
import csv
def get_csv():
"""
获取csv数据
:return: 返回数据的结构:[[1, 1, 2], [3, 6, 9], [100, 200, 300]]
"""
with open('../data/params.csv', 'r') as file:
raw = csv.reader(file)
data = []
for line in raw:
data.append(line)
return data
16.Pytest 结合数据驱动 json
## json 文件使用
* 查看 json 文件
* pycharm
* txt 记事本
* 读取 json 文件
* 内置函数 open()
* 内置库 json
* 方法:`json.loads()`
* 方法:`json.dumps()`
# 读取json文件内容
def get_json():
with open('demo.json', 'r') as f:
data = json.loads(f.read())
print(data)
## Pytest 数据驱动结合 json 文件
# params.json 文件内容
{
"case1": [1, 1, 2],
"case2": [3, 6, 9],
"case3": [100, 200, 300]
}
# 读取json文件
def get_json():
"""
获取json数据
:return: 返回数据的结构:[[1, 1, 2], [3, 6, 9], [100, 200, 300]]
"""
with open('../data/params.json', 'r') as f:
data = json.loads(f.read())
return list(data.values())
17. Fixture 用法
## Fixture 特点及优势
* 1、命令灵活:对于 setup,teardown,可以不起这两个名字
* 2、数据共享:在 conftest.py 配置⾥写⽅法可以实现数据共享,不需要 import 导⼊。可以跨⽂件共享
* 3、scope 的层次及神奇的 **yield** 组合相当于各种 setup 和 teardown
* 4、实现参数化
## Fixture 在自动化中的应用- 基本用法
* **场景:**
测试⽤例执⾏时,有的⽤例需要登陆才能执⾏,有些⽤例不需要登陆。
setup 和 teardown ⽆法满⾜。fixture 可以。默认 scope(范围)function
* **步骤:**
* 1.导⼊ pytest
* 2.在登陆的函数上⾯加@pytest.fixture()
* 3.在要使⽤的测试⽅法中传⼊(登陆函数名称),就先登陆
* 4.不传⼊的就不登陆直接执⾏测试⽅法。
示例
@pytest.fixture()
def login():
print("完成登录")
def test_search():
print("搜索")
def test_order(login):
print("下单")