软件测试所有内容笔记正在陆续更新中,笔记已经在本地记录,全部为自己手动记录的笔记及总结,正在开始更新中,后续会逐步更新并完善到
软件测试学习内容总结
专栏。
本节内容为:Python编程语言与测试框架
课程价值
理解 pytest 框架结构
掌握运行及常用的运行参数
掌握参数化与数据驱动
大纲
pytest 介绍与安装
pytest 常用执行参数
pytest 框架结构
pytest 参数化与数据驱动
pytest 介绍
pytest 安装
pip install pytest
pytest --version
Pycharm 配置
编写用例
calc.py文件
# 计算器
class Calculator:
# 加法
def add(self, a, b):
return a + b
# 减法
def sub(self, a, b):
return a - b
# 乘法
def mul(self, a, b):
return a * b
# 除法
def div(self, a, b):
return a / b
test_calc.py 文件
# python_pytest包名
from python_pytest.calc import Calculator
class TestCalc:
def test_add(self):
# 实例化计算器类
calc = Calculator()
# 调用 add 方法
result = self.calc.add(1, 1)
# 得到结果之后,要有断言
assert result == 2
pytest 规则
pytest 运行方式
pytest 文件名::测试类名::测试方法名
pytest -vs
pytest --collect-only
pytest --junitxml=./result.xml
测试用例的识别与运行
pytest
pytest -v
(最高级别信息 --verbose) 打印详细运行日志信息pytest -v -s 文件名
(s是带控制台输出结果,也是输出详细)-s打印print的内容pytest 文件名.py
( 执行单独一个pytest模块)pytest 文件名.py::类名
(运行某个模块里面某个类)pytest 文件名.py::类名::文件名
(运行某个模块里面某个类里面的方法)测试用例常用参数
pytest -k
“类名 and not 方法名” (执行某个关键字的用例)pytest -m [标记名] @pytest.mark.[标记名]
(将运行有这个标记的测试用例)常用的执行参数
pytest --collect-only
(只收集用例)pytest --junitxml=./result.xml
(生成执行结果文件)pytest --setup-show
(回溯 fixture 的执行过程)test_a 函数名、TestCalc 类名、test_add 方法名
pytest test_calc.py::TestCalc -vs
pytest test_calc.py::TestCalc::test_add -vs
pytest test_calc.py::test_a -vs
pytest --collect-only
pytest test_calc.py --junitxml=./result.xml
setup / teardown默认为方法级别,setup_method / teardown_methond
Unittest:https://docs.python.org/3/library/unittest.html
test fixture:测试装置; -做准备工作,数据准备,清理的工作,测试用例之前和测试用例之后要做的事
test case:测试用例
test suite:测试套件,将测试用例组装起来
test runner:测试执行器,用来执行测试用例
类似于setup、teardown
def setup_module(self):
print('模块级别 setup')
def teardown_module(self):
print('模块级别的 teardown')
def setup_function(self):
print('函数级别 setup')
def teardown_function(self):
print('函数级别的 teardown')
class TestDemo:
def setup_class(self):
print('类级别 setup')
def teardown_class(self):
print('类级别的 teardown')
def setup(self):
print('方法级别 setup')
def teardown(self):
print('方法级别的 teardown')
def test_demo1(self):
print('test_demo1')
def test_demo2(self):
print('test_demo2')
class TestDemo1:
def setup(self):
print('方法级别 setup')
def teardown(self):
print('方法级别的 teardown')
def test_demo1(self):
print('test_demo1')
test_calc.py 改造
from python_pytest.calc import Calculator
class TestCalc():
def setup_class(self):
print('开始计算')
# 实例化计算器类
self.calc = Calculator()
def teardown_class(self):
print('计算结束')
def test_add(self):
result = self.calc.add(1, 1)
assert result == 2
★★★★★
self.calc = Calculator()
在setup方法中创建实例calc,但实例是局部变量,别的方法调用不到,calc的作用域只限制在setup这个方法中,所以其他的方法想要调用setup方法中的这个实例的话,要改变它的作用域,把它的作用域变得更大一些,在calc前面加上self.,把calc变为实例变量
参数化与数据驱动
列表
套列表或者元组的方式传递 eg:@pytest.mark.parametrize('a, b, expect', [(1,1,2)])
pytest 参数化
mark.parametrize 参数化
@pytest.mark.parametrize
进行参数化和数据驱动更灵活判断result是浮点数,并处理
if isinstance(result,float):
result = round(result,2)
class TestCalc():
def setup_class(self):
print('开始计算')
# 实例化计算器类
self.calc = Calculator()
def teardown_class(self):
print('计算结束')
@pytest.mark.parametrize('a, b, expect', [
(1,1,2), (0.1,0.1,0.2), (-1,-1,-2), (0.1,0.2,0.3)
], ids=['int','float','negative','round'])
def test_add(self, a, b, expect):
# 调用 add 方法
result = self.calc.add(a, b)
# 判断result是浮点数,并处理
if isinstance(result, float):
result = round(result, 2)
# 得到结果之后,要有断言
assert result == expect
用yaml文件实现
calc.yaml文件
add:
datas:
-
- 1
- 1
- 2
- - 100
- 100
- 200
- [0.1,0.2,0.3]
- [0.1,-0.2,0.3]
- [-1,-1,-2]
myid:
- 'int'
- 'bigint'
- 'float'
# - 'round'
- 'fail'
- 'negative'
test_calc.py
import yaml
import pytest
from python_pytest.calc import Calculator
with open('./datas/calc.yaml') as f:
datas = yaml.safe_load(f)['add']
add_datas = datas['datas']
print(add_datas)
myid = datas['myid']
print(myid)
class TestCalc():
def setup_class(self):
print('开始计算')
self.calc = Calculator()
def teardown_class(self):
print('计算结束')
@pytest.mark.parametrize('a, b, expect', add_datas, ids=myid)
def test_add(self, a, b, expect):
result = self.calc.add(a, b)
if isinstance(result, float):
result = round(result, 2)
assert result == expect
笛卡尔积:用两个装饰器分别传入参数
a的取值和b的取值组合的情况
a有10种可能,b有10种可能,a b组合的情况
import pytest
@pytest.mark.parametrize('a', [1, 2, 3])
@pytest.mark.parametrize('b', [4, 5, 6])
def test_param(a, b):
print(f'a = {a}, b = {b}')
作业
代码 https://github.com/ceshiren/HogwartsFIS03
课程价值
掌握 pytest fixture 用法
掌握 pytest 常用插件
掌握 allure
了解 pytest hook 函数
大纲
pytest fixture 高级用法
conftest.py 用法
pytest 配置
pytest 常用插件
allure 生成测试报告
pytest hook 函数
allure 下载: https://repo1.maven.org/maven2/io/qameta/allure/allure-commandline/2.13.7/
hook 函数:https://docs.pytest.org/en/latest/_modules/_pytest/hookspec.htm
定义
@pytest.fixture()
def fixture_method():
print("setup 操作")
yield
print("teardown 操作")
调用方式
fixture方法名
@pytest.mark.usefixtures("fixture方法名")
@pytest.fixture(autouse=True)
#默认为False,不推荐使用fixture 作用域
package 新开放的功能
通过scope来使用
@pytest.fixture(scope="")
@pytest.fixture(scope=‘function’)
test_scope文件
import pytest
@pytest.fixture(scope='module') # function class module session
def connectdB():
print('连接数据库操作')
yield
print('断开数据库连接')
class TestDemo:
def test_a(self, connectdB):
print('测试用例a')
def test_b(self, connectdB):
print('测试用例b')
class TestDemo1():
def test_a(self, connectdB):
print('测试用例a')
def test_b(self, connectdB):
print('测试用例b')
fixture 方法返回值获取
fixture方法名
获取返回值fixture 的作用
fixture 的用法
函数名字
调用或使用装饰器 @pytest.mark.usefixtures('test1')
import pytest
@pytest.fixture()
def login():
print('登录操作')
# 提前登录 传入fixture方法名login
def test_case1(login):
print('测试用例1')
def test_case2():
print('测试用例2')
# 提前登录 写装饰器的方式进行调用
@pytest.mark.usefixtures('login')
def test_case4():
print('测试用例4')
yield
@pytest.fixture()
def login():
print('登录操作')
yield
print('登出操作')
生成器示例
#在生成器中想拿到里面的内容,必须调用next()方法
# yield 生成器
def provider():
# 循环读取
for i in range(10):
print('开始操作')
# 相当于 return i,记录上一次执行的位置
yield i
print('结束操作')
p = provider()
# 打印的对象类型就是生成器 generator
# 在生成器中想拿到里面的内容,必须调用next()方法
# print(p)
# print(next(p))
# print(next(p))
for i in p:
print(i)
test_fixture.py 文件
import pytest
# 创建登录的fixture 方法
@pytest.fixture()
def login():
print('登录操作')
username = 'chan'
password = '123'
token = 'token123'
yield [username, password, token]
print('登出操作')
# 提前登录
def test_case1(login):
# 通过fixture方法的名称,相当于调用了这个方法,可以拿到返回的值
print(f'login information: {login}')
print('测试用例1')
pytest --setup-show test_fixture.py -vs
yield 关键字激活了fixture中teardown的操作。
yield 把方法分为上下两部分,上面的所有操作属于setup操作,下面的所有操作属于teardown操作
有teardown的操作,就用yield返回数据,如果没有teardown操作,就把yield换成return,yield 和return都可以返回数据
生成器(generator)
只要一个方法中有yield关键字,就是一个生成器
生成器一次只能拿一个值
next() 方法拿到后面的数,一个一个往后拿
yield i 相当于return i,记录上一次执行的位置
需要fixture返回数据时,可以直接在方法体中通过使用fixture方法的名字,调用它返回的数据
@pytest.fixture(autouse=True)
# 自动调用,默认为False
和setup、teardown一样了,不推荐使用,简单了解
import pytest
@pytest.fixture(autouse=True)
def login():
print('登录操作')
# 提前登录 不许用传fixture 自动调用
def test_case1():
print('测试用例1')
conftest.py文件中有打开文件的操作时,如果报编码错误,在open中添加encoding=‘utf-8’
with open(yaml_file_path, encoding=‘utf-8’) as f:
应用场景
conftest.py 用法
pytest -vs test_fixture.py test_scope.py
conftest.py 生效遵循就近原则,离的最近的生效。如果当前文件中有相同名称的fixture方法,则调用当前文件中的。如果没有则调用同一个包中conftest中的fixture方法。同包中没有则调用外层包中的conftest中的fixture方法。
优先调用同级目录下的conftest方法。
所有文件优先调用最外面的conftest.py
conftest.py文件所在包中的所有文件和包都会执行pytest.py文件,conftest.py有问题,所有的文件都会报错。
conftest.py 文件
import pytest
import yaml
import os
from python_pytest.calc import Calculator
# 要使用绝对路径打开yaml文件,conftest.py文件所在包中的所有文件和包都会执行pytest.py文件,conftest.py有问题,所有的文件都会报错。
# os.path.dirname(__file__)获取当前文件conftest.py所在的路径
yaml_file_path = os.path.dirname(__file__) + '/datas/calc.yaml'
with open(yaml_file_path, encoding='utf-8') as f:
datas = yaml.safe_load(f)['add']
add_datas = datas['datas']
print(add_datas)
myid = datas['myid']
print(myid)
@pytest.fixture(params=add_datas, ids=myid)
def get_add_datas(request):
print('开始计算')
data = request.param
print(f'request.param 里面的测试数据:{data}')
yield data
print('结束计算')
@pytest.fixture(scope='session')
def connectdB():
print('连接数据库操作')
yield
print('断开数据库连接')
@pytest.fixture(scope='class')
def get_calc():
print('获取计算器实例')
calc = Calculator()
return calc
test_calc.py文件
import yaml
import pytest
from python_pytest.calc import Calculator
class TestCalc():
# @pytest.mark.parametrize('a, b, expect', add_datas, ids=myid)
def test_add(self, get_calc, get_add_datas):
result = None
try:
# 调用 add 方法
result = get_calc.add(get_add_datas[0], get_add_datas[1])
# 判断result是浮点数,并处理
if isinstance(result, float):
result = round(result, 2)
except Exception as e:
print(e)
assert result == get_add_datas[2]
calc.yml
add:
datas:
- [1,1,2]
- [100,100,200]
- [0.1,0.2,0.3]
- [0.1,-0.2,-0.1]
- [-1,-1,-2]
- [' ', 5, '请输入数字']
myid:
- 'int'
- 'bigint'
- 'float'
- 'fail'
- 'negative'
- '空格'
场景:fixture 带参数传递
fixture通过params=[]
来传递参数,ids=""来传递别名
使用fixture传递参数,要拿到params里面的数据,需要在fixture方法中传递参数request
使用request.param
获取params传入的值,得到的值为列表的形式
import pytest
@pytest.fixture(params=[1, 2, 3])
def login1(request):
data = request.param
print('获取测试用例')
return data
def test_case1(login1):
print(login1)
print('测试用例1')
相关练习:testing文件夹中的test_param.py,calc.yaml, conftest.py, test_calc.py文件
pytest.ini
在使用pytest.ini配置文件时,执行测试用例报gbk编码的错误。首先选中pytest.ini文件,然后点击上方的File—File Encoding—GBK—弹出对话框点击Convert(转换)
报错:UnicodeDecodeError: 'gbk' codec can't decode byte 0xa1 in position 70: illegal multibyte sequence
规则
pytest 配置文件
[pytest]
markers
自定义mark标签名
addopts
运行时的参数(可添加多个命令行参数,空格分隔,所有参数与命令行一致)
python_files
自定义测试文件命名规则
python_classes = Test*
自定义测试类命名规则
python_functions = test_* check_*
自定义测试方法命名规则
testpaths = bilibili baidu
指定特定路径运行
norecursedirs = result logs datas test_demo*
运行时忽略某些文件夹
pytest.ini 文件(文件名固定)
[pytest]
markers = add
div
sub
mul
;运行时的参数
addopts = -vs
;自定义测试文件命名规则
python_files = check_* test_*
;自定义测试类命名规则
python_classes = Check* Test*
;自定义测试方法命名规则
python_functions= test_* check_*
; 指定执行路径
testpaths = sub_demo
; 忽略路径
norecursedirs = datas
mark参数代码
class TestCalc():
@pytest.mark.div
def test_div(self):
print('test_div')
pytest test_calc.py -vs -k add
首先要在pytest.ini文件的markers
中定义相关的mark标记,然后使用-k
参数运行
check_demo.py文件
def check_demo():
print('check_demo')
class CheckDemo:
def check_a(self):
print('check_a')
python_files = check_* test_*
python_classes = Check* Test*
python_functions= test_* check_*
Windows的Pycharm在界面安装的插件在命令行不起作用,命令行使用的插件为系统配置路径中的插件,所以想在pycharm命令行执行命令,必须要在环境变量中安装插件
pytest 工作中常用的插件
https://pypi.org/
pytest-rerunfailures
运行环境要求Python版本在3.5-3.8之间 (pypi中可以查看)
pip install pytest-rerunfailures
pytest -vs --reruns 3 test_class.py
pytest -vs --reruns 5 --reruns-delay 1
@pytest.mark.flaky(reruns=5,reruns_delay=2)
指定某个用例–reruns 设置重跑次数
–reruns-delay 设置每次重跑之间的间隔时间
from time import sleep
import pytest
def test_rerun():
sleep(0.5)
assert 1 == 2
def test_rerun1():
sleep(0.5)
assert 2 == 2
@pytest.mark.flaky(reruns=4, reruns_delay=1)
def test_rerun2():
sleep(0.5)
assert 3 == 2
pytest test_rerun.py --reruns 3 --reruns-delay 1
所有测试用例都会失败重跑
在测试用例中加入装饰器时,直接使用pytest test_rerun.py运行,加了装饰器的测试用例才会失败重跑
pytest-assume
pip install pytest-assume
pytest.assume(1==4)
import pytest
def test_a():
# assert 1 == 1
# assert False == True
# assert 100 == 200
pytest.assume(1 == 1)
pytest.assume(False == True)
pytest.assume(100 == 200)
pytest.assume(300 == 1)
pytest test_assume
pytest-ordering
pip install pytest-ordering
@pytest.mark.run(order=2)
case 基本设计原则
import pytest
# @pytest.mark.last
@pytest.mark.run(order=2)
def test_foo():
assert True
# @pytest.mark.first
@pytest.mark.run(order=1)
def test_bar():
assert True
pytest-xdist
pip install pytest-xdist
pytest -n 3
pytest test_ordering.py
pytest test_ordering.py -n 3
pytest-html
pip install pytest-html
pytest -vs --html=report.html --self-contained-html
pytest-sugar
pip install pytest-sugar
使命令行的log日志更加好看,展示进度,显示对勾
allure 安装
pip install allure-pytest
allure 用法
pytest —alluredir=./result
allure serve ./result
allure generate ./result
allure generate result -o result/html --clean result/html
allure open -h 127.0.0.1 -p 8883 ./result/
-o 指定存放生成结果的文件夹,–clean 清空文件夹
pytest --alluredir test_calc.py ./result
allure generate result -o result/html --clean result/html
allure open -h 127.0.0.1 -p 8883 ./result/html
pytest 插件加载方式
自定义插件
编写自己的插件
for item in items:
pytest test_hook.py -m add
pytest test_hook.py -k add
items 所有测试用例组成的列表
item.nodeid 拿到测试用例名称
课后作业