介绍
- pytest是基于unittest开发的另一款更高级更好用的单元测试框架
- 支持参数化
- 执行测试过程中可以将某些测试跳过(skip),或者对某些预期失败的case标记成失败
- 支持运行由 nose, unittest 编写的测试 case
- 方便的和持续集成工具 jenkins 集成
- 具有很多第三方插件,并且可以自定义扩展
- 可支持执行部分用例
- 支持失败重跑功能
安装
- pytest是第三方库,需要安装后使用
- 在命令行输入以下任意命令即可安装,使用第三方镜像源可加快下载速度
# 豆瓣源
pip install pytest -U -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com
# 清华源
pip install pytest -U -i https://pypi.tuna.tsinghua.edu.cn/simple/ --trusted-host pypi.tuna.tsinghua.edu.cn
命名规范
- 用例文件应当以 test_*.py 进行命名,或者以 *_test.py命名
- 类必须以 Test 开头,且类当中不能有__init__方法
- 方法或函数必须以 test_ 开头
- 可以在项目根目录建pytest.ini文件,在里面自定义用例匹配规则
# 用例匹配规则
[pytest]
python_files = test_*.py *_test.py
python_classes = Test*
python_functions = test_*
断言
- 断言必须使用assert,语法:assert 表达式
- 测试的结果, .表示成功 F表示失败,如果测试失败,会显示具体的语句
assert 1==2 #判断等式两边是否相等
assert 200 #判断某个语句是否为真
assert 10 in [10,20] #判断某个值是否属于某个对象
assert not True #判断某个语句是否不为真
assert 1!=2 #判断某个值是否不等于另一个值
初始化和清除
def setup_module():
print('\n *** 模块初始化开始 ***')
pass # 这里写初始化代码
print('\n *** 模块初始化结束 ***')
def teardown_module():
print('\n *** 模块清除开始 ***')
pass # 这里写清除代码
print('\n *** 模块清除结束 ***')
class Test_A:
@classmethod
def setup_class(cls):
print(f'\n === {cls.__name__}类初始化 ===')
@classmethod
def teardown_class(cls):
print(f'\n === {cls.__name__}类清除 ===')
def setup_method(self):
print('\n --- 类方法初始化 ---')
def teardown_method(self):
print('\n --- 类方法清除 ---')
# 在类外面自定义一个方法,名字随意,但是要带上pytest的装饰器
@pytest.fixture(scope='function')
def custom_setup():
print(f'\n --- 自定义方法初始化 ---')
pass # 在这写要初始化的代码
yield custom_setup # 初始化函数名(不要带括号)
custom_teardown() # 清除函数
# 同时要自定义一个清除函数,名字随意
def custom_teardown():
print(f'\n --- 自定义方法清除 ---')
pass # 在这写清除的代码
# 如果有个方法需要调用自定义的初始化和清除函数,只需要把初始化函数传入即可
def test_abc(custom_setup):
pass
- 执行顺序(以类中的一个方法为例):模块初始化→类初始化→类方法初始化→自定义方法初始化→自定义方法清除→类方法清除→类清除→模块清除
- 类方法的初始化和清除会作用于类里的每一个函数
- 自定义方法的初始化和清除只会对传入该方法名的函数生效
参数化
- 使用@pytest.mark.parametrize(参数名, 参数值列表)
# 传入多个参数时,参数值列表中,每一组数据的类型都是元组
@pytest.mark.parametrize("val_a,val_b", [(1, 2), (3, 4), (5, 6)])
def test_parametrize_1(val_a,val_b):
print(f"变量val_a的值:{val_a},变量val_b的值:{val_b}")
# 可以使用多个参数化装饰器,测试数据为不同数据的笛卡尔积,实际会生成m*n条用例(下面的代码生成3*4=12条用例)
@pytest.mark.parametrize('a', [1,2,3])
@pytest.mark.parametrize('b', [4,5,6,7])
def test_parametrize_2(a, b):
print(f'笛卡尔积 测试数据为:{a},{b}')
mark标记用例
- pytest支持案例标记的功能,可以给每个用例加上自定义标记,在执行测试案例时通过标记筛选要执行的案例
- 使用标记方法
- 在 pytst.ini 中添加标记名称
- 在需要标记的函数上使用装饰器 @pytest.mark.标记名
- pytst.ini :pytest的配置文件
# 在pytst.ini文件的标记名列表中添加自定义的标记
[pytest]
markers =
smoke
test
hello
# 在需要标记的函数上增加装饰器(以smoke,hello为例)
@pytest.mark.smoke
def test_001(self):
print('我是一个冒烟测试用例')
@pytest.mark.hello
def test_002(self):
print('我是一个属于hello标记的测试用例')
执行用例
- '-k 标记名' ==>可选择执行该标记所包含的用例
- '-s' ==>在控制台输出执行过程中的print内容
- '-q' ==>打印用例执行的简略过程
- '-v' ==>打印用例执行的详细过程
- '-x' ==>用例运行失败则立即停止执行
- '--maxfail=num' ==>用例运行时允许的最大失败次数,超过num次则立即停止
- '-l' ==>用例运行失败时,打印相关的局部变量
- 不输入参数默认执行该当前目录及子目录中的所有测试用例
- pytest执行案例时使用pytest.main()函数,参数传入一个列表
# 执行所有标记为smoke的用例,显示详细过程,并打印print输出
pytest.main(['-k','smoke','-sv'])
- 如果执行过程中出现了Module already imported so cannot be rewritten的警告,那么可以用子进程的方式来执行用例,就不会弹出这种警告了
# 导入子进程模块,避免运行时弹出警告
import subprocess
# 列表中第一个元素为pytest,后面的元素可以输入各种参数
subprocess.call(['pytest', '-k smoke', '-s'])
# 运行指定目录下用例
subprocess.call(['pytest', './test'])
# 运行指定模块
subprocess.call(['pytest', './test/test_demo.py'])
# 运行模块中的指定用例
subprocess.call(['pytest', './test/test_demo.py::test_001'])
# 运行类中的指定用例
subprocess.call(['pytest', './test/test_demo.py::TestDemo::test_001'])
# 运行test_demo.py模块下名称包含hello的用例
subprocess.call(['pytest', '-k hello','./test/test_demo.py'])
# 运行TestDemo类中名称包含aa的用例
subprocess.call(['pytest', '-k aa','./test/test_demo.py::TestDemo'])
allure安装
allure装饰器
- 根据实际需要对用例使用allure装饰器,可以提升allure报告的可读性
- 几种常用的装饰器:
@allure.epic('层级1')
@allure.feature('层级2')
@allure.story('层级3')
@allure.title('用例标题')
@allure.description('用例描述')
allure报告
- allure生成报告分为两步:
- 第一步先执行用例并生成测试数据(一大堆json文件)
- 根据某次执行用例后生成的测试数据,通过allure generate命令来生成测试报告(包含了index.html,可在浏览器打开查看)
- 生成allure测试数据
# 这里我是在每次执行的时候,将当天的测试数据和测试报告按日期+时间的双层目录来存放
date = time.strftime('%Y%m%d', time.localtime()) # 当前日期
time = time.strftime('%H%M%S', time.localtime()) # 当前时间
# allure命令要在命令行使用,在python中可通过os.system来调用
os.system(f'pytest --alluredir ./result/{date}/{time}/') # 生成测试结果
# 根据测试结果生成测试报告
# 加上--clean参数可以先清除数据,但由于我是按时间存放的,所以每次生成的目录都在不同位置,这里就不clean了,自己调试代码测试的时候可以用上clean
os.system(f'allure generate ./result/{date}/{time}/ -o ./report/{date}/{time}/')
- 手动打开测试报告:在生成的测试报告文件夹里有index.html文件,直接在浏览器打开即可查看
- 也可以起一个服务,会自动打开allure测试报告
- 注意:使用这个命令后会在本地起一个服务,如果打开的网页是空白的(可能存在网络限制等各种原因),那么可以将ip换成 127.0.0.1:(启动服务后命令行显示的端口号) 试下能不能正常访问
# 自动打开测试报告,将启动后的服务ip换成 127.0.0.1
os.system(f'allure serve ./result/{date}/{time}/')