Pytest学习笔记
本文非原创,感谢被借鉴学习的各位大佬的文章
一、Pytest简介
1.1 Pytest介绍
pytest是一个非常成熟的python单元测试框架,比unittest更灵活、更容易上手
pytest可以和selenium、requests、appinum结合实现web自动化、接口自动化、app自动化
pytest可以是实现测试用例的跳过以及reruns失败用例重试
pytest可以和aliure生成非常美观的测试报告
pytest可以和jenkins持续集成
pytest有非常强大的插件,并且这些插件能够实现很多的使用的操作
#常用安装插件
pytest
pytest-html (生成html格式的自动化测试报告)
pytest-xdist (测试用例分布式执行,多cpu分发)
pytest-ordering(用于改变测试用例的执行顺序)
allure-pytest(用于生成美观的测试报告)
pytest-rerunfailures(用例失败后重跑)
1.2 Pytest默认规则
1、模块名必须以test_开头或者_test结尾
2、测试类必须以Test开头,并且不能有__init__方法
3、测试方法必须以test开头
二、Pytest测试用例执行方式
2.1 主函数模式
1、运行所有:pytest.main()
2、指定模块:pytest.main([‘-vs’,‘test_login.py’])
3、指定目录:pytest.main([“-vs”,“testcase”])
4、通过nodeid指定用例运行:nodeid由模块名,分隔符,类名,方法名,函数名组成
pytest.main([“-vs”,“./interface_testcase/test_interface.py::Testinterface::test_03_zhiliao”])
'''
前置条件:在test_login.py下创建两条测试用例在test_pass.py下创建两条测试用例一共4条测试用例
'''
import pytest
class TestLogin:
def test_01(self):
print("测试")
if __name__ == '__main__':
pytest.main(["-vs"])# pytest.main(["-vs"]) # 将会执行所有的测试用例,4条测试用例都会执行
#pytest.main(["-vs","./test_login.py"] 只会执行test_login.py下的两条测试用例
2.2 命令行模式
1、运行所有:pytest 指定模块:pytest -vs
2、test_login.py 指定目录:pytest -vs ./interface_testcase
3、指定nodeid:pytest -vs ./interface_testcase/test_interface.py::Testinterface::test_03_zhiliao
2.3 参数详解
参数详解:
-s: 表示输出调试信息,包括print打印的信息
-v: 显示更详细的信息,包括用例所在的模块名和类名
-vs: 这两个参数可以一起使用
–reruns=2: 失败用例重跑
pytest.main(["-vs", "./web_testCase", '--reruns=2'])
-x: 表示只要有一个用例报错,那么测试停止
–maxfail=2: 表示出现两个用例失败就停止
-k: 根据测试用例的部分字符串指定测试用例
def test_01_sunwukong def test_02_xiaohong
eg: pytest -vs ./web_testCase -k 'on'
–html ./report/report.html 生成html格式的测试报告
-m: 执行指定标记的用例 eg: -m ‘smoke’ 标志只执行smoke用例
pytest -vs -m 'smoke'
pytest -vs -m 'somke or usermanager'
-n:支持多线程或者分布式运行测试用例
# 主函数模式
pytest.main(["-vs", "./web_testCase/test_login", "-n=2"])
# 命令行模式书写
pytest -vs ./web_testCase/test_login -n 2
如果有5个用例,分配两个线程来执行的话,那么第一个线程会执行1 3 5 用例,第二个线程会执行2 4 用例
2.4 通过读取pytest.ini配置文件运行(重点)
pytest.ini这个文件它是pytest单元测试框架的核心配置文件
1、位置:一般放在项目的根目录
2、编码:必须是ANSI,可以使用notepad++修改编码格式
3、作用:改变pytest默认的行为
4、 运行的规则:不管是主函数模式,还是命令行模式,都会读取这个配置文件
#文件名pytest.ini
[pytest]
#命令行参数,用空格分割
addopts = -vs
#测试用例文件夹,可自己配置
testpaths = ./testcase
#配置测试搜索的模块文件名称
python_files = test*.py
#配置测试搜索的测试类名
python_classes = Test*
#配置测试搜索的测试函数名
python_functions = test
三、Pytest执行顺序
unittest执行顺序:按照ascli的大小来执行
pytest执行顺序:默认从上到下执行
改变pytest的默认执行顺序:使用@pytest.mark.run(order=1)
'''
需求:将test_02第一个先执行
'''
import pytest
class TestLogin:
def test_01(self):
print("测试-1")
@pytest.mark.run(order=1) #.run(order=1) 要手动敲不会自动补全
def test_02(self):
print("测试-2")
四、如何分组执行
我们在测试过程中经常会有冒烟测试、分模块测试、分接口测试和web测试等等,那么如何使用pytest进行分组执行测试?
'''
在pytest.ini文件中添加markers字段
'''
[pytest]
addopts = -vs
testpaths = ./testcase
python_files = test*.py
python_classes = Test*
python_functions = test
markers =
smoke:冒烟用例
usermanage:用户管理模块
productmangage:商品管理模块
五、Pytest跳过用例
5.1 无条件跳过
无条件跳过: @pytest.mark.skip(reason"跳过")
import pytest
class TestLogin:
@pytest.mark.usermanage
@pytest.mark.skip(reason="跳过")
def test_01(self):
print("测试-1")
@pytest.mark.run(order=1)
@pytest.mark.smoke
def test_02(self):
print("测试-2")
#执行结果:
testcase/test_login.py::TestLogin::test_01 SKIPPED (跳过)
5.2 有条件跳过
有条件跳过: @pytest.mark.skipif(age=18,reason=“年龄等于18则跳过”)
import pytest
class TestLogin:
age = 18
@pytest.mark.usermanage
@pytest.mark.skip(reason="跳过")
def test_01(self):
print("测试-1")
@pytest.mark.skipif(age=18,reason="年龄等于18则跳过")
@pytest.mark.run(order=1)
@pytest.mark.smoke
def test_02(self):
print("测试-2")
#执行结果
testcase/test_login.py::TestLogin::test_02 SKIPPED (年龄等于18则跳过)
interfacecase/test_inter.py::TestPass::test_05 测试-5
PASSED
interfacecase/test_inter.py::TestPass::test_06 测试-6
PASSED
testcase/test_login.py::TestLogin::test_01 SKIPPED (跳过)
testcase/test_pass.py::TestPass::test_03 测试-3
PASSED
testcase/test_pass.py::TestPass::test_04 测试-4
PASSED======================== 4 passed, 2 skipped in 0.03s =========================
六、前后置(夹具、固件)
6.1 setup/teardown,setup_class/teardown_class实现前后置
为什么要使用前后置?
比如:web自动化执行用例之前,请问需要打开浏览器嘛?用例执行后需要关闭浏览器嘛?
'''
用法:
def setup(self):和def teardown(self): 在每个用例执行前后执行一次。
def setup_class(self): 和def teardown_class(self): 在当前类的所有用例执行前后执行一次
'''
class TestWeb:
#这个在所有的用例之前只执行一次
def setup_class(self):
print('在每个类执行前的初始化的工作:比如:创建日志对象,创建数据库的连接,创建接口的请求对象。')
#在每个用例之前执行一次。
def setup(self):
print('\n在执行测试用例之前初始化的代码:打开浏览器,加载网页')
def test_01_baili(self):
print('测试-01')
def test_02_xingyao(self):
print('测试-02')
def teardown(self):
print('\n在执行测试用例之后的扫尾的代码:关闭浏览器')
def teardown_class(self):
print('在每个类执行后的扫尾的工作:比如:销毁日志对象,销毁数据库的连接,销毁接口的请求对象。')
6.2 使用@pytest.fixture()装饰器来实现用例的前后置
@pytest.fixture(scope=“”,params=“”,autouse=“”,ids=“”,name=“”)
- scope:表示的是被@pytest.fixture标记的方法的作用域,有四个级别参数"function"(默认),“class”,“module”,“session”
- package/sessionparams:参数化autouse,一个可选的参数列表,它将导致多个参数调用fixture功能和所有测试使用它:默认False
- autouser:自动执行。如果True,则为所有测试激活fixture func可以看到它;如果为False则显示需要参考来激活fixture
- ids:当使用params参数化时,给每一个值设置一个变量,意义不大。每个字符串id的列表,每个字符串对应于params这样他们就是测试ID的一部分。如果没有提供ID它们将从params自动生成
- name:表示的是被@pytest.fixture标记的方法取一个别名。这默认为装饰函数的名称。如果fixture在定义它的统一模块中使用,夹具的功能名称将被请求夹具的功能arg遮蔽,解决这个问题的一种方法时将装饰函数命令"fixture_“然后使用”@pytest.fixture(name=‘’)"。
参数作用示例移步 https://www.cnblogs.com/huizaia/p/10331469.html
import pytest
class TestLogin:
age = 18
#部分用例前置,后置,yield表示后置
@pytest.fixture()
def my_fixtre(self):
print("前置")
yield
print("后置")
#将my_fixture函数传入,将此用例进行前后置
def test_01(self,my_fixtre):
print("测试-1")
def test_02(self):
print("测试-2")
#执行结果:
test_login.py::TestLogin::test_02 测试-2
PASSED
test_login.py::TestLogin::test_01 前置
测试-1
PASSED后置
参数化:(一般不用,了解)
注:写在最外层,不在类中填写,函数名中传入request是注定写法,request.param 返回参数也是固定写法
import pytest
#函数名中传入request是固定写法,request.param 返回参数也是固定写法
@pytest.fixture(scope="function", params=["成龙", "甄子丹", "蔡依林"])
def my_fixtre(request):
yield request.param #retuen和yield都表示返回的意思,但是yield后面可以跟代码
print("前置")
class TestLogin:
age = 18
@pytest.mark.usermanage
def test_01(self,my_fixtre):
print("测试-1")
print(my_fixtre)
@pytest.mark.run(order=1)
@pytest.mark.smoke
def test_02(self):
print("测试-2")
#执行结果:
test_login.py::TestLogin::test_02 测试-2
PASSED
test_login.py::TestLogin::test_01[\u6210\u9f99] 测试-1
成龙
PASSED
前置test_login.py::TestLogin::test_01[\u7504\u5b50\u4e39] 测试-1
甄子丹
PASSED
前置test_login.py::TestLogin::test_01[\u8521\u4f9d\u6797] 测试-1
蔡依林
PASSED
前置test_pass.py::TestPass::test_03 测试-3
PASSED
test_pass.py::TestPass::test_04 测试-4
PASSED
6.3 通过conftest.py和@pytest.fixture()结合使用实现全局的前后置应用
使用规则:
conftest.py文件是单独存放的一个夹具配置文件,名称不能更改
用处可以在不同的py文件中使用同一个fixture函数
原则上conftest.py需要和运行的测试用例放到同一层级,并且不需要做任何的import导入的操作
- 全局的conftest.py文件,在最外层
- 内部的conftest.py文件
用例调用
七、@pytest.mark.parametrize—参数化
@pytest.mark.parametrize(args_name,args_value)
args_name:参数名
args_value:参数值(列表,元祖,字典列表,字典元祖) 有多少个值用例就会执行多少次
import pytest
class TestLogin:
age = 18
@pytest.mark.parametrize("args", ["老付", "小刘", "小孟"])
def test_01(self, args):
print("测试-1")
print(args)
def test_02(self):
print("测试-2")
if __name__ == '__main__':
pytest.main()
#执行结果
test_login.py::TestLogin::test_01[\u8001\u4ed8] 测试-1
老付
PASSED
test_login.py::TestLogin::test_01[\u5c0f\u5218] 测试-1
小刘
PASSED
test_login.py::TestLogin::test_01[\u5c0f\u5b5f] 测试-1
小孟
PASSED
test_login.py::TestLogin::test_02 测试-2
PASSED
八、YAML文件详解–实现接口自动化
8.1 YAML介绍
8.2 YAML编写测试用例常用关键字
feature: 模块名(必填)
story: 接口名(必填)
title: 用例标题(必填)
request:请求(必填)method:请求方法(必填)url:请求路径(必填)headers:请求头params:url之后的参数data:表单数据json:json数据files:文件上传
extract:access_token: $.access_token 接口关联提取
vaildate:断言(必填)codes:断言状态码equals:相等断言contains:包含断言db_equals: 数据断言
8.3 示例
#test_api.yaml文件
-name: 获取token鉴权码的接口
request:
url: https://www.baidu.com/
method: get
headers:
Content-type: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36
vaildate:eq: 200
#yaml.util.py文件import yaml
class YamlApi:
def __init__(self, yaml_file):
"""通过init方法把yaml文件传入到这个类:param yaml_file: yaml文件路径"""
self.yaml_file = yaml_file
def write_yaml(self):"""读取yaml,对yaml反序列化,就是把我们的yamk格式转成dict格式:return:"""
with open(self.yaml_file, encoding="utf-8") as f:
value = yaml.load(f, Loader=yaml.FullLoader)
print(value, type(value))
return value
if __name__ == '__main__':
YamlApi("./test_api.yaml").write_yaml()
#test_api.py 文件
import pytest
import requests
from testcase.yaml_util import YamlApi
class TestApi:
@pytest.mark.parametrize("args", YamlApi("./test_api.yaml").write_yaml())
def test_10_baidu(self, args):
print(args)
url = args["request"]["url"]
code = args["vaildate"]["eq"]
res = requests.get(url)
code1 = res.status_codeprint(code1)
assert code == code1
if __name__ == '__main__':
pytest.main(["-vs", "./test_api.py"])
# 执行结果
test_api.py::TestApi::test_10_baidu[args0] PASSED
============================== 1 passed in 0.46s ==============================