找回密码
 会员注册
查看: 29|回复: 0

Pytest自动化测试框架详解

[复制链接]

2万

主题

0

回帖

6万

积分

超级版主

积分
65001
发表于 2024-9-4 12:22:22 | 显示全部楼层 |阅读模式
今日之失,未必不为后日之得。大家好,刚才在翻看之前整理的一些关于自动化测试的文档,突然发现一个比较详细的关于pytest框架的介绍和使用文章,对于打算使用python进行自动化测试编写的小伙伴,还是很值得一看的,希望对大家有所帮助。Pytest简介一、前言    pytest是python一个第三方单元测试框架,有非富的第三方插件可以扩展,例如:Allure,兼容unittest但比原生未做功能增强的unittest其更高效简洁。二、pytest安装1.pytest通过pip安装即可,安装命令:pipinstallpytest2.查看是否安装成功pytest--version三、Pytest的常用功能1.用例运行规则2.用例运行的级别3.@pytest.fixture使用4.@pytest.mark使用5.插件之pytest-ordering(用例执行顺序)6.插件之pytest-rerunfailures(失败重跑)7.插件之pytest-assume(多断言)8.插件之pytest-xdist(并发)Pytest运行及控制台输出信息运行一个简单的用例:deftest_passing():    assert(1,2,3)==(1,2,3)运行结果及说明:测试运行可能出现的结果总结(上图6、7运行结果列举)示例:importpytest#测试通过deftest_passing():    assert(1,2,3)==(1,2,3)#测试失败deftest_failing():assert(1,2,3)==(3,2,1)#跳过不执行@pytest.mark.skip()deftest_skip():    assert(1,2,3)==(3,2,1)#预期失败,确实失败@pytest.mark.xfail()deftest_xfail():    assert(1,2,3)==(3,2,1)#预期失败,但是结果pass@pytest.mark.xfail()deftest_xpass():    assert(1,2,3)==(1,2,3)运行结果:Pytest常用命令行选项--case相关pytest命令运行语法pytest--help常用case相关参数说明详细举例说明case文件为:import pytestdeftest_a():    assert(1,2,3)==(1,2,3)deftest_failing():    assert(1,2,3)==(3,2,1)deftest_failing2():    assert(1,2,3)==(3,2,1)deftest_b():    assert(1,2,3)==(1,2,3)@pytest.mark.smoke1deftest_c():    assert(1,2,3)==(1,2,3)@pytest.mark.smoke2deftest_d():assert(1,2,3)==(1,2,3)1、–collect–only:收集目录下所有的用例pytest-–collect-only2、-k:模糊筛选指定的case,只要匹配到了就,就收集运行pytest-k"aorb"从运行结果可以看出,-k是会按条件模糊查找case名称中的信息,只要匹配到了就,就收集运行3、-m:运行带用指定标记的用例,常于@pytest.mark结合使用pytest-m“smoke1orsmork2”test_two.py注:上图的mark警告信息是由于,mark标记smoke1等不是fixture自带的标记,为自定义标记,所以报错,解决办法将在@pytest.mark内详细说明4、-x:遇到失败停止运行脚本上图可以看出,遇到停卡,其它几条用例就不再运行5、–maxfail=num:达到num错误次数再停卡整个case,此参数与-x类似,都是遇错误终止pytest--maxfail=2 test_two.py6、--lf(--last-failed):运行最后一次失败用例,且只运行失败的case(前提是已经运行过的用例有失败的一次运行,-x我们已经有过一次失败了)pytest--lftest_two.py7、--ff(--failed-first):同lf相似,但–ff是所有的case都会运行,只是优先运行上次失败的case,再运行其它状态的casepytest--lftest_two.py失败重试:•测试失败后要重新运行n次,要在重新运行之间添加延迟时间,间隔n秒再运行。•安装:pipinstallpytest-rerunfailures•pytest-v--reruns5--reruns-delay1—每次等1秒重试5次比如:10个用例执行到第4个失败然后重试会重试第四个,如果成功就继续往下执行。Pytest前后置处理函数Fixture简介    Fixture是pytest的核心功能,也是亮点功能.属装饰器函数(在不改变被装饰函数的前提下对函数进行功能增强),用于在测试用例运行之前进行前后置工作处理工作。与setup/teardown类似,但更强大灵活Fixture简介        Fixture的目的是提供一个固定基线,在该基线上测试可以可靠地和重复地执行。fixture提供了区别于传统单元测试(setup/teardown)有显著改进:1.有独立的命名,并通过声明它们从测试函数、模块、类或整个项目中的使用来激活。2.按模块化的方式实现,每个fixture都可以互相调用。    Fixture的范围从简单的单元扩展到复杂的功能测试,允许根据配置和组件选项对fixture和测试用例进行参数化,或者跨函数function、类class、模块module或整个测试会话sessio范围。Fixture工作原理@Pytest.fixture()装饰器用于声明函数是一个fixture如果测试函数的参数列表中包含fixture装饰的函数名那么pytest就会检测到然后在测试函数运行之前执行该fixture如果fixture装饰的函数有返回值,那么fixture在完成任务后,将数据再返回给测试函数,相当于传参。Pytest搜索fixture的顺序测试用例的参数列表中包含一个fixture名,pytest会用该名称搜索fixture优先搜索测试所在的模块然后再搜索模块同一文件路径下的conftest.py找不到再搜索上一层的conftestPytest之Fixture参数详解及使用Fixture的调用方式:@pytest.fixture(scope ="function",params=None,autouse=False,ids=None,name=None)参数详解:1、SCOPE用于控制Fixture的作用范围作用类似于Pytest的setup/teardown默认取值为function(函数级别),控制范围的排序为:session>module>class>function作用范围举例:scope=“function”语法:@pytest.fixture() #或者@pytest.fixture(scope='function')场景一:做为参数传入importpytest#fixture函数(类中)作为多个参数传入@pytest.fixture()deflogin():    print("打开浏览器")    a="account"returna@pytest.fixture()deflogout():    print("关闭浏览器")classTestLogin:    #传入loninfixture    deftest_001(self,login):        print("001传入了logingfixture")        assertlogin=="account"    #传入logoutfixture    deftest_002(self,logout):        print("002传入了logoutfixture")    deftest_003(self,login,logout):        print("003传入了两个fixture")    deftest_004(self):        print("004未传入仍何fixture哦")if__name__=='__main__':pytest.main()运行结果:从运行结果可以看出,fixture做为参数传入时,会在执行函数之前执行该fixture函数。再将值传入测试函数做为参数使用,这个场景多用于登录场景二、Fixture的相互调用代码:importpytest#fixtrue作为参数,互相调用传入@pytest.fixture()defaccount():    a="account"    print("第一层fixture")    returna    #fixture的相互调用一定是要在测试类里调用这层fixture才会生次,普通函数单独调用是不生效的@pytest.fixture()  deflogin(account):    print("第二层fixture")classTestLogin:    deftest_1(self,login):        print("直接使用第二层fixture,返回值为{}".format(login))    deftest_2(self,account):        print("只调用accountfixture,返回值为{}".format(account))if__name__=='__main__':pytest.main()运行结果:注:1.即使fixture之间支持相互调用,但普通函数直接使用fixture是不支持的,一定是在测试函数内调用才会逐级调用生效2.有多层fixture调用时,最先执行的是最后一层fixture,而不是先执行传入测试函数的fixture3.上层fixture的值不会自动return,这里就类似函数相互调用一样的逻辑scope=“class”:当测试类内的每一个测试方法都调用了fixture,fixture只在该class下所有测试用例执行前执行一次测试类下面只有一些测试方法使用了fixture函数名,这样的话,fixture只在该class下第一个使用fixture函数的测试用例位置开始算,后面所有的测试用例执行前只执行一次。而该位置之前的测试用例就不管。语法:@pytest.fixture(scope='class')场景一、importpytest#fixture作用域scope='class'@pytest.fixture(scope='class')deflogin():    print("scope为class")classTestLogin:    deftest_1(self,login):        print("用例1")    deftest_2(self,login):        print("用例2")    deftest_3(self,login):        print("用例3")if__name__=='__main__':pytest.main()运行结果:场景二、importpytest@pytest.fixture(scope='class')deflogin():    a='123'    print("输入账号密码登陆")classTestLogin:    deftest_1(self):        print("用例1")    deftest_2(self,login):        print("用例2")    deftest_3(self,login):        print("用例3")    deftest_4(self):        print("用例4")if__name__=='__main__':pytest.main()运行结果:scope=“module”:与class相同,只从.py文件开始引用fixture的位置生效importpytest#fixturescope='module'@pytest.fixture(scope='module')deflogin():    print("fixture范围为module")deftest_01():    print("用例01")deftest_02(login):    print("用例02")classTestLogin():    deftest_1(self):        print("用例1")    deftest_2(self):        print("用例2")    deftest_3(self):        print("用例3")if__name__=='__main__':    pytest.main()运行结果:scope=“session”:用法将在conftest.py文章内详细介绍session的作用范围是针对.py级别的,module是对当前.py生效,seesion是对多个.py文件生效session只作用于一个.py文件时,作用相当于module所以session多数与contest.py文件一起使用,做为全局Fixture2、params:Fixture的可选形参列表,支持列表传入默认None,每个param的值fixture都会去调用执行一次,类似for循环可与参数ids一起使用,作为每个参数的标识,详见ids被Fixture装饰的函数要调用是采用:Request.param(固定写法,如下图)举个栗子:3、ids:用例标识ID与params配合使用,一对一关系举个栗子:未配置ids之前,用例:配置了IDS后:4、autouse:默认False若为True,刚每个测试函数都会自动调用该fixture,无需传入fixture函数名由此我们可以总结出调用fixture的三种方式:1.函数或类里面方法直接传fixture的函数参数名称2.使用装饰器@pytest.mark.usefixtures()修饰3.autouse=True自动调用,无需传仍何参数,作用范围跟着scope走(谨慎使用)让我们来看一下,当autouse=ture的效果:5、Name:fixture的重命名通常来说使用fixture的测试函数会将fixture的函数名作为参数传递,但是pytest也允许将fixture重命名如果使用了name,那只能将name传如,函数名不再生效调用方法:@pytest.mark.usefixtures(‘fixture1’,‘fixture2’)举栗:importpytest@pytest.fixture(name="new_fixture")deftest_name():    pass    #使用name参数后,传入重命名函数,执行成功deftest_1(new_fixture):    print("使用name参数后,传入重命名函数,执行成功")#使用name参数后,仍传入函数名称,会失败deftest_2(test_name):print("使用name参数后,仍传入函数名称,会失败")运行结果:Pytest前后置处理函数Fixture之yiledimportpytest@pytest.fixture()deflogin():    print("今天的笔记做完了吗?")    yield    print("今天的笔记做完啦!!!")deftest_01(login):    print("我是用例一")    if__name__=='__main__':pytest.main()运行结果:从以上运行结果我们可以看到,带有yiled的fixture函数,yiled函数语句分别在测试类前后被执行,我们来总结一下带yiled的fixture特性。总结:一般的fixture函数会在测试函数之前运行,但是如果fixture函数包含yiled,那么会在yiled处停止并转而运行测试函数,等测试函数执行完毕后再回到该fixture继续执行yiled后面的代码。可以将yiled前面的代码看作是setup,yiled后面的部分看作是teardown的过程。无论是测试函数中发生了什么是成功还是失败或者error等情况,yiled后面的代码都会被执行,yiled中的返回数据也可以在测试函数中使用Pytest之conftest.pyconftest.py是什么?conftest.py是fixture函数的一个集合,可以理解为公共的提取出来放在一个文件里,然后供其它模块调用。不同于普通被调用的模块,conftest.py使用时不需要导入,Pytest会自动查找conftest.py使用场景如果我们有很多个前置函数,写在各个py文件中是不很乱?再或者说,我们很多个py文件想要使用同一个前置函数该怎么办?这也就是conftest.py的作用conftest.py使用原则conftest.py这个文件名是固定的,不可以更改。conftest.py与运行用例在同一个包下,并且该包中有__init__.py文件使用的时候不需要导入conftest.py,会自动寻找。conftest.py使用举例conftest.py文件(scope=“session”)importpytest@pytest.fixture(scope="session")deflogin():    print("输入账号密码")    yield    print("清理数据完成")case文件:importpytestclassTestLogin1():    deftest_1(self,login):        print("用例1")    deftest_2(self):        print("用例2")    deftest_3(self,login):        print("用例3")if__name__=='__main__':    pytest.main()运行结果从上图可以看出,conftest.py内的fixture方法的作用范围是session,调用时,整个.py文件只会调用一次conftest.py作用域我们先来看下目录结构内层case文件:b.pyimportpytestclassTestLogin1():    #调用了内层fixture    deftest_1(self,login2):        print("用例1")    #调用了外层fixture    deftest_3(self,login):        print("用例3")if__name__=='__main__':    pytest.main()内层conftest.pyimportpytest@pytest.fixture(scope="session")deflogin2():    print("内层fixture前置")外层case:a.pyimportpytestclassTestLogin1():    #调用了内层fixture    deftest_1(self,login2):        print("用例1")    #调用了外层fixture    deftest_3(self,login):        print("用例3")if__name__=='__main__':    pytest.main()外层conftest.pyimportpytest@pytest.fixture(scope="session")deflogin():    print("外层fixture")运行内层b.py由上图过行结果可以看出,内外层的conftest.py都可以被找到并被调用执行我们再来运行一下外层case,a.py可以看到,外层调用内层的fixture是失败的由以上运行结果可以总结出:conftest.py的作用域与Python变量作用域相同内层包内conftest.py不允许被其它包的测试类或方法使用,相当于本地变量外层conftest.py可被内层测试类和方法使用,相当于全局变量Pytest跳过用例之@pytest.mark.skip和@pytest.mark.skipif@pytest.mark.skip:跳过该条测试用例用法:@pytest.mark.skip(self,reason=None)def test_one():pass参数说明:@pytest.mark.skipif:跳过符合条件的测试用例用法:@pytest.mark.skipif(self,condition, reason=None)def test_two():pass参数说明:两者的区别@pytest.mark.skip:无差别跳过,只要用这个mark就跳过@pytest.mark.skipif:符合条件的才跳过举个栗子:importpytest#设置跳过conditioncon="跳过条件"@pytest.mark.skip("这条是skip,无差别跳过")deftest_mark_skip():    assert1==1    @pytest.mark.skipif(con=="跳过条件",reason='skipif有条件的跳过')deftest_mark_skipif():    assert1==1    @pytest.mark.skipif(con=="不符合",reason='skipif不符合条件的执行')deftest_mark_noskipif():    assert 2==2    if__name__=='__main__':pytest.main()运行结果Pytest之调用Fixture@pytest.mark.usefixtures前言@pytest.mark.usefixtures是Pytest调用fixture的方法之一,与直接传入fixture不同的是,它无法获取到被fixture装饰的函数的返回值。@pytest.mark.usefixtures的使用场景是被测试函数需要多个fixture做前后置工作时使用用法@pytest.mark.usefixtures(self,*fixturenames)deftest_one():pass参数说明:举栗场景一:传入单个Fixtureimportpytest@pytest.fixture()defone():    print("我是一个fixture函数")@pytest.mark.usefixtures('one')deftest_one_fixture():    print("测试用例一")assert1==1运行结果:场景一:传入多个Fixtureimportpytest@pytest.fixture()defone():    print("我是第一个fixture函数")@pytest.fixture()deftwo():    print("我是第二个fixture函数")    @pytest.mark.usefixtures('one','two')deftest_one_fixture():    print("测试用例一")assert1==1运行结果场景一:叠加多个Fixtureimportpytest@pytest.fixture()defone():    print("我是第一个fixture函数")@pytest.fixture()deftwo():    print("我是第二个fixture函数")@pytest.mark.usefixtures('one')@pytest.mark.usefixtures('two')deftest_one_fixture():    print("测试用例一")assert1==1运行结果:总结:单个fixture:在被测试函数之前执行多个fixture:按传值先后顺序执行叠加fixture:自下至上执行,离测试函数越近的先被执行Pytest之@pytest.mark.自定义标签使用前言:        ytest允许用户自定义自己的用例标签,用于将用例进行分组,以便在运行用例的时候筛选你想要运行的用例。@pytest.mark.自定义标签的使用:可以标记测试方法、测试类,标记名可以自定义,最好起有意义的名字;同一测试类/方法可同时拥有多个标记;importpytest @pytest.mark.loginclassTestLogin:    """登陆功能测试类"""    @pytest.mark.smoke    @pytest.mark.success    deftest_login_sucess(self):        """登陆成功"""        #实现登陆逻辑        pass     @pytest.mark.failed    deftest_login_failed(self):        """登陆失败"""        #实现登陆逻辑        pass  @pytest.mark.logoutclassTestLogout:    """登出功能测试类"""    @pytest.mark.smoke    @pytest.mark.success    deftest_logout_sucess(self):        """登出成功"""        #实现登出功能        pass     @pytest.mark.failed    deftest_logout_failed(self):        """登出失败"""        #实现登出功能        pass运行方法:语法:pytest-m"自定义标签名"或:pytest.main(['-m自定义标签名'])示例: pytest-m"smoke"testmark.py运行结果:从以上运行结果可以看出,只有被选择的标记用例被运行组合运行用例使用-m参数运行标记的测试用例;-m参数支持and、or、not等表达式;#运行登陆功能的用例pytest.main(['-mlogin'])#运行登出功能的用例pytest.main(['-mlogout'])#运行功能成功的用例pytest.main(['-msuccess'])#运行功能失败的用例pytest.main(['-mfailed'])#运行登陆功能但是不运行登陆失败的测试用例pytest.main(['-mloginandnotfailed'])#运行登出功能但是不运行登出成功的测试用例pytest.main(['-mlogoutandnotsuccess'])#运行登陆和登出的用例pytest.main(['-mloginorlogout'])注册、管理mark标记:当使用-m参数执行mark标记的用例时,pytest会发出告警信息“PytestUnknownMarkWarning:Unknownpytest.mark.login-isthisatypo?”,告诉你这是一个pytest未知的一个标记!为了消除告警,我们需要在pytest的配置文件中注册mark标记!注册mark标记:首先在项目根目录创建一个文件pytest.ini,这个是pytest的配置文件;然后在pytest.ini文件的markers中写入你的mark标记,冒号“:”前面是标记名称,后面是mark标记的说明,可以是空字符串;注意:pytest.ini文件中只能使用纯英文字符,绝对不能使用中文的字符(尤其是冒号和空格)![pytest]markers=    login  :'markstestsaslogin'    logout :'markstestsaslogout'    success:'markstestsassuccess'failed :'markstestsasfailed'添加之后再看,警告信息就不再有了:规范使用mark标记:注册完mark标记之后pytest便不会再告警,但是有时手残容易写错mark名,导致pytest找不到用例,一时想不开很难debug,尤其是团队协作时很容易出现类似问题,所以我们需要“addopts=--strict”参数来严格规范mark标记的使用!在pytest.ini文件中添加参数 “addopts=--strict”;[pytest]markers=    smoke  :'markstestsaslogin'    login  :'markstestsaslogin'    logout :'markstestsaslogout'    success:'markstestsassuccess'    failed :'markstestsasfailed' addopts=--strict*注意要另起一行,不要在markers中添加;添加该参数后,当使用未注册的mark标记时,pytest会直接报错:“‘xxx’notfoundin markers configurationoption”,不执行测试任务;注意:pytest.ini配置文件不支持注释,不支持注释,不支持注释…Pytest使用-多线程多进程运行一、需要安装如下环境:    pipinstallpytest-xdist或     pipinstallpytest-parallel二、对比说明:    pytest-parallel比pytst-xdist相对好用,功能支持多。    pytest-xdist不支持多线程,    而pytest-parallel支持python3.6及以上版本,如果是想做多进程并发的需要在linux平台或mac上做,windows上不起作用即(workers永远=1),如果是做多线程的Linux/Mac/Windows平台都支持,进程数为workers设置的值。三、pytest-xdist常用命令配置如下:    -n:进程数,也就是cpu个数可以指定个数,最大值为当前机器cpu个数,也可以设置为auto,自动识别cpu个数。    示例:pytest-stest_multiProcess.py-n=2   #用2个进程运行py用例文件    细节使用参照官网:https://pypi.org/project/pytest-xdist/importpytestimporttimedeftestcase_01():    time.sleep(2)print('这里是testcase_01')deftestcase_02():    time.sleep(4)print('这里是testcase_02')deftestcase_03():    time.sleep(5)print('这里是testcase_03')if__name__=='__main__':pytest.main(['-s',__file__,'-n=4'])#输出......gw0I/gw1I/gw2I/gw3Igw0[3]/gw1[3]/gw2[3]/gw3[3]...============3passedin6.81s========  #总结:非多进程:9.07s 1cpu:10.59s 2cpu:7.72s 3cpu:4.98s 4cpu:5.32s四、pytest-parallel常用配置命令如下:    –workers(optional) *:多进程运行需要加此参数, *是进程数。默认为1。    –tests-per-worker(optional) *:多线程运行,*是每个worker运行的最大并发线程数。默认为1    使用示例:    pytesttest.py--workers3:3个进程运行    pytesttest.py--tests-per-worker4:4个线程运行    pytesttest.py--workers2--tests-per-worker4:2个进程并行,且每个进程最多4个线程运行,即总共最多8个线程运行。    参考:https://pypi.org/project/pytest-parallel/代码示例:importpytestimporttimedeftestcase_01():    time.sleep(2)print('这里是testcase_01')deftestcase_02():    time.sleep(4)print('这里是testcase_02')deftestcase_03():    time.sleep(5)print('这里是testcase_03')deftestcase_04():    time.sleep(9)print('这里是testcase_04')if__name__=='__main__':    pytest.main(['-s',__file__,'--workers=1','--tests-per-worker=4']) 总结:单线程:20.08s(单个线程累计时长)2个线程:13.06   3个线程:11.05   4个线程:9.06【特别注意】:    1.pytest-parallel的workers参数在windows系统下永远是1,在linux和mac下可以取不同值。    2..pytest-parallel加了多线程处理后,最后执行时间是运行时间最长的线程的时间。    3.在windows下想用多进程的选pytst-xdist; 想用多线程的选pytest-paralle
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-29 12:23 , Processed in 0.900280 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表