一文了解Python的Pytest架构及相关内容

在之前的文章里我们已经学习了python自带测试框架unittest,但是unittest具有一定的局限性
这篇文章里我们来学习第三方框架pytest,它在保留了unittest框架语法的基础上有着更多的优化处理
下面我们将从以下角度来介绍pytest:
pytest基本介绍
pytest基本使用
pytest进阶内容
pytest基本介绍
下面我们首先来简单介绍pytest及相关内容
单元测试框架
我们首先需要知道测试一般分为四个方面的测试:
单元测试:称模块测试,针对软件设计中的最小单位——程序模块,进行正确性检查的测试工作
集成测试:称组装测试,通常在单元测试的基础上,将所有程序模块进行有序的、递增测试,重点测试不同模块的接口部分
系统测试:将整个软件系统看成一个整体进行测试,包括对功能、性能以及软件所运行的软硬件环境进行测试
验收测试:指按照项目任务书或合同、供需双方约定的验收依据文档进行的对整个系统的测试与评审,决定是否接收或拒收系统
而我们这篇文章主要针对的是单元测试:
python:通常使用unittest和pytest来进行单元测试自动化,但pytest已经成为主流
java:通常使用testng和junit来进行单元测试自动化,但testng已经成为主流
最后我们需要明白单元测试框架的主要功能:
发现测试用例
执行测试用例
判断测试结果
生成测试报告
框架基本介绍
下面我们来简单介绍pytest框架:
pytest是一个非常成熟的单元测试框架,经过多版本的迭代,主要优点在于灵活和简单
pytest具有极强的兼容性和生态环境,它可以结合selenium,requests,appium完成各种不同的自动化
pytest具有更好的页面展示效果,它可以生成自定义allure报告以及和jenkins持续集成
下面我们给出一些和pytest框架可以很好聚合的框架类型:
pytestpytest-html:主要用来生成html报告的插件
pytest-xdist:主要用来进行多线程运行的插件
pytest-ordering:主要用来改变用例的执行顺序的插件
pytest-rerunfailres:主要用来失败用例重跑的插件
allure-pytest:主要用来生成美观自定义的allure报告
我们可以采用一种比较简便的方式来一次性下载这些框架:
# 首先我们需要将这些名称全部放入一个txt文件中,假设我们放在requestment.txt文件中# requestment.txt文件pytest-htmlpytest-xdistpytest-orderingpytest-rerunfailures# 我们只需要在pycharm的console中输入指令下载该文件夹中全部内容即可pip install -r requirements.txt pytest基本使用
下面我们来介绍pytest的基本使用
pytest默认测试用例
下面我们首先讲解pytest默认测试用例的格式:
# 首先我们的模块名(文件名)通常被统一存放在一个testcases文件夹中,然后需要保证模块名须以test_开头或者_test结尾# 例如我们下面的模块名命名就是正确示例test_demo1demo2_test# 然后我们需要注意我们模块中的测试类类名必须以test开头,并且不能带有init方法# 例如我们下面的类名命名就是正确示例class testdemo1:class testlogin:# 最后我们需要注意我们测试类中的测试方法名(case名)必须以test_开头# 例如我们下面的模块名命名就是正确示例test_demo1(self)test_demo2(self)# 我们给出一个测试用例例子:# 文件名为test_demo1class testdemo: def test_demo1(self): print(测试用例1) def test_demo2(self): print(测试用例2) # 当然我们上述的要求都不是必须相同的,在后续我们可以进行修改,我们将在下述讲解执行方法时讲解 然后我们再来讲解一下pytest的测试用例该如何执行:
# 首先我们讲解一下全局配置文件pytest.ini# 我们可以在pytest.ini中进行一些属性的配置来修改pytest的默认属性,我们需要在项目的根目录下创建,名称必须是pytest.ini1 [pytest]2 #参数3 addopts = ‐vs # 这里指当默认使用指令时的一些辅助参数,我们后面会讲解4 testpaths = ./testcases# 这里指默认的执行路径,它会默认执行该文件夹下所有的满足条件的测试case5 python_files = test_*.py# 这里就是前面我们所说的文件命名规则6 python_classes = test*# 这里就是前面我们所说的类名命名规则7 python_functions = test_*# 这里就是前面我们所说的case命名规则8 #标记9 markers =# 这里是冒烟规则,我们后面会讲到10 smoke:冒烟用例 11 product_manage:商品管理# 然后我们首先来讲采用console命令行执行pytest的方法# 最简单的就是直接在console命令行输入pytest,如果存在pytest.ini,它会根据文件内容进行执行;如果没有就按照默认格式执行# 但是我们可以通过一些参数来强化pytest参数指令# -vs: -v输出详细信息 -s输出调试信息pytest -vs# -n: 多线程运行(前提安装插件:pytest-xdist)pytest -vs -n=2# --reruns num: 失败重跑(前提安装插件:pytest-rerunfailres)pytest -vs --reruns=2# -x: 出现一个用例失败则停止测试pytest -vs -x# --maxfail: 出现几个失败才终止pytest -vs --maxfail=2# --html: 生成html的测试报告,后面 需要跟上所创建的文件位置及文件名称(前提安装插件:pytest-html)pytest -vs --html ./reports/result.html# -k: 运行测试用例名称中包含某个字符串的测试用例,我们可以采用or表示或者,采用and表示都# 采用or就表示:我们的运行用例名称中包含or两侧的其中一个数据即可# 采用and就表示:我们的运行用例名称中包含and两侧的所有数据才满足条件pytest -vs -k qiuluopytest -vs -k qiuluo or weiliangpytest -vs -k qiuluo and weiliang# -m:冒烟用例执行,后面需要跟一个冒烟名称# 我们在这里简单介绍一下冒烟用例的执行方法,我们这里其实就是一个分组执行的方法# 例如我们的用例划分为user_manage用户管理测试和product_manage商品管理测试,我们只希望执行其中一组测试# 首先我们需要在他们的不同方法上进行@mark划分,具体操作如下:class testdemo: # 我们在case上采用@pytest.mark. + 分组名称,就相当于该方法被划分为该分组中 # 注意:一个分组可以有多个方法,一个方法也可以被划分到多个分组中 @pytest.mark.user_manage def test_demo1(self): print(user_manage_test1) @pytest.mark.product_manage def test_demo2(self): print(product_manage_test1) @pytest.mark.user_manage @pytest.mark.product_manage def test_demo3(self): print(manage_test1)# 我们在执行中只需要采用前面我们所说的-m + 分组名称即可pytest -vs -m user_manage# 这里插一句,我们在运行过程中可以采用抛出异常的方式来模拟测试失败:raise exception() 抛出异常# 最后我们也可以采用main方法来执行pytest,同样我们也可以使用参数来进行调节if __name__ == '__main__':pytest.main() if __name__ == '__main__':pytest.main([‐vs]) 最后我们插入一个简单的案例跳过方法:
# pytest的跳过案例方法其实和unittest是完全相同的# 我们只需要采用skip或skipif方法来指定参数并贴在方法上即可跳过# @pytest.mark.skip(跳过原因)# @pytest.mark.skipif(跳过条件,跳过原因)# 我们给出一个示例class testdemo: workage2 = 5 workage3 = 20 @pytest.mark.skip(reason=无理由跳过) def test_demo1(self): print(我被跳过了) @pytest.mark.skipif(workage2<10,reason=工作经验少于10年跳过) def test_demo2(self): print(由于经验不足,我被跳过了) @pytest.mark.skipif(workage3 none: print('------打开浏览器') # 在执行该类后所需要调用的方法 @classmethod def teardownclass(cls) -> none: print('------关闭浏览器') # 每个测试方法执行之前都会先调用的方法 def setup(self): print('输入网址......') # 每个测试方法执行之后都会调用的方法 def teardown(self) -> none: print('关闭当前页面......') # 测试case1 def test_1(self): print('输入正确用户名密码验证码,点击登录 1') # 测试case2 def test_2(self): print('输入错误用户名密码验证码,点击登录 2') 然后我们还需要讲解一下fixtrue实现前后置的方法:
# 首先我们需要知道fixtrue所实现的功能基本和固件所实现的功能是一样的,但是会更加方便# 首先我们给出fixture的完整格式,然后我们再分开介绍各个参数@pytest.fixture(scope=none,autouse=false,params=none,ids=none ,name=none)# scope:作用范围# 参数主要有三种:function函数,class类,package/session包# function:在函数层面上执行前后置# 我们通常采用yield进行前后置划分,yield前是前置,yield后是后置@pytest.fixture(scope=function)def exe_database_sql():print(执行sql查询)yieldprint(关闭数据库连接)# 我们还可以通过yield或return去返回一些参数在方法中使用# 但是需要注意,yield返回参数后后置仍旧可以执行,但是return返回参数后后置操作无法执行@pytest.fixture(scope=function)def exe_database_sql():print(执行sql查询)yield success # return success 执行后无法执行后置操作print(关闭数据库连接)# 我们的方法在调用时,可以直接使用exe_database_sql表示返回信息进行输出def test_2(self,exe_database_sql): print(exe_database_sql) # class:在类之前和之后执行@pytest.fixture(scope=class)def exe_database_sql():print(执行sql查询)yieldprint(关闭数据库连接) # package/session:在整个项目会话之前和之后执行@pytest.fixture(scope=session)def exe_database_sql():print(执行sql查询)yieldprint(关闭数据库连接) # autouse:是否自动启动# 该参数默认为false,我们可以将其修改为true# 该参数的功能主要在判断该固件是否在自定义范围内可以自动启动# 若自动启动,则所有方法在执行时都会自动执行该前后置;但若为false,则我们需要手动启动# 首先如果是自动启动,则我们无需关心任何参数,我们的所有方法都会自动调用@pytest.fixture(scope=function,autoues=true)def exe_database_sql():print(执行sql查询)yieldprint(关闭数据库连接) # 但若是关闭自动启动,我们在不同的scope下有不同的调用方法@pytest.fixture(scope=function,autoues=flase)def exe_database_sql():print(执行sql查询)yieldprint(关闭数据库连接) # scope = function:我们需要在方法后加上该fixture方法名def test_2(self,exe_database_sql): print(exe_database_sql) # scope = class:我们需要在对应的类上添加@pytest.mark.usefixtures(exe_database_sql)装饰器调用@pytest.mark.usefixtures(exe_database_sql)class testdemo: pass# scope = session:.一般会结合conftest.py文件来实现,我们后面再介绍# 还需要注意autouse仅限于在自己的类中使用上述方法,如果要跨类使用,那么我们也需要在conftest.py中配置# params:实现参数化配置# 通常我们的脚本都是根据导出的yaml文件进行属性填充,针对参数化我们后面再讲,我们先将fixture的参数化# params通常后面跟上具体的数据(列表,元组等),然后我们在调用时有固定的写法# 首先我们需要在fixture方法参数中定义一个request,然后使用request.param来使用我们传递的params数据class testdemo: def read_yaml(): return [胡桃,胡桃宝宝,胡桃厨] # 首先我们的参数需要获取数据:params=read_yaml() @pytest.fixture(scope=function,autouse=false,params=read_yaml()) # 然后我们的fixture方法需要一个request参数def exe_database_sql(request): print(执行sql查询) # 我们通过request.param获取数据,可以采用yield返回该数据 yield request.param print(关闭数据库连接) # ids:参数别名id# 不能单独使用,必须和params一起使用,作用是对参数起别名 # 我们在采用pytest进行测试数据输出时会有对应的方法调用n次,该n次采用不同的params参数,这个ids就是修改了console控制台展示数据class testdemo: def read_yaml(): return [胡桃,胡桃宝宝,胡桃厨] # 当我们书写了ids,我们的控制输出就不会再是上面的[胡桃,胡桃宝宝,胡桃厨],而是我们所书写的[1,2,3] @pytest.fixture(scope=function,autouse=false,params=read_yaml(),ids=[1,2,3])def exe_database_sql(request): print(执行sql查询) # 我们通过request.param获取数据,可以采用yield返回该数据 yield request.param print(关闭数据库连接) # name:fixture别名# 作用是给fixtrue起别名,一旦使用了别名,那么fixtrue的名称就不能再用了,只能用别名class testdemo: # 如果我们在这里使用到了别名 @pytest.fixture(scope=function,name=exe_datebase_sql_name)def exe_database_sql(request): print(执行sql查询) yield print(关闭数据库连接) # 我们这里就需要使用别名进行操作,之前的名称无法使用def test_2(self,exe_datebase_sql_name): print(exe_database_sql) 接下来我们就将会讲解到我们刚刚提到的conftest.py文件:
# 首先我们需要知道conftest.py文件的名字是固定形式,不可改变# conftest.py文件主要就是用来存储我们的fixture,然后我们会根据该文件的不同位置来判断可以使用的方法# conftest可以在不同的目录级别下创建,如果我们在根目录下创建,那么所有case都会使用到该fixture# 但是如果我们在testcases文件夹下的某个模块文件下创建conftest.py,那么它的作用范围就只包含在该目录下# 根目录创建的conftest.py# 我们在该目录下的conftest文件里写的所有fixture可以在任意测试类下执行import pytest@pytest.fixture(scope=function,name=exe_datebase_sql_name)def exe_database_sql(): print(全部方法运行前均可以执行) yield print(全部方法运行后均可以执行)# testcases文件下的所有测试类# 这里需要注意:我们使用conftest下的fixture时,不需要import导包就可以使用import pytestclass testdemo1:# 测试case1 def test_1(self,exe_datebase_sql_name): print('输入正确用户名密码验证码,点击登录 1' + exe_datebase_sql_name) # testcases文件夹下的usercases文件夹下创建的conftest.py# 我们在该目录下创建的conftest文件里写的所有fixture仅可以在该目录下的测试类中使用,在其他测试类中使用会出现报错import pytest@pytest.fixture(scope=function,name=usercases_fixture)def exe_database_sql(): print(usercases方法运行前均可以执行) yield print(usercases方法运行后均可以执行)# testcases文件下的usercases文件夹下的测试类import pytestclass testusercases1:# 测试case1 def test_1(self,usercases_fixture): print('输入正确用户名密码验证码,点击登录 1' + usercases_fixture) # 最后我们简单给出一个前后置执行顺序优先级:fixture_session > fixture_class > setup_class > fixture_function > setup 然后最后我们给出前后置执行的一个总体逻辑顺序:
查询当前目录下的conftest.py文件
查询当前目录下的pytest.ini文件并找到测试用例的位置
查询用例目录下的conftest.py文件
查询测试用例的py文件中是否有setup,teardown,setup_class,teardown_class
再根据pytest.ini文件的测试用例的规则去查找用例并执行
pytest进阶内容
最后我们再来讲解一些pytest比较关键性的一些进阶内容
allure效果美化
我们在使用pytest所生成的页面往往不够美观且展示信息杂乱不好分析,所以我们通常搭载allure来实现界面美化:
allure框架是一个灵活轻量级多语言测试报告工具
它不仅可以以web的方式展示简介的测试结果,而且允许参与开发过程的每个人从日常执行的测试中最大限度的提取有用信息
下面我们就来学习如何安装使用allure:
# 首先我们需要去下载在电脑上下载allure并配置好环境变量# 我们这里给出官网下载地址:https://github.com/allure-framework/allure2/releases # 温馨提醒:下载链接在github上,如果无法打开可以刷新重试或者使用加速器梯子等辅助工具# 环境变量的配置只需要将bin文件所在目录放在电脑的path路径下即可,这里不再展示# 第二步我们需要在pycharm上下载allure-pytest插件(如果之前pip了那个整体文件,这里应该是已经下载过了)pip install allure-pytest# 第三步我们就可以直接来生成allure的测试结果展示界面了# 1.我们通常首先需要生成一个allure临时json文件# 我们通常会加上这么一串‐‐alluredir=./temps ‐‐clean‐alluredir# ‐‐alluredir = 文件生成地址 : 表示我们将allure临时文件生成在我们所指定的相对临时目录下# ‐‐clean‐alluredir : 由于每次都会生成大量文件,所以我们会在生成前清除当前目录下的allure文件,保证我们数据都是最新数据# 2.我们需要依靠临时文件来生成allure.html网页# 我们通常在main方法中执行if __name__ == '__main__': # 正常运行 pytest.main() # 休眠:主要为了json临时文件的生成 time.sleep(3) # allure generate 固定语句 + allure临时json文件目录 + -o 输出指令 + allure.html生成文件目录 + --clean 清除旧数据 os.system(allure generate ./temps ‐o ./reports ‐‐clean) parametrize数据驱动
我们通常会采用parametrize注解来进行数据驱动,下面我们来详细讲解一下:
# 格式:@pytest.mark.parametrize(参数名称,参数值)# 意义:我们会将参数名称作为id,然后根据参数值的个数去依次调用,存在n个参数值,我们将会调用n次case# 1.参数值为列表或元组时,参数名称可以为一个# 首先我们这里因为使用单个元素的列表(元组),我们的参数名可以为一个@pytest.mark.parametrize('caseinfo',['胡桃','胡桃宝宝','芙芙','芙芙宝宝'])# 在方法参数里,我们需要调用parametrize的参数名称caseinfo,需要保证一模一样def test_01_get_token(self,caseinfo): # 在这里我们可以借助参数名称caseinfo来代替列表中的元素 # 列表中存在几个,我们该方法将执行几次,例如现在列表是四个元素,那么我们方法将会重复执行四次并每次按顺序赋值不同的元素print(获取统一接口鉴权码:+caseinfo) # 2.参数值为列表的多个时,参数名称可以为多个# 这里我们列表中嵌套了一个列表,如果我们是单参数名称,那么输出时就会将第一个列表['胡桃厨','胡桃宝宝']输出出去# 但是如果我们是多参数名称,系统会自动将第一个列表的元素分开赋值给arg1,arg2便于我们分开使用,个人还是比较推荐的@pytest.mark.parametrize('arg1,arg2',[['胡桃厨','胡桃宝宝'],['芙芙厨','芙芙宝宝']])# 注意:这里当然也需要和参数名称对应!!!def test_01_get_token(self,arg1,arg2):print(获取统一接口鉴权码:+str(arg1)+ +str(arg2)) 我们在进行数据驱动时通常会结合yaml文件来进行数据获取,这里我们简单介绍一下yaml文件:


永磁直线电机驱动位置传感器方案
什么是显存频率
凌华科技推出嵌入式模块计算机Express-IB,助力提升图像处理性能
中车电气拟募资超77亿元用于高性能精密连接器项目
超声波换能器的类型有哪些?
一文了解Python的Pytest架构及相关内容
利用物联网技术,探寻智慧农业的解决方案
电阻焊与电弧焊的区别
变频器使用的21个干货经验
李自学踏上深圳的改革征程是否星辰大海?能否带领中兴通讯逆风翻盘?
PD充电是什么?电脑充电器能给手机充电吗?
车载功放的遥控器
卷积编码与分组编码的区别及应用案例
温湿度检测仪在危险环境中可以有什么应用?-欧森杰
思特威推出其全新0.7μm像素尺寸52MP超高分辨率图像传感器产品SC520XS
“DopeRaider”是区块链技术开发的地下犯罪题材的RPG游戏
关于光伏支架使用场景及安装形式的介绍和应用
今年的LG G7多了一个AI后缀,可怎么也打不破“刘海屏”魔咒
2020上半年TCL量子点电视销量,证明OLED未来可期
高通获国美1200万部终端大单抢占3G芯片市场