一、pytest基础
1、什么是单元测试框架
单元测试指的是在软件开发测试中,针对软件的最小单位(函数、方法)进行正确性的检查测试。
2、单元测试框架主要做什么?
- 测试发现:从多个文件里面去找到我们测试用例(如何找?:按照一定的命名规则)
- 测试执行:按照一定的顺序和规则去执行。并生成结果
- 测试判断:通过断言判断预期结果和实际结果的差异
- 测试报告:统计测试进度,耗时,通过率,生成测试
3、单元测试框架和自动化测试框架的关系
单元测试框架:只是目动化测试框架中的组成部分之一
pom设计模式:只是目动化测试框架中的组成部分之一。
除此以外,自动化测试框架还包括:
数据驱动
关键字驱动
全局配置文件的封装
日志监控
selenium,requests二次封装
断言
报告邮件
……
4、pytest简介
- pytest是一个非常成熟的python的单元框架,比unittest更灵活,容易上手
- pytest可以和selenium,requests,appium结合实现web自动化,接口自动化,app自动化.
- pytest可以实现测试用例的跳过以及reruns失败用例重试。
- pytest可以和allure生成非常美观的测试报告。
- pytest可以和Jenkins持续集成。
- pytest有很多非常强大的插件,并且这些插件能够实现很多的实用的操作。
- pytest-html(生成html格式的自动化测试报告)
- pytest-xdist 测试用例分布式执行。多CPU分发
- pytest-ordering 用于改变测试用例的执行顺序
- pytest-rerunfailures用例失败后重跑
- allure-pytest 用于生成美观的测试报告
python批量安装插件:
创建
requirements.txt
文件,在该文件中输入想要安装的插件名称使用pip指令:
pip install -r requirements.txt
5、pytest测试用例的默认发现规则
- py文件必须以
test_
开头或者以_test
结尾 - 需要被测试的类必须以
Test
开头,并且不能有__init__()
方法 - 需要被测试的方法必须以
test
开头
只是默认的规则,可以在pytest.ini配置文件中自定义规则
6、pytest测试用例的启动方式(常见)
-
主函数模式
if __name__ == '__main__': pytest.main() #运行所有测试用例,不管是不是本py文件中的用例
pytest.main()
后面可以跟参数:-
-s:表示输出调试信息,包括print打印的信息
-
-v:显示更详细的信息(两个参数可以一起用)
-
跟某个.py文件名/文件夹路径/nodeid:只启动指定的py文件/指定文件夹下的py文件/制定方法或函数
nodeid:py文件路径::函数名 py文件路径名::类名::方法名
-
-n: 用多线程运行测试用例(线程数目可以自行设置)
-
–reruns=n:运行失败的用例会再重试n次
-
-x:若有用例测试失败,则停止整个测试流程。
-
–maxfail=n:出现n个失败用例就停止整个流程。
-
-k “str”:只运行名称包含”str“的测试方法。
-
–html ./report (报告文件的保存路径):生成测试报告
if __name__ == '__main__': pytest.main(['-vs','test_login.py']) #注意参数列表要加[]
if __name__ == '__main__': pytest.main(['-vs','./interface_testcase'])
if __name__ == '__main__': pytest.main(['-vs','./interface_testcase.test_interface::test_demo_fun'])
-
-
命令行模式
直接在终端中运行
pytest
命令,会运行所有测试用例。与主函数模式一样,可以添加参数
pytest -vs test_login.py
pytest -vs ./test_case -n 2 //设置了两个线程
-
通过pytest.ini配置文件运行(实际工作中最常用的)
位置:一般放在根目录下
编码:ANSI编码方式
作用:改变pytest的默认行为。不管是命令行模式还是主函数模式,都会去读取这个配置文件。
7、测试用例执行顺序
默认情况:从上到下
自定义设置执行顺序:利用注解@pytest.mark.run
(需要第三方库pytest-ordering
)
@pytest.mark.run(order=2)
def test_01(self):
print("第一个方法")
@pytest.mark.run(order=1)
def test_02(self):
print("第二个方法")
#运行时按照设置的order的顺序,会先执行test_02
8、pytest如何分组执行用例
现在ini配置文件中设置分组
markers =
xxx1:组1的名称/解释
xxx2:组2的名称/解释
xxx3:组3的名称/解释
……
将某个测试方法归为某个组:添加注解
@pytest.mark.xxx(组名)
def test_demo_01(self):
pass
运行指定的组的全部用例:在运行命令中添加参数 :-m “xxx(组名)” 多个组之间用or
连接
pytest -m "xxx"
pytest -m "xxx" or "yyy"
9、pytest跳过测试用例
-
无条件跳过用例
@pytest.mark.skip(reson="跳过的原因")
-
有条件跳过
@pytest,mark.skipif(条件表达式,'跳过的原因')
例如
age = 10 @pytest.mark.skipif(age < 20,"年龄不足20岁") def test_demo_01(self): pass
10、conftest.py文件
conftest.py是fixture函数的一个集合,可以理解为公共的提取出来放在一个文件里,然后供其它模块调用。不同于普通被调用的模块,conftest.py使用时不需要导入,Pytest会自动查找。
我们可以把许多fixture函数写到该文件中,这样fixture函数就不会分散到许多py文件中,方便我们管理。
二、pytest拓展
1、用例前后置处理(夹具 固件)
-
setup/teardown和setup_class/teardown_class
class Test_demo: def setup_class(self): #这个类执行之前运行的代码 def setup(self): #每个测试方法执行之前都会执行一遍的代码 def test_demo_01(self): …… def teardown(self): ##每个测试方法执行之后都会执行一遍的代码 def setup_class(self): #这个类执行之后运行的代码
-
使用@pytest.fixture()实现部分用例的前后置
fixture修饰器的参数解释:
-
scope:表示fun1的作用域,包括function(默认),class,moudle,package,session;当设置为class时,只有该类被执行时才会执行fun1.(类似于setup_class和teardown_class)。其他参数以此类推
-
params:参数化(支持列表、元组、字典列表、字典元组)
@pytest.fixture(,params=[1,2,3,5]) def fun1(request): print("这是前置函数的内容") return request.param
-
autouse:是否自动执行
-
ids:启用参数化时,为传入的数值赋一个变量名
-
name:为被#pytest.fixture修饰的函数设置一个别名
@pytest.fixture(scope='',params='',autouse='',ids='',name='') def fun1(): print("这是前置函数的内容") yield print("这是后置函数的内容") #如何使用 class Test_demo: def test_demo_01(self,fun1): …… #在函数的参数列表中加入被fixture修饰的函数名,多个前置函数之间用逗号隔开,按照顺序执行各个前置 #如果fixture修饰器中autouse设置为true,则不需要在参数列表中加入被fixture修饰的函数名,会自动执行
-
-
结合conftest和fixture实现全局前置
conftest.py文件介绍:
conftest.py是fixture函数的一个集合,可以理解为公共的提取出来放在一个文件里,然后供其它模块调用。不同于普通被调用的模块,conftest.py使用时不需要导入,Pytest会自动查找。
conftest.py,pytest用例会自动识别该文件,放到项目的根目录下就可以全局目录调用了,如果放到某个package下,那就在该package内有效,可有多个conftest.py
2、断言
assert a==2 #assert后面直接跟上要判断的表达式,若表达式为true,则用例通过
3、allure插件生成报告
allure生成测试报告的步骤:
- 在编写好的测试用例中添加allure装饰器函数
- 在运行入口处编写运行allure执行命令
- 查看生成的测试报告
常见的allure中的装饰器函数
这些装饰器函数写在测试类或者测试方法的开头;同一个测试类或测试函数可以添加多个装饰器函数
step和attachment比较特殊,他们位于方法内部而不是开头
函数 | 说明 |
---|---|
@allure.epic() | 项目名称 |
@allure.feature() | 模块名称 |
@allure.title() | 测试用例名称 |
@allure.description() | 测试用例描述(通常在此处写出预期结果) |
allure.step | 描述测试步骤 |
示例:
@allure.epic("vshop") #标记属于那个项目
@allure.feature("订单") # 标记属于那个模块
@allure.story("订单列表") #标记属于那个子模块
@allure.issue("http://localhost")
class TestOrder(unittest.TestCase):
@allure.title("查询订单列表") #标记测试用例
@allure.description("显示该列表的所有数据") # 标记用例的描述
def test_order_list(self):
with allure.step("1.进入我的订单列表页"): #标记测试步骤
self.order(self.driver)
# 断言
self.assertEqual("安全退出",self.reg.get_reg_msg(self.driver))
pytest集成allure后如何生成测试报告
-
若使用命令行启动pytest时,通过在pytest后面添加参数来生成allure报告:
pytest --alluredir=./allure-results #其中`./allure-results`是指定的目录,用来存放生成的测试报告文件
-
若使用脚本调用pytest时,在参数列表中添加相应的参数:
import pytest if __name__ == '__main__': pytest.main(['-vs', 'your_test_file.py', '--alluredir=./allure-results'])
-
执行完命令后,指定目录中生成的测试数据还不是html格式的,需要下列命令进行转换:
allure generate ./allure-results -o ./allure-report --clean
这条命令会读取
allure-results
目录下的数据,并在allure-report
目录下生成HTML格式的测试报告。--clean
参数用于在生成新报告之前清除allure-report
目录中的旧文件。
4、python操作yaml文件
yaml文件基本语法:
- 大小写敏感
#
表示注释- 缩进表示层级关系
yaml三种数据结构:
-
字典:键值对的集合
user: admin age: 18
#字典嵌套字典 data: user: admin age: 18
-
列表
name: - 小红 - 小明 - 小白
#列表嵌套字典 data: - admin1: 123 - admin2: 456 - admin3: 789
-
字符串/布尔值/整数/浮点数/Null/时间/日期
使用PyYaml库操作yaml文件
-
读操作:使用yaml.safe_load()方法.该函数会解析YAML内容并返回一个Python对象(通常是字典或列表)。
# 假设你有一个名为data.yaml的文件 with open('data.yaml', 'r') as file: data = yaml.safe_load(file) print(data)
-
写操作:使用yaml.dump()方法,它接受一个Python对象(通常是字典或列表)并将其序列化为YAML格式的字符串。你可以将这个字符串写入文件。
data = { 'name': 'John Doe', 'age': 30, 'children': [ {'name': 'Jane Doe', 'age': 5}, {'name': 'Jim Doe', 'age': 7} ] } with open('output.yaml', 'w') as file: yaml.dump(data, file, allow_unicode=True)
格式化写入:设置参数
default_flow_style=False
,这会生成一个更容易阅读的YAML文件,其中的键值对和列表使用缩进而不是方括号和花括号。with open('output_pretty.yaml', 'w') as file: yaml.dump(data, file, allow_unicode=True, default_flow_style=False)
写入中文:设置参数
allow_unicode=True
🔶**在一个pytest测试框架中,会专门定义一个
yaml_Util.py
类来进行各种yaml文件操作,其他py文件需要对yaml文件进行操作时直接调用即可。**这个文件中一般包括对yaml文件的读、写、清空等。
5、pytest实现接口关联
新建yaml文件,把需要用到的数据保存到yaml文件中;需要时再从yaml文件中读取。
此处yaml文件相当于postman中environment的作用
6、requests库中的测试会话session
创建requests的session对象,再用session发送get/post等请求,而不是直接发送
- 自动管理cookies:session对象会自动处理请求和响应中的cookies,可以自动保存和发送cookies,无需手动处理。这有助于保持用户登录状态等需要cookies支持的功能。
- 保持参数和状态:session对象可以在多个请求之间保持一些参数和状态,例如HTTP头、身份验证信息等。这样可以避免在每个请求中重复设置这些参数。
- 使用连接池:session对象使用底层的urllib3库,可以使用连接池来管理和重用HTTP连接,提高性能。
import requests
# 创建一个session对象
session = requests.Session()
# 使用session对象发送GET请求
response = session.get('https://www.example.com')
print(response.text)
# 假设登录接口需要POST请求,并且会返回登录后的cookies
login_response = session.post('https://www.example.com/login', data={'username': 'user', 'password': 'pass'})
# 登录成功后,使用同一个session对象发送其他请求,会自动携带登录后的cookies
other_response = session.get('https://www.example.com/protected_resource')
print(other_response.text)
# 关闭session(可选,因为session对象会在Python程序结束时自动关闭)
# session.close()
7、统一请求封装
目的:使多个py文件发送的请求都在同一个session下,方便自动管理cookie和统计相关信息
即不再是在每个测试类里都创建一个session对象,这样每个session只包含了一个测试类
实现方法:单独创建一个requests_util,.py文件,这个文件里的RequestUtil类有个session对象,是专门用来发送各种请求的。其他测试类想要发送请求,都来调用这个RequestUtil类中的方法。
import requests
#封装意味着,这个方法要能够适应所有请求(各个种类的请求,以及各种参数的情况)
class RequestUtil:
sess = requests.session()
def send_request(self,method,url,datas=None,**kwargs): #只有method和url是必填的
method = str(method).lower() #将传入的参数转化为字符串并且全部小写
res = None #用来接收返回结果的变量
if method == 'get':
res = RequestUtil.sess.request(method='get',url=url,params=datas,**kwagrs)
elif method == 'post':
if datas and isinstance(datas,dict): #如果datas不为空且是一个字典
str_datas = json.dumps(datas) #json.dumps可以把python对象转化为json字符串
res RequestUtil.sess.request(method='post',url=url,data=str_datas,**kwargs)
else:
pass
return res
8、yaml数据驱动封装
假设有一个存储测试用例的yaml文件cases.yaml
- case_id: 1
input:
number: 5
expected: 10
description: "Double the number"
- case_id: 2
input:
number: 10
expected: 20
description: "Double the number again"
向yaml文件操作类yaml_Util.py
中添加专门读取yaml测试用例的方法
def read_testcase(yaml_name):
with open(os.getcwd() + '文件路径' + yaml_name,mode='r',encoding='utf-8') as f:
value = yaml.load(stream=f,Loader=yaml.FullLoader)
return value
使用pytest.mark.parametrize
装饰器来修饰测试方法
def double_number(number):
return number * 2
@pytest.mark.parametrize("test_case", read_testcase(cases.yaml))
def test_double_number(test_case):
assert double_number(test_case['input']['number']) == test_case['expected'], test_case['description']
我们使用pytest.mark.parametrize
装饰器来遍历cases.yaml
文件中的每个测试用例并取名test_case
。每个测试用例都是一个字典,包含了input
、expected
和description
等键。我们调用double_number
函数,并使用assert
语句来验证输出是否符合预期。如果测试失败,description
字段将作为失败消息的一部分显示。