zoukankan      html  css  js  c++  java
  • 全功能Python测试框架:pytest

    python通用测试框架大多数人用的是unittest+HTMLTestRunner,这段时间看到了pytest文档,发现这个框架和丰富的plugins很好用,所以来学习下pytest.

     

    pytest介绍:

    pytest是一个非常成熟的全功能的Python测试框架,主要有以下几个特点:

    • 简单灵活,容易上手
    • 支持参数化
    • 能够支持简单的单元测试和复杂的功能测试,还可以用来做selenium/appnium等自动化测试、接口自动化测试(pytest+requests)
    • pytest具有很多第三方插件,并且可以自定义扩展,比较好用的如pytest-selenium(集成selenium)、pytest-html(完美html测试报告生成)、pytest-rerunfailures(失败case重复执行)、pytest-xdist(多CPU分发)等
    • 测试用例的skip和xfail处理
    • 可以很好的和jenkins集成
    • report框架----allure 也支持了pytest

    ####

    安装pytest:

    pip install -U pytest

     

    验证安装的版本:

    pytest --version

    ####

     

    几个pytest documentation中的例子:

    例子1:

    1.创建test_sample.py文件,创建一个方法、一个用例

    import pytest
    
    # content of test_sample.py
    def func(x):
        return x + 1
    def test_answer():
        assert func(3) == 5
     

    命令行切换到文件所在目录,执行测试(也可以直接在IDE中运行):

    命令:pytest

    这个测试返回一个失败报告,因为func(3)不返回5。

    ####

    例子2:

    当需要编写多个测试样例的时候,我们可以将其放到一个测试类当中,如:

    创建test_sample2.py文件

    class TestClass:  
        def test_one(self):  
            x = "this"  
            assert 'h' in x  
      
        def test_two(self):  
            x = "hello"  
            assert hasattr(x, 'check') 

    运行以上例子:

    pytest -q test_sample2.py

     

    从测试结果中可以看到,该测试共执行了两个测试样例,一个失败一个成功。

    同样,我们也看到失败样例的详细信息,和执行过程中的中间结果。-q即-quiet,作用是减少冗长,具体就是不再展示pytest的版本信息。

    ####

    如何编写pytest测试样例

    通过上面2个实例,我们发现编写pytest测试样例非常简单,只需要按照下面的规则:

    • 测试文件以test_开头(以_test结尾也可以)
    • 测试类以Test开头,并且不能带有 init 方法
    • 测试函数以test_开头
    • 断言使用基本的assert即可

    运行模式

    Pytest的多种运行模式,让测试和调试变得更加得心应手,下面介绍5种常用的模式。

    在介绍之前需要提醒一句,运行pytest时会找当前目录及其子目录中的所有test_*.py 或 *_test.py格式的文件以及以test开头的方法或者class,不然就会提示找不到可以运行的case了。

    1.运行后生成测试报告(htmlReport)

    安装pytest-html:

    pip install pytest-html

     

    运行模式:

    pytest --html=report.html

     

    报告效果还可以

    在以上报告中可以清晰的看到测试结果和错误原因,定位问题很容易。

    2.运行指定的case

    当我们写了较多的cases时,如果每次都要全部运行一遍,无疑是很浪费时间的,通过指定case来运行就很方便了。

    例子代码:

    新建test_sample3.py

    class TestClassOne(object):
        def test_one(self):
            x = "this"
            assert 't'in x
    
        def test_two(self):
            x = "hello"
            assert hasattr(x, 'check')
    
    
    class TestClassTwo(object):
        def test_one(self):
            x = "iphone"
            assert 'p'in x
    
        def test_two(self):
            x = "apple"
            assert hasattr(x, 'check')

    运行模式:

    模式1:直接运行test_se.py文件中的所有cases:

    pytest test_sample3.py

     

    模式2:运行ttest_sample3.py文件中的TestClassOne这个class下的两个cases:

    pytest test_sample3.py::TestClassOne

    模式3:运行test_sample3.py文件中的TestClassTwo这个class下的test_one:

    pytest test_sample3.py::TestClassTwo::test_one

     

    注意:定义class时,需要以T开头,不然pytest是不会去运行该class的。

    3.多进程运行cases

      当cases量很多时,运行时间也会变的很长,如果想缩短脚本运行的时长,就可以用多进程来运行。

    安装pytest-xdist:

    pip install -U pytest-xdist

     

    运行模式:

    pytest test_se.py -n NUM

    其中NUM填写并发的进程数。

    4.重试运行cases

      在做接口测试时,有事会遇到503或短时的网络波动,导致case运行失败,而这并非是我们期望的结果,此时可以就可以通过重试运行cases的方式来解决。

    安装pytest-rerunfailures:

    pip install -U pytest-rerunfailures

    运行模式:

    pytest test_se.py --reruns NUM

    NUM填写重试的次数。

    5.显示print内容

      在运行测试脚本时,为了调试或打印一些内容,我们会在代码中加一些print内容,但是在运行pytest时,这些内容不会显示出来。如果带上-s,就可以显示了。

    运行模式:

    pytest test_se.py -s

    另外,pytest的多种运行模式是可以叠加执行的,比如说,你想同时运行4个进程,又想打印出print的内容。可以用:

    pytest test_se.py -s -n 4

    其他运行方法

    执行目录下所有用例


    pytest testcase/

    ####

    pytest参数

    1、-K EXPRESSION

    执行某个关键字的用例

    用例要匹配给出的表达式;使用python的语法,匹配的范围是文件名、类名、函数名为变量,用and来区分

    运行pytest时带-k参数

    pytest -k "test and TestClass and not test_a" test.py

    可以看出,test_a这个用例被取消选择了,没有运行了

    3、--maxfail=num

    当错误个数到达给定数时,退出测试,这里就不列举实例了,结果与-x类似

    4、-m MARKEXPR

    只能运行有相应标识的测试用例,使用这个参数,测试用例要使用@pytest.mark.marker修饰

    # content of test.py
    import pytest
    class TestClass(object):
         def test_one(self):
             '''new_etests'''
             x = "this"
             assert 'h' in x
     
         @pytest.mark.slow
         def test_two(self):
             '''new_sssetests'''
             x = "hello"
             assert hasattr(x, 'check')
             
         def test_a(self):
             assert 1==2

    teste_two使用了@pytest.mark.slow来修饰

    在使用时,使用如下参数

    pytest –m slow test.py

    从上图中可以看出,只运行了一个我们带有标识的用例。

    注意,-m后面不能带''号(单引号),只能带“”(双引号),不然识别不到

    如果要运行多个标识的话,用表达式,如下

    pytest -m "slow or faster" 运行有slow标识或 faster标识用例

    pytest -m "slow and faster" 运行有slow和faster标识的用例

    pytest -m "slow and not faster" 运行有slow和没有faster标识的用例

    5、 -v, --verbose
    详细结果

    6、-q, --quiet
    极简结果显示,简化控制台的输出,可以看出输出信息和之前不添加-q不信息不一样, 下图中有两个..点代替了pass结果

    7、-s
    输入我们用例中的调式信息,比如print的打印信息等,我们在用例中加上一句 print(driver.title),我们再运行一下我们的用例看看,调试信息输出

    8、-V
    可以输出用例更加详细的执行信息,比如用例所在的文件及用例名称等

    9、--junit-xml=path
    输出xml文件格式,在与jenkins做集成时使用

    10、 --result-log=path
    将最后的结果保存到本地文件中

    注意:标黄的是经常使用的

    ####

    pytest fixture 这个很重要!

    1、fixture scope的范围参数
    之前使用@pytest.fixture(scope='module')来定义框架,scope的参数有以下几种

    function 每一个用例都执行
    class 每个类执行
    module 每个模块执行(函数形式的用例)
    session 每个session只运行一次,在自动化测试时,登录步骤可以使用该session

    2、调用fixture的三种方法
    2.1函数或类里面方法直接传fixture的函数参数名称

    from __future__ import print_function
    import pytest
     
    @pytest.fixture(scope='module')
    def resource_a_setup(request):
        print('\nresources_a_setup()')
        def resource_a_teardown():
            print('\nresources_a_teardown()')
        request.addfinalizer(resource_a_teardown)
     
    def test_1(resource_a_setup):
        print('test_1()')
     
    def test_2():
        print('\ntest_2()')
     
    def test_3(resource_a_setup):
        print('\ntest_3()')

    运行

    pytest -s -v test_sample4.py

    这是setup teardown的pytest的用法

    2.2使用装饰器@pytest.mark.usefixtures()修饰需要运行的用例

    import pytest
    
    
    # test_fixture1.py
    
    
    @pytest.fixture()
    def test1():
        print('\n开始执行function')
    
    
    @pytest.mark.usefixtures('test1')
    def test_a():
        print('---用例a执行---')
    
    
    @pytest.mark.usefixtures('test1')
    class TestCase:
    
        def test_b(self):
            print('---用例b执行---')
    
        def test_c(self):
            print('---用例c执行---')
    
    
    if __name__ == '__main__':
        pytest.main(['-s', 'test_sample5.py'])

    运行

    pytest -s -v test_sample4.py

    这是在每一个用例的前面都加了一下处理

    2.3叠加usefixtures
    如果一个方法或者一个class用例想要同时调用多个fixture,可以使用@pytest.mark.usefixture()进行叠加。注意叠加顺序,先执行的放底层,后执行的放上层

    import pytest
    # test_fixture1.py
     
     
    @pytest.fixture()
    def test1():
        print('\n开始执行function1')
     
     
    @pytest.fixture()
    def test2():
        print('\n开始执行function2')
     
     
    @pytest.mark.usefixtures('test1')
    @pytest.mark.usefixtures('test2')
    def test_a():
        print('---用例a执行---')
     
     
    @pytest.mark.usefixtures('test2')
    @pytest.mark.usefixtures('test1')
    class TestCase:
     
        def test_b(self):
            print('---用例b执行---')
     
        def test_c(self):
            print('---用例c执行---')
     
     
    if __name__ == '__main__':
        pytest.main(['-s', 'test_fixture1.py'])

    3.usefixtures与传fixture区别
    如果fixture有返回值,那么usefixture就无法获取到返回值,这个是装饰器usefixture与用例直接传fixture参数的区别。

    当fixture需要用到return出来的参数时,只能讲参数名称直接当参数传入,不需要用到return出来的参数时,两种方式都可以。

    4.fixture自动使用autouse=True
    当用例很多的时候,每次都传这个参数,会很麻烦。fixture里面有个参数autouse,默认是False没开启的,可以设置为True开启自动使用fixture功能,这样用例就不用每次都去传参了

    autouse设置为True,自动调用fixture功能

    import pytest
    # test_fixture1.py
     
     
    @pytest.fixture(scope='module', autouse=True)
    def test1():
        print('\n开始执行module')
     
     
    @pytest.fixture(scope='class', autouse=True)
    def test2():
        print('\n开始执行class')
     
     
    @pytest.fixture(scope='function', autouse=True)
    def test3():
        print('\n开始执行function')
     
     
    def test_a():
        print('---用例a执行---')
     
     
    def test_d():
        print('---用例d执行---')
     
     
    class TestCase:
     
        def test_b(self):
            print('---用例b执行---')
     
        def test_c(self):
            print('---用例c执行---')
     
     
    if __name__ == '__main__':
        pytest.main(['-s', 'test_fixture1.py'])

    这样就不用每一个用例去加装饰器了,清爽了很多,

    5.conftest.py的作用范围

    一个工程下可以建多个conftest.py的文件,一般在工程根目录下设置的conftest文件起到全局作用。在不同子目录下也可以放conftest.py的文件,作用范围只能在改层级以及以下目录生效。

    '''scope=session'''

    '''fixture为session级别是可以跨.py模块调用的,也就是当我们有多个.py文件的用例时候,如果多个用例只需调用一次fixture,那就可以设置为scope="session",并且写到conftest.py文件里'''

    '''conftest.py文件名称是固定的,pytest会自动识别该文件。放到工程的根目录下,就可以全局调用了,如果放到某个package包下,那就只在该package内有效'''

    '''如果想同时运行test_fixture11.py和test_fixture12.py,在cmd执行

    > pytest -s test_fixture11.py test_fixture12.py
    '''

     5.1conftest在不同的层级间的作用域不一样

    # conftest层级展示/conftest.py
     
    import pytest
     
     
    @pytest.fixture(scope='session', autouse=True)
    def login():
        print('----准备登录----')
     
     
    # conftest层级展示/sougou_login/conftest
    import pytest
     
     
    @pytest.fixture(scope='session', autouse=True)
    def bai_du():
        print('-----登录百度页面-----')
     
     
    # conftest层级展示/sougou_login/login_website
    import pytest
     
     
    class TestCase:
        def test_login(self):
            print('hhh,成功登录百度')
     
     
    if __name__ == '__main__':
        pytest.main(['-s', 'login_website.py'])

    ###

     5.2conftest是不能跨模块调用的(这里没有使用模块调用)

    # conftest层级演示/log/contfest.py
    import pytest
     
     
    @pytest.fixture(scope='function', autouse=True)
    def log_web():
        print('打印页面日志成功')
     
     
     
     
    # conftest层级演示/log/log_website.py
    import pytest
     
     
    def test_web():
        print('hhh,成功一次打印日志')
     
     
    def test_web1():
        print('hhh,成功两次打印日志')
     
     
    if __name__ == '__main__':
        pytest.main(['-s', 'log_website.py'])

    ###

    pytest之mark的使用,这个也很重要

    使用方法:

    1、注册标签名

    2、在测试用例/测试类前面加上:@pytest.mark.标签名

          打标记范围:测试用例、测试类、模块文件

    注册方式:

    1、单个标签:

    在conftest.py添加如下代码:
    def pytest_configure(config):
        # demo是标签名
        config.addinivalue_line("markers", "demo:示例运行") 

    2、多个标签:   

    在conftest.py添加如下代码:
    def pytest_configure(config):
        marker_list = ["testdemo", "demo", "smoke"]  # 标签名集合
        for markers in marker_list:
            config.addinivalue_line("markers", markers)

    3、添加pytest.ini 配置文件(在你项目的任意一个文件下,新建一个file,文件命名为pytest.ini)

        

    [pytest]
    markers=
        smoke:this is a smoke tag
        demo:demo
        testdemo:testdemo

    使用方法:

      

    复制代码
    import pytest
    
    
    @pytest.mark.testdemo
    def test_demo01():
        print("函数级别的test_demo01")
    
    
    @pytest.mark.smoke
    def test_demo02():
        print("函数级别的test_demo02")
    
    
    @pytest.mark.demo
    class TestDemo:
        def test_demo01(self):
            print("test_demo01")
    
        def test_demo02(self):
            print("test_demo02")
    复制代码

    运行方式:

      1、命令行模式

    复制代码
    通过标记表达式执行
    pytest -m demo
    这条命令会执行被装饰器@pytest.mark.demo装饰的所有测试用例
    
    生成html报告:
    pytest -m demo --html=Report/report.html
    
    生成xml报告:
    pytest -m demo --junitxml=Report/report.xml
    
    运行指定模块:
    pytest -m demo --html=Report/report.html TestCases/test_pytest.py
    
    运行指定测试目录
    pytest -m demo --html=Report/report.html TestCases/
    
    通过节点id来运行:
    pytest TestCases/test_pytest.py::TestDemo::test_demo01
    
    通过关键字表达式过滤执行
    pytest -k "MyClass and not method"
    这条命令会匹配文件名、类名、方法名匹配表达式的用例
    
    获取用例执行性能数据
    获取最慢的10个用例的执行耗时
    pytest --durations=10
    复制代码

    2、新建run.py文件运行,代码如下:

    pytest.main(["-m","demo","--html=Report/report.html"])

    ###

    ####

  • 相关阅读:
    字王谈M1字形与个人云字库
    想让网站销量爆涨?你离成功只差一个出色的购物车设计
    学习JavaScript很吃力?开发五年经验带你轻松上路!
    摹客—— 自动生成你的颜色规范
    【求职,不求人】2019最全Web设计师面试问题,助你轻松拿下面试
    交易类APP原型设计分享
    全是宝!20款优质高效的在线协作工具任你挑,就是这么强大!
    灵感专题—2019年优秀网页设计作品赏析#4月
    2019年最实用的导航栏设计实践和案例分析全解
    摹客标注:自动标注一键生成,手动标注自由补充
  • 原文地址:https://www.cnblogs.com/andy0816/p/15616573.html
Copyright © 2011-2022 走看看