web自动化——pytest
pytest框架
- pytest:基于unittest之上的单元测试框架
- 自动发现测试模块和测试方法
- 断言使用assert+表达式即可
- 可以设置会话级、模块级、类级、函数级的fixtures、数据准备和清理工作;注意使用fixtures模块的时候,不能和unittest混用
- 有丰富的插件库,目前有300个以上。==allure
安装
- 直接file_setting安装即可
- 或者在网上下载插件,然后pip install 插件名
搜集用例的规则
- 默认从当前目录搜集测试用例,即在那个目录下运行的pytest命令,就从那个目录中开始搜索
- 搜索规则:
- 符合命名规则:test_py,或者* *_test.py **(函数方式)
- 在上面的基础找以下方法:
- 以test_开头——没有测试类的情况下
- 以Test开头的测试类(不能有
__init__
函数)当中,以test开头的函数 -
- 这种是类的方式
- pytest执行用例的顺序为:文件按照名字执行(ASCII顺序),想要修改顺序,直接修改位置即可
- unittest一定是在类当中,pytest不继承父类,所以,用例可以不用固定在测试类当中
# 函数形式
def test_case_03():
excpeted = 1
actual = 2
assert actual == excpeted
# 类的形式
class TestDemo:
def test_case_01(self):
excpeted = 1
actual = 2
assert actual == excpeted
def test_case_02(self):
excpeted = 2
actual = 2
assert actual == excpeted
用例的区分与执行——pytest.main
- 运行:在不同的模块,不同的目录中,只要是符合命名规则的,都会拿来去执行
- 如果要执行所有的测试用例,就需要将执行文件放在测试框架中(和测试用例文件夹在一层),使用main函数即可
- 代码如下:
#测试用例文件1
def test_case_03():
excpeted = 1
actual = 2
assert actual == excpeted
class TestDemo:
def test_case_01(self):
excpeted = 1
actual = 2
assert actual == excpeted
def test_case_02(self):
excpeted = 2
actual = 2
assert actual == excpeted
#测试文件2
def test_case_04():
excpeted = 1
actual = 2
assert actual == excpeted
class TestDemo:
def test_case_05(self):
excpeted = 1
actual = 2
assert actual == excpeted
def test_case_06(self):
excpeted = 2
actual = 2
assert actual == excpeted
# 和测试用例文件夹同级的运行文件:
import pytest
# 自动在这个文件所在的目录中,收集符合命名规则的文件
# 规则:在当前目录中,查找文件名字test_开头,函数或者类以test/Test开头
pytest.main()
- 如果想要指定运行文件,测试用例中的代码详见上方
path = pathlib.Path(__file__).absolute()
path = path.parent/"testcase"/"test_1.py"
pytest.main([f"{path}"])
- 通过加标签的方式(装饰器),装饰器用:
@pytest.mark.one
,其中one是关键字变量 - 这种方式可以跨文件实现,不一定非要写在一个文件中
- 代码如下:
# 测试用例文件
import pytest
@pytest.mark.one
def test_case_07():
excpeted = 1
actual = 2
assert actual == excpeted
@pytest.mark.two
class TestDemo:
def test_case_08(self):
excpeted = 1
actual = 2
assert actual == excpeted
def test_case_09(self):
excpeted = 2
actual = 2
assert actual == excpeted
# 测试用例执行(和测试用例文件夹一个层级)
pytest.main(["-m two or one"]) # 运行多个用例
pytest.main(["-m two"])#运行单个用例
- 用例的标签使用的时候,会出现警告提示,如果不想要,可以配置pytest.ini配置文件,设置方式
- 如果将pytest.main放在一个文件中,执行全部的测试用例,需要在文件中添加
if __name__ == '__main__'
,否则会执行两次,相见下方截图
生成测试报告——allure
- allure,是一个开源的,独立的展示测试报告的工具,可以支持各种框架执行测试用例,生成allure可解析的结果文件,从而再去生成能够看懂的html文件
- 使用allure,第一步需要安装,安装包的下载地址
- 下载.zip的包
- 安装,放在电脑上的某个目录中,目录不要太深
- 加压zip文件,然后打开bin目录,配置环境变量
- 检验:cmd,输入allure --version检查版本
- allure需要结合pytest框架来进行,需要安装第三方库:allure-pytest
- 运行的时候,需要在main执行中加一个参数即可,需要执行在那个目录去生成结果文件。
"--alluredir={path_1}"
,代码如下:
pytest.main(["-s","-v","-m two",f"--alluredir={path_1}"])
#path_1为报告的放置路径
- 代码执行之后,会生成一批json文件,这些文件是allure来看的
- 需要在terminal中,切换到rootdir(run文件所在的目录)中去执行:
allure serve .\testcase\allure_report\
,serve 为allure的json文件放置的路径, - 如果想要报告中显示的都是最新的报告,需要添加
--clean-alluredir
- 详见下图:
mac电脑配置allure
- 首先和windows一样,现将安装包下载下来
- 其次要讲安装包的路径配置在控制台的配置文件中,命令如下:
open -t ~/.bash_profile
# 然后讲以下路径配置放在配置文件中
export PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin
PATH="/Users/fyc/Desktop/Study/tools/allure-2.24.0/bin:${PATH}"
# 保存之后,需要使文章生效
source ~/.bash_profile
如果需要每次启动,解决方式
- 打开设置——用户于群组
- 使用密码解锁之后,ctrl+鼠标左键,选择高级设置
- 将登陆shell修改为/bin/bash 即可
- 重启电脑,就可在pycharm中使用allure
fixtures模块 处理前置和后置
- 即测试用你管理执行的环境准备和清理,相当于unittest中的setUp和tearDown
- fixtures的主要目的是为了提供一种可靠和可重复性的手段去运行那些最基本的测试内容。例如在测试网站时,每个测试用例都要登录和退出。利用 fixtures就可以只写一次,不需要每条用例中写一次
- fixtures通过函数来处理前置和后置的,名字随意定义
- pytest中,前置和后置,统一成为夹具
定义fixtures
- 第一点:将函数定义为fixtures,就直接在函数声明的时候加上装饰器:
@pytest.fixture()
,表示此函数为测试环境数据的准备和清理;fixture在不传参数的时候,可以不加括号
- 第二点:在函数中使用yield关键字,这个关键字可以作为分割线来使用,这个词的前面是前置代码,后面是后置代码
-
- yield还可以类return使用,可以定义返回值,使用场景:例如购物车,需要登录接口返回鉴权的数据(例如登录接口返回的token)
-
- 首先在夹具中调用登录接口,获取token的返回值,作为yield的返回值,然后在测试用例中调用购物车的接口,查看购物车信息
-
- 如果一个函数没有前置条件,也需要加yield关键字
- 调用:测试类或者测试用例当中,主动通过fixcure的函数名来调用fixcure;不需要在测试文件中引用conftestpy文件
- 注意,当yield返回的数值有多个时,可以使用多个变量接受,作为不同的变量来使用
# 夹具装饰函数
@pytest.fixture(scope="class")
def setUp_tearDown():
print("这是一个前置条件")
yield {"actual":1}
print("这是一个后置条件")
cases = [{"excpeted":1,"actual":2},
{"excpeted":0,"actual":0},
{"excpeted":15,"actual":60}]
@pytest.mark.parametrize("cases",cases)
def test_case_03(cases,setUp_tearDown):# 调用的时候直接将夹具的名字作为参数传入
excpeted = cases.get("excpeted")
actual = setUp_tearDown.get("actual")
assert actual == excpeted
#夹具装饰类
import pytest
@pytest.fixture() #声明这是一个夹具
def setUp_tearDown():
print("这是一个前置条件")
yield "yiyi","cases"
print("这是一个后置条件")
cases = [{"excpeted":1,"actual":2},
{"excpeted":0,"actual":0},
{"excpeted":15,"actual":60}]
class TestDemo:
def test_case_01(self,setUp_tearDown):
excpeted = 1
actual = 1
name,g = setUp_tearDown
assert actual == excpeted
print(f"这是{name}的{g}")
@pytest.mark.parametrize("cases", cases)
def test_case_02(self,cases,setUp_tearDown):
excpeted = cases.get("excpeted")
actual = cases.get("actual")
name,g = setUp_tearDown
assert actual == excpeted
print(f"这是{name}的{g}")
fixture的使用
- 使用的装饰器:
-
- 方式一:直接将夹具的名称作为参数,传入测试用例中,示例如下:
- 方式一:直接将夹具的名称作为参数,传入测试用例中,示例如下:
-
- 方式二:
@pytest.mark.usefixtures("fixture的函数名")
;在测试类或者测试用例的前面使用
- 方式二:
-
- 使用这种方式,不能使用fixture的返回值
- 定义conftest.py文件,再次文件中可定义多个fixcure
- 如果有返回值,直接在关键字yield后面,用空格隔开
- 测试用例接收fixcure的返回值时,需要将函数名称作为测试用例的参数传入测试用例
- pytest的断言是:是assert 断言表达式 ——中间要有空格
夹具的作用域
- 夹具的作用于通过设置参数来控制
-
- fixtures中的参数:
-
- scope参数:
The scope for which this fixture is shared; one of"function"
(default),"class"
,"module"
,"package"
or"session"
- scope参数:
-
- 这句话的意思是:不设置的情况下,默认使用function(每条用例);class为类;module是模块(py文件);package是包名(没有在用,可跳过),session是会话层
-
- 如果scope等于class,则是在类执行前,会执行夹具,但是在方法调用的时候不会调用夹具;scope等于function,不论执行类中的方法还是测试用例的方法,都会执行夹具
-
- 如果是类的这种形式,夹具要统一写在第一个方法上
-
- param:参数,可以进行参数化,列表形式
-
- autouse自动使用,输入布尔值,默认False
- 如果要使用夹具中yidld中的返回值,测试用例类中的方法,必须将夹具座位参数传入,代码如下:
import pytest
@pytest.fixture(scope="class") #声明这是一个夹具
def setUp_tearDown():
print("这是一个前置条件")
yield "yiyi","cases"
print("这是一个后置条件")
cases = [{"excpeted":1,"actual":1},
{"excpeted":0,"actual":0},
{"excpeted":60,"actual":60}]
@pytest.mark.parametrize("cases",cases)
def test_case_03(cases,setUp_tearDown):# 调用的时候直接将夹具的名字作为参数传入
excpeted = cases.get("excpeted")
actual = cases.get("actual")
assert actual == excpeted
class TestDemo:
def test_case_01(self,setUp_tearDown):
excpeted = 1
actual = 1
name,g = setUp_tearDown
assert actual == excpeted
print(f"这是{name}的{g}")
@pytest.mark.parametrize("cases",cases)
def test_case_02(self,cases,setUp_tearDown):
excpeted = cases.get("excpeted")
actual = cases.get("actual")
name,g = setUp_tearDown
assert actual == excpeted
print(f"这是{name}的{g}")
夹具的共享
- 所有的fixtures去出来当成一个公共的文件来进行存放,防止的py文件名称必须是:
conftest.py
- 定义在
conftest.py
文件夹中的夹具,其他模块可以自动发现并直接使用,不需要导入 - 这个文件要自己创建
- 作用范围是
conftest.py
文件所在的目录下的所有测试用例可以自动发现,不再同一个层级的测试用例不在夹具的范围内 - 文件与测试用例:这个文件的位置必须与测试用例平级或者测试用例是这个文件的子集
conftest.py
文件可以创建多个,识别的顺序是-
- 名字不一样,可以按照名字来区分
-
- 如果名字一样,就近原则。先查找子集模块下的py文件,即用例当前所在目录下的
conftest.py
文件
- 如果名字一样,就近原则。先查找子集模块下的py文件,即用例当前所在目录下的
-
- 然后查找模块外的py文件,即当前所在目录的上级目录,查找
conftest.py
文件,一直找到根目录为止
- 然后查找模块外的py文件,即当前所在目录的上级目录,查找
fixcure的代码
from selenium import webdriver
from web_login.class_test_datas import test_common_data as CO
import pytest
driver = None
#第一点,函数前面打标签(装饰器),scope决定作用域,默认function
#第二点:关键字yield前为前置代码;后面为后置代码
#第三点:driver定义在一个函数中,使用global全局变量,更改变量值
#第四点:方法不能和unittest和ddt一起使用
#第五点:如果有返回值,显示在yield后面
#类级别,传参
@pytest.fixture(scope="class")
def prepare_chrome():
print('================测试类级别===============')
#前置条件
# 打开一个会话
global driver
driver = webdriver.Chrome()
# 全屏
driver.maximize_window()
driver.get(CO.login_url)
yield driver
#后置条件
driver.quit()
@pytest.fixture
#函数级别,不需要传参
def refresh_page():
print('================测试用例级别===============')
yield
global driver
driver.refresh()
测试用例的代码
#调用:测试类或者测试用例当中,主动通过fixture的函数名称来调用
#不需要导入conftest的py文件
#用法:@pytest.mark.usefixtures("函数名称")
#如何接收fixture的返回值?答:函数名称代表返回值,
# 如果测试用例要使用,将函数名称传入用例后使用
@pytest.mark.usefixtures("prepare_chrome")
@pytest.mark.usefixtures("refresh_page")
@pytest.mark.login
class TestLogin:
#登录成功
@pytest.mark.smake
def test_login_success(self,prepare_chrome):
#在本页面打开会话,将diver传入页面page的初始化函数中,调用的时候直接传入
user = Lo.login_success_datas.get("user")
pwd = Lo.login_success_datas.get("pwd")
Login(driver=prepare_chrome).click_login_button()
Login(driver=prepare_chrome).ifram_swith_and_click_user_pwd()
Login(driver=prepare_chrome).send_login_data(user,pwd)
try:
#断言使用assert+表达式
assert UserPage(driver=prepare_chrome).isExit_name()
MyLog().info_mes("登录成功验证通过")
except Exception as e:
MyLog().error_mes("登录成功验证未通过")
raise e
发现一个很好解决问题的链接——解决了我安装pytest后,不能识别用例的问题
https://littlede.blog.csdn.net/article/details/105003366
pytest的参数化
- unittest与ddt的使用,可以执行多条用例,pytest怎么实现相似的功能呢?
- 答:使用装饰器,参数化
- 使用方式:
@pytest.mark.parametrize("参数名",列表数据)
- 参数名:用来接收每一项数据,并作为测试用例参数
- 列表数据:一组测试数据
- 这个方法的原理是:
-
- 依次获取到测试用例数据中的每一个元素,赋值给变量
-
- 变量座位用例方法的参数,依次执行每一条测试用例
-
- 直到所有的用例全部执行完毕
- 注意:参数化的时候,参数名和传入用例的参数名称必须一致
- parametrize方法和ddt一样,都可以用多个,不过传入其中的参数信息
- 函数去参数化的代码如下:
import pytest
cases = [{"excpeted":1,"actual":2},
{"excpeted":0,"actual":0},
{"excpeted":15,"actual":60}]
@pytest.mark.parametrize("cases",cases)
def test_case_03(cases):
excpeted = cases.get("excpeted")
actual = cases.get("actual")
assert actual == excpeted
- 如果测试用例是类的形式
-
- 如果数据驱动写在单个方法上面,那么只有这个方法可以使用
-
- 如果数据驱动写在类上面,那么类中所有的方法都可以使用
-
- 类和方法如果都写的话,会报错,请不要这么使用
- 其代码如下:
# 写在类上面:
@pytest.mark.parametrize("cases",cases)
class TestDemo:
def test_case_01(self,cases):
excpeted = cases.get("excpeted")
actual = cases.get("actual")
assert actual == excpeted
def test_case_02(self,cases):
excpeted = cases.get("excpeted")
actual = cases.get("actual")
assert actual == excpeted
#写在方法上面
class TestDemo:
def test_case_01(self):
excpeted = 0
actual = "0"
assert actual == excpeted
@pytest.mark.parametrize("cases", cases)
def test_case_02(self,cases):
excpeted = cases.get("excpeted")
actual = cases.get("actual")
assert actual == excpeted
重试机制——pytest-rerunfailures
- 一般来说,重试机制运行两次到三次,建议不超过3次
- 安装:
pip install pytest-rerunfailures
- 使用方式:pytest --return 重试次数
- 例如:pytest --return 2 代表运行失败的用例可以重新运行两次
- pytest --return 重试次数 --returns-delay 次数之间对的延时设置(单位:秒)
- pytest --return 2 --return-delay 5 表示失败的用例可以重新运行2次,第一次和第二次之间的间隔时间为5s钟
- 这里是一旦失败,立马执行
报告生成插件——pytest-html
- 生成result-log格式的测试报告,命令 :–resultlog=report\log.txt
- 生成JunitXML格式的测试报告,命令:–junitxml=path
- 生成html格式的测试报告,路径一般为相对路径,命令:–html=report\test_one_func.html
- result-log为txt文件,一般不用
- JunitXML,拿到之后直接解析,针对这个结果可以做二次定制开发;也可以与jekines集成,jekines拿到后可解析生成测试报告
不使用命令行运行
pytest.main([‘-m’,‘demo’]),如果一个命令中存在空格,则需要放在列表中,使用逗号隔开,注意中间不能加其他的参数
命令行不能运行pytest或者pip
解决方式:配置环境变量
- 如果没有配置pyCharm的环境变量,直接将pip的路径写在path中
- 如果已经配置了pyCharm的环境变量,在pyCharm的环境变量后面加上\Scripts即可
给你一个项目,如何去进行自动化测试?
- 先了解项目业务,点点点或 接口调用
- 熟悉需求,设计用例
- 评估是否适合,哪些功能/模块适合?接口/web/app,需要说明理由
- 自动化测试用例不是为了发现bug,要选择需求稳定,不怎么变化的项目来啊进行,他的主要作用主要在于预警,适合周期比较长的项目
- 优先接口自动化,原因:效率比较高;接口在底层,接口没有问题的话,页面基本上也没有问题
- 了解那些是核心项目,历史功能中bug率最高的模块,注意:沟通是必不可少的
- 自动化测试计划:根据那些模块,哪方面的自动化,筛选合适的测试用例(web自动化,可以让别人辅助);然后用例评审,统一意见;接口自动化可以求助于开发,给出某一个模块大致的接口数量,先调用再编写
- 抽取全部接口中的部分,取平均值估算用例条数
- 写计划,预计实现时间(准备工作时间、框架实现时间、额外的代码时间;了解接口时间、编写用例时间、jekines持续集成)
- 先实现一部分,出计划,然后再进行下一部分;成果汇报,进展汇报,发现的bug,主要是汇报业绩
- 注意:写了部分接口,要马上进行持续集成(尽量早的进行),设置定时或者发版本的时候去运行,尽早确认、暴露暴露问题
- 一个小的模块的用例完成后,要出测试报告(主要是测试数据分析),报告包含:共有多少接口,覆盖了那些功能,一定有多少条用例,接口通过率,要说明接口失败的原因,是bug还是自己脚本的问题
- 如果通过率非100%,要根据结果分析问题,是自己的问题要尽快修改
unittest与 pytest有什么不同?
- 自动搜索用例,执行顺序不一样
- 断言:assert +表达式
- 方便给用例归类
- 层级分明:会话级(数据库,token)、模块级、类级和函数级
- 有非常丰富的插件,例如:重运行。html、allure
- @pytest.mark.标签名 打标签,在测试类和测试用例前面
- fixture功能:前置、后置,提取公共的前置、后置条件,实现数据共享。函数实现,作用域默认fun,分割线关键字:yield 返回值(相当于return返回)
- 测试用例中的使用:在测试用例/测试 类的前面使用装饰器:@pytst.mark.usefixtures(函数名),如果要使用就将函数名称作为测试用例的参数传入测试用例
- 文件:conftest.py,与测试用例评级或是测试用例的父级,每一个用例包中都可以创建一个相同的py文件 。注意web的测试用例必须是一个包
- 用例运行配置:pytest命令行执行
- 参数化——数据驱动
如果在运行pytest的时候,报错:fixture XXX not found
- 原因:因为在pytest的版本在7。4.0以上,在这个版本智商,对应的conftest发生了变化,默认会从测试用例目录下去查找对应的文件
- 解决方式:
-
- 将pytest版本卸载之后降级,重新安装pytest7.4.0以下的版本
-
- 或者在根目录下存放一个pytest.ini的空文件