一、pytest实践补充
1、测试报告report.html,要展示具体的log日志,不要在运行的时候加-vs
2、assert False ,str(‘mes’) 后面会输出mes的信息
3、assert True ,str(‘mes’) 就不会有任何输出
4、在assert前面添加print 就会有print内容输出在测试报告中
5、考虑如何不将所有的log日志打印到html报告里???
6、要使得传参成立 用pytest test_* -a= 1 -b=2 来执行有效
7、禁止掉失败用例的所有输出(stdout,stderr以及logs):
pytest --show-capture=no
一、pytest单元测试框架
1、单元测试:在软件开发郭恒忠,针对软件的最小单元(函数,方法)进行正确的检查测试
2、python: unittest,pytest
3、主要做什么:
1)测试发现:多个py文件中找到测试用例
2)测试执行:按照一定的顺序和规则执行,生成结果
3)测试判断:通过断言判断预期结果和实际结果
4)测试报告:统计测试进度,耗时,通过率,生成测试报告
二、单元测试框架和自动化测试框架关系?
1)自动化框架
1、提高我们测试效率,降低维护 成本
2、减少人工干预,提高测试的准确性,增加代码的重用性
3、核心思想:让不懂代码的人也可以通过这个框架实现自动化测试
单元测试框架是自动化测试框架的组成部分之一
ps: pom 数据驱动 关键字驱动 全局配置文件的封装 日志监控 selenium,request二次封装 断言 报告邮件
三、pytest简介
1、非常成熟的python的单元框架,比unittest更加灵活,容易上手
2、可以和selenium,requests,appium结合实现web自动化,接口自动化,app自动化
3、实现测试用例的跳过以及reuns失败用例重试
4、可以和allure生成非常美观的测试报告
5、和jenkins持续集成
6、pytest有很多非常强大的插件,并且能够实现很多的实用的操作
pytest
pytest-html(生成html格式的测试报告)
pytest-xdist(分布式执行,多cpu分发)
pytest-ordering(用于改变测试用例的默认执行顺序,默认从上到下)
pytest-rerunfailures 用例失败后重跑
allure-pytest 用于生成美观的测试报告
放到requirements.txt中, 终端进行pip install -r requirements
四、使用pytest,默认的测试用例的规则以及基础应用
1、模块名必须以 test_ or _test开头 或者结尾
2、测试类必须以Test开头,并且不能有init方法
3、测试方法必须以test开头
五、pytest测试用例的运行
1、主函数模式
if __name__ == "__main__":
1) 运行所有 pytest.main()
2)运行指定模块 pytest.main(['-vs','test_login.py'])
3) 指定目录
pytest.main(['-s','./interface_testcase'])
- 通过nodeid指定用例执行:nodeid通过模块名,分隔符,类名,方法名,函数名组成
pytest.main(['-vs','./interface_testcase/test_interface.py::test_04_func']) 函数
pytest.main(['-vs','./interface_testcase/test_interface.py::Testinterface::test_03_zhiliao']) 类下面的函数
2、命令行模式
1)运行所有 : pytest
2)指定模块: pytest -vs test_login.py
3)指定目录: pytest -vs ./interface_testcase
参数详解:
-s: 表示输出用例调试信息,以及print打印信息
-v: 显示更详细的信息
-vs: 这两个参数一起用
-n :支持多线程或者分布式运行测试用例 pytest.main(['-s','./interface_testcase','-n=2'])
-reruns num: 失败用例重跑 pytest.main(['-s','./interface_testcase','-n=2','--reruns=2'])
-x 表示有一个用例报错,测试停止、
--maxfail=2 出现两个用例失败,就停止
-k : 根据测试用例的部分字符串指定测试用例 pytest -vs ./testcase -k "ao" 包含ao的用例
--html 生成html的测试报告: addopts = -vs --html ./report/report.html
pytest -vs ./testcase -n 2
3、读取pytest配置文件运行
1、位置:pytest.ini 是pytest核心的配置文件,一般放在项目的根目录
2、编码格式:必须是ANSI 可以使用notepad++修改格式
3、作用:改变pytest默认的行为(默认是模块名必须以test开头)
4、运行的规则: 不管使用的是主函数还是命令行,都会去读取配置文件
[pytest]
命令行参数,用空格分隔
addopts = -vs
配置测试搜索的模块文件路径
addopts = -vs
testpaths = ./testcase
python_files = aaa_*.py
python_classes = Test* # 类名的规则
python_functions = test_* # 方法名的规则
markers = # 用例分组
smoke: 冒烟用例
usermanage: 用户管理模块
productmanage: 商品管理模块
六、pytest执测试用例的顺序是怎样的?
unittest: ascII的大小来决定执行的顺序
pytest: 默认执行顺序是从上向下
改变默认执行顺序: 使用mark标记
@pytest.mark.run(order=3) 这个标记决定执行顺序
七、如何分组执行(冒烟,分模块执行,分接口和web执行)
smoke: 冒烟用例,分布在各个模块里面
用例前加: @pytest.mark.smoke
终端执行:pytest -vs -m smoke
既执行smoke又执行usermanage:
pytest -vs -m " smoke or usermanage"
八、pytest跳过用例
1、无条件
@pytest.mark.skip(reason="微微太漂亮")
2、有条件
@pytest.mark.skipif(age>=18,reason='已成年')
pytest框架实现的一些前后置(固件,夹具)的处理,常见有三种
九、setup/teardown, setup_class/teardown_class全局
为什么需要这些功能
比如:web自动化执行用例之前,需要打开浏览器吗,执行完毕需要关闭吗?
setup/teardown 在每个用例前后执行一次、
class Testmashang:
def setup(self):
print('\n在执行测试用例之前初始化代码:打开浏览器,加载网页')
def test_09_baili(self):
# time.sleep(3)
print('\n测试百里')
def test_08_xingyao(self):
# time.sleep(3)
print('\n测试星耀')
def teardown(self):
print('\n在执行测试用例之后扫尾的代码:关闭浏览器')
setup_class/teardown_class 在每个类前后执行一次、
class Testmashang:
# 这个方法在所有用例之前只执行一次
def setup_class(self):
print('\n在每个类之前初始化代码:创建日志对象,创建数据请求对象,创建接口的连接')
# 在每个用例之前执行一次
def setup(self):
print('\n在执行测试用例之前初始化代码:打开浏览器,加载网页')
def test_09_baili(self):
# time.sleep(3)
print('\n测试百里')
def test_08_xingyao(self):
# time.sleep(3)
print('\n测试星耀')
def teardown(self):
print('\n在执行测试用例之后扫尾的代码:关闭浏览器')
def teardown_class(self):
print('\n在每个类之后扫尾的代码:销毁连接')
和unittest不一样,全是小写
十、使用@pytest.fixture装饰器来实现部分用例的前后置
@pytest.fixture(scope="",params="",autouse="",ids="",name="")
1、scope表示被@pytest.fixture标记的方法的作用域,function, class, module, package/session
初级用法:
import pytest
@pytest.fixture(scope="function")
def my_fixture():
print('这是前后置的方法,可以实现部分以及全部用例的前后置')
yield
print('这是后置的方法')
class Testmashang:
def test_09_baili(self):
# time.sleep(3)
print('\n测试百里')
def test_08_xingyao(self,my_fixture):
# time.sleep(3)
print('\n测试星耀')
加上autouse = True 就可以全部进行使用
@pytest.fixture(scope=“module”, autouse=True) 模块的话只执行一次
@pytest.fixture(scope=“class”, autouse=True) 类的话在每个类执行一次
fixture优势
命名方式灵活,不局限于 setup 和teardown 这几个命名
1、conftest.py 配置里可以实现数据共享,不需要 import 就能自动找到fixture
2、scope=“module” 可以实现多个.py 跨文件共享前置
3、scope=“session” 以实现多个.py 跨文件使用一个 session 来完成多个用例
测试用例如何调用fixture
一、调用一个
# 1、将fixture作为测试用例函数的输入参数
# 2、测试用例加上装饰器: @pytest.mark.userfixtures(fixture_name)
# 3、fixture设置autouse=True 默认的是False
import pytest
@pytest.fixture
def login():
print('先输入账号密码,再登录')
def test_01(login):
print('登录后,进行操作01')
def test_02():
print('不需要前面登录,直接操作02')
if __name__ == "__main__":
pytest.main(['-vs'])
输出结果:
test_fixture.py::test_01 先输入账号密码,再登录
登录后,进行操作01
PASSED
test_fixture.py::test_02 不需要前面登录,直接操作02
PASSED
二、调用多个
import pytest
@pytest.fixture
def login():
print('先输入账号密码,再登录')
@pytest.fixture
def login2():
print('输入账号密码再登陆')
@pytest.mark.usefixtures('login2', 'login')
def test_03():
print('用例03,账号密码登录后进行其他操作')
if __name__ == "__main__":
pytest.main(['-vs'])
输出结果
test_fixture.py::test_03 输入账号密码再登陆
先输入账号密码,再登录
用例03,账号密码登录后进行其他操作
PASSED
三、自动调用
# 1、将fixture作为测试用例函数的输入参数
# 2、测试用例加上装饰器: @pytest.mark.userfixtures(fixture_name)
# 3、fixture设置autouse=True 默认的是False
import pytest
@pytest.fixture
def login():
print('先输入账号密码,再登录')
@pytest.fixture
def login2():
print('输入账号密码再登陆')
@pytest.fixture(autouse=True)
def login():
print('自动调用')
@pytest.mark.usefixtures('login2', 'login')
def test_03():
print('用例03,账号密码登录后进行其他操作')
if __name__ == "__main__":
pytest.main(['-vs'])
输出结果:
test_fixture.py::test_03 自动调用
输入账号密码再登陆
用例03,账号密码登录后进行其他操作
PASSED
fixture实例化顺序
import pytest
order = []
# session > module > class > function
# s1 在 m1之前,
@pytest.fixture(scope='session')
def s1():
order.append('s1')
@pytest.fixture(scope='module')
def m1():
order.append('m1')
# f3在a1和f1之前
@pytest.fixture
def f1(f3, a1):
# 先实例化f3,再实例化a1, 最后实例化f1
order.append('f1')
assert f3 == 123
@pytest.fixture
def f3():
order.append('f3')
a = 123
yield a
@pytest.fixture
def a1():
order.append('a1')
@pytest.fixture
def f2():
order.append('f2')
def test_order(f1, m1, f2, s1):
assert order == ['s1', 'm1', 'f3', 'a1', 'f1', 'f2']
if __name__ == "__main__":
pytest.main(['-vs'])
输出结果:
test_fixtureorder.py::test_order PASSED
添加了 @pytest.fixture ,如果fixture还想依赖其他fixture,需要用函数传参的方式,不能用 @pytest.mark.usefixtures() 的方式,否则会不生效
@pytest.fixture(scope="session")
def open():
print("===打开浏览器===")
@pytest.fixture
# @pytest.mark.usefixtures("open") 不可取!!!不生效!!!
def login(open):
# 方法级别前置操作setup
print(f"输入账号,密码先登录{open}")
前面讲的,其实都是setup的操作,那么现在就来讲下teardown是怎么实现的
用fixture实现teardown并不是一个独立的函数,而是用 yield 关键字来开启teardown操作
import pytest
@pytest.fixture(scope="function")
def my_fixture():
print('这是前后置的方法,可以实现部分以及全部用例的前后置')
yield
print('这是后置的方法')
def test_08_xingyao(my_fixture):
# time.sleep(3)
print('\n测试星耀')
if __name__ == "__main__":
pytest.main(['-vs'])
输出结果:
test_fixtureorder.py::test_08_xingyao 这是前后置的方法,可以实现部分以及全部用例的前后置
测试星耀
PASSED这是后置的方法
如果yield前面的代码,即setup部分已经抛出异常了,则不会执行yield后面的teardown内容
如果测试用例抛出异常,yield后面的teardown内容还是会正常执行
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pytest
@pytest.fixture(scope="session")
def open():
print("===打开浏览器===") # 会话前置操作setup
test = "测试变量是否返回"
yield test
print("==关闭浏览器==") # 会话后置操作teardown
@pytest.fixture
def login(open):
print(f"输入账号,密码先登录{open}") # 方法级别前置操作setup
name = "==我是账号=="
pwd = "==我是密码=="
age = "==我是年龄=="
yield name, pwd, age # 返回变量
print("登录成功") # 方法级别后置操作teardown
def test_s1(login):
print("==用例1==") # 返回的是一个元组
print(login) # 分别赋值给不同变量
name, pwd, age = login
print(name, pwd, age)
assert "账号" in name # 断言
assert "密码" in pwd
assert "年龄" in age
def test_s2(login):
print("==用例2==")
print(login)
if __name__ == "__main__":
pytest.main(['-vs'])
输出结果:
test_fixtureorder.py::test_s1 ===打开浏览器===
输入账号,密码先登录测试变量是否返回
==用例1==
('==我是账号==', '==我是密码==', '==我是年龄==')
==我是账号== ==我是密码== ==我是年龄==
PASSED登录成功
test_fixtureorder.py::test_s2 输入账号,密码先登录测试变量是否返回
==用例2==
('==我是账号==', '==我是密码==', '==我是年龄==')
PASSED登录成功
==关闭浏览器==
2、params: 作参数化 (支持list, tuple, 列表,元组,字典列表,字典元组{[],[],[]}
传参
@pytest.fixture(scope="function", params=['成龙', '甄子丹', '菜菜'])
def my_fixture(request):
return request.param
params=['成龙', '甄子丹', '菜菜'] 是参数名
request.param 是属性名,是没有s的
@pytest.fixture(scope="function", params=['成龙', '甄子丹', '菜菜'])
def my_fixture(request):
print('前置')
yield request.param # return和yield都表示返回,但是return的后面不能有代码,yield返回后可以接代码,return在yield后不能返回值?
print('后置')
# 一个参数一个值
@pytest.mark.parametrize("input", ["输入值"])
def test_case1(input):
print("\n" + input)
assert input == "输入值"
if __name__ == "__main__":
pytest.main(['-vs'])
输出结果:
test_fixtureorder.py::test_case1[\u8f93\u5165\u503c]
输入值
PASSED
3、autouse= True 自动执行,默认是False
4、ids: 当使用params参数化是,给每一个值设置一个变量名,意义不大
@pytest.fixture(scope="function", params=['成龙', '甄子丹', '菜菜'], ids=['cl', 'zzd', 'cyl'])
输出后有ids
@pytest.mark.parametrize("input", ["输入值1", "输入值2"], ids=['cl', 'zzd'])
def test_case2(input):
print("\n" + input)
assert '输入值' in input
if __name__ == "__main__":
pytest.main(['-vs'])
输出结果:
test_fixtureorder.py::test_case2[cl]
输入值1
PASSED
test_fixtureorder.py::test_case2[zzd]
输入值2
PASSED
多参数的混合使用
data1 = [1, 2]
data2 = ["python", "java"]
data3 = ["软", "件"]
@pytest.mark.parametrize("a", data1)
@pytest.mark.parametrize("b", data2)
@pytest.mark.parametrize("c", data3)
def test_case3(a, b, c):
print(f"生成新的数据组合为:[{a} {b} {c}]")
if __name__ == "__main__":
pytest.main(['-vs'])
输出结果
test_fixtureorder.py::test_case3[\u8f6f-python-1] 生成新的数据组合为:[1 python 软]
PASSED
test_fixtureorder.py::test_case3[\u8f6f-python-2] 生成新的数据组合为:[2 python 软]
PASSED
test_fixtureorder.py::test_case3[\u8f6f-java-1] 生成新的数据组合为:[1 java 软]
PASSED
test_fixtureorder.py::test_case3[\u8f6f-java-2] 生成新的数据组合为:[2 java 软]
PASSED
test_fixtureorder.py::test_case3[\u4ef6-python-1] 生成新的数据组合为:[1 python 件]
PASSED
test_fixtureorder.py::test_case3[\u4ef6-python-2] 生成新的数据组合为:[2 python 件]
PASSED
test_fixtureorder.py::test_case3[\u4ef6-java-1] 生成新的数据组合为:[1 java 件]
PASSED
test_fixtureorder.py::test_case3[\u4ef6-java-2] 生成新的数据组合为:[2 java 件]
PASSED
============================== 8 passed in 0.34s ==============================
Process finished with exit code 0
5、name: 表示是被@pytest.fixture标记的方法取一个别名
给标记的方法起一个别名,使用别名,再用本身的方法来传参,是不能用的
十一、通过conftest.py和@pytest.fixture结合使用实现全局前置应用(比如:项目的全局登录,模块的全局处理)
1、conftest.py 文件是单独存放的一个夹具配置文件,名称是不能更改的
2、可以再不同的py文件中使用同一个fixture函数(跨文件使用前置)
3、contfest.py 文件需要和运行的用例放在同一层,并且不需要任何import导入的操作(原则上同层,但是不同层也一样可以做到)
总结:
setup/teardown ,setup_class/teardown_class 作用于所有用例或者所有的类
@pytest.fixtue() 它的作用既可以部分也可以全部前后置
conftest.py和@pytest.fixture()结合使用,作用于全局的前后置
@pytest.fixture(scope="function")
def all_fixture():
print('全局前置')
yield
print('全局后置')
@pytest.fixture(scope="function")
def user_fixture():
print('用户管理前置')
yield
print('用户管理后置')
class Testmashang1:
def test_userall_xingyao(self, all_fixture, user_fixture):
# time.sleep(3)
print('\n测试星耀')
print('--------------------'+str(user_fixture)) # 先局部变量
print(all_fixture) # 再全局变量
十二、断言
assert
assert 1==2
十三、pytest结合allure-pytest插件生成allure测试报告
allure-pytest
如果dos可以验证,pycharm验证不了,重启pycharm
2、加入命令生成json格式的临时报告
pytest.ini加入
addopts = -vs --alluredir ./temp
3、生成allure报告
在pytest.main() 后面构建
os.system('allure generate ./temp -o ./report --clean')
# 固定语法+找到临时报告+输出+输出到当前report目录下+清空原有的报告
生成allure的json文件:
pytest test_03_neiwanglogin.py --loopcount=1 --checkloopcount=2 --alluredir ./tmp
由json文件生成allure报告:
allure generate ./tmp/ -o ./tmpreport/ --clean
优点: 生成的json文件,可以直接看到log日志,以及输出结果等, 并且报告中也分别独立展示
二、自带报告
pytest test_03_neiwanglogin.py --html=report.html --loopcount=1 --checkloopcount=2 # 如果出现-s就禁止capture输出
优点:所有内容都可以在一个页面中展示
缺点:会显示log日志,目前没有办法只禁止log日志的输出,循环次数较多的时候,报告内容过长
三、选择allure原因
由于html报告展示了全部日志
目前没有找到让部分日志不显示的方法,所以用allure测试报告,会有分开的json文件,以及报告里面各个显示情况是独立的,可以做参考
十四、@pytest.mark.parametrize()基本用法
@pytest.mark.parametrize(args_name, args_value)
args_name: 参数名
args_value: 参数值(列表,元组,字典列表,字典元组)有多少个值用例就会执行多少次
第一种方式:
import pytest
class Testapi:
@pytest.mark.parametrize('args', ['百里', '星耀', '依然'])
def test_01_baili(self, args):
print(args)
if __name__ == "__main__":
pytest.main()
输出结果:
test_fixtureorder.py::Testapi::test_01_baili[\u767e\u91cc] 百里
PASSED
test_fixtureorder.py::Testapi::test_01_baili[\u661f\u8000] 星耀
PASSED
test_fixtureorder.py::Testapi::test_01_baili[\u4f9d\u7136] 依然
PASSED
第二种方式:跟unittest的ddt里面的@ynpack解包一样
import pytest
class Testapi:
@pytest.mark.parametrize('name, age',[['百里', '38'], ['星耀', '18']])
def test_01_baili(self, name, age):
print(name, age)
if __name__ == "__main__":
pytest.main()
输出结果:
test_fixtureorder.py::Testapi::test_01_baili[\u767e\u91cc-38] 百里 38
PASSED
test_fixtureorder.py::Testapi::test_01_baili[\u661f\u8000-18] 星耀 18
PASSED
十五、YAML文件详解—实现接口自动化
1、用于全局的配置文件 ini yaml
2、用于写接口的测试用例
yaml简介:
yaml是一种数据格式, 支持注释,换行,多行字符串,裸字符串(整形,字符串)
语法规则:
1、区分大小写
2、使用缩进表示层级,不能使用tab键,只能用空格键
3、缩进没有数量的,只要前面是对其就行
4、注释是#
数据组成:
1、Map对象, 键值对 键:(空格)值
多行写法
msxy:
name: 百里
age: 18
一行写法
msxy: {name: 百里, age: 18}
检测yaml是否是正确格式的网站: https://www.bejson.com/validators/yaml_editor/
2、数组(list) 用一组横线开头
msxy:
- name: 百里
- age: 18
msxy: [{name: 百里}, {age: 18}]
2、断言的封装
3、allure报告的定制
4、关键字驱动和数据驱动结合实现接口自动化测试
5、python的反射
正常:先初始化对象吗,再调方法
反射:通过对象得到类对象,然后通过类对象使用方法
6、jenkins的持续集成和allure的报告继承,并且根据自动化的报告的错误率去发送电子邮件