美文网首页
pytest测试框架(三)

pytest测试框架(三)

作者: 在下YM | 来源:发表于2022-02-21 15:31 被阅读0次

    pytest.fixture

    • setup和teardown函数能够在测试用例之前或者之后添加一些操作,但这种是整个脚本全局生效的。
    • 如果我们想实现以下场景:用例1需要登录,用例2不需要登录,用例3需要登录,这就无法直接用setup和teardown来同一个类中实现,却可以通过pytest.fixture实现。

    fixture基本使用

    fixture是pytest特有的功能,它以装饰器形式定义在函数上面, 在编写测试函数的时候,可以将被fixture装饰的函数的名字做为测试函数的参数,运行测试脚本时,执行测试函数时就会自动传入被fixture装饰的函数的返回值。

    import pytest
    import requests
    
    # 0.@pytest.fixture装饰函数
    @pytest.fixture()
    def get_web_url():
        print('get_web_url')
        return 'https://www.baidu.com'
    
    # 1. 把上面函数名作为测试用例的参数
    def test_web(get_web_url):
        # 2. 测试用例调用前,需要先确定形参get_web_url,就是调用get_web_url
        print('test_web')
        print(get_web_url) # 测试用例内部使用get_web_url,就是使用它返回值
        r = requests.get(get_web_url)
        assert r.status_code == 200, '测试成功'
    

    运行结果如下:

    plugins: tavern-1.16.3, openfiles-0.3.2, arraydiff-0.3, allure-pytest-2.9.45, doctestplus-0.3.0, remotedata-0.3.1
    collected 1 item                                                                                                                                          
    
    pytest_code5.py get_web_url
    test_web
    https://www.baidu.com
    .
    
    ==================================================================== 1 passed in 0.19s ====================================================================
    

    conftest.py文件

    共享fixture函数

    • 如果在测试中多个测试文件中用例用到同一个的fixture函数,则可以将其移动到conftest.py文件中,所需的fixture对象会自动被pytest发现,而不需要再每次导入

    • conftest.py文件名固定

    • 在conftest.py文件中实现共用的fixture函数

    conftest.py内容如下:

    # pytest_fixture/conftest.py 文件名不能改变,否则无效
    import pytest
    
    # 默认是function级别的
    @pytest.fixture()
    def login_fixture():
        """可以把函数作为参数传递"""
        print("\n公用的登陆方法")
    

    test_fixture1.py内容如下:

    # pytest_fixture/test_fixture1.py
    import pytest
    
    def test_get_carts():
        """购物车不需要登陆"""
        print("\n测试查询购物车,无需登录")
    
    
    class TestFixtures(object):
        """需要登陆的信息"""
        def test_get_user_info(self, login_fixture):
            print("获取用户信息")
    
        def test_order_info(self, login_fixture):
            print("查询订单信息")
    
    def test_logout(login_fixture):
        """登出"""
        print("退出登录")
    
    if __name__ == '__main__':
        pytest.main(['-s', 'test_fixture1.py'])
    

    运行结果如下:

    test_fixture1.py .
    测试查询购物车,无需登录
    
    公用的登陆方法
    .获取用户信息
    
    公用的登陆方法
    .查询订单信息
    
    公用的登陆方法
    .退出登录
                                                        [100%]
    
    ============================== 4 passed in 0.03s ===============================
    

    pytest.mark.usefixtures

    • 可以使用@pytest.mark.usefixtures('fixture函数名字符串')来装饰测试类和测试方法
      test_fixture2.py内容如下:
    # pytest_fixture/test_fixture2.py
    import pytest
    
    def test_get_carts():
        """购物车不需要登陆"""
        print("\n测试查询购物车,无需登录")
    
    @pytest.mark.usefixtures('login_fixture')
    class TestFixtures(object):
        """需要登陆的信息"""
        def test_get_user_info(self):
            print("获取用户信息")
    
        def test_order_info(self):
            print("查询订单信息")
    
    @pytest.mark.usefixtures('login_fixture')
    def test_logout():
        """登出"""
        print("退出登录")
    
    if __name__ == '__main__':
        pytest.main(['-s', 'test_fixture2.py'])
    

    右键运行如下:

    test_fixture2.py .
    测试查询购物车,无需登录
    
    公用的登陆方法
    .获取用户信息
    
    公用的登陆方法
    .查询订单信息
    
    公用的登陆方法
    .退出登录
                                                        [100%]
    
    ============================== 4 passed in 0.03s ===============================
    

    pytest.fixture参数

    pytest.fixture(scope='function', params=None, autouse=False, ids=None, name=None)
    
    • scope: 被标记方法的作用域, 可以传入以下四个值;
      -- "function": 默认值,每个测试用例都要执行一次 fixture 函数
      -- "class": 作用于整个类, 表示每个类只运行一次 fixture 函数
      -- "module": 作用于整个模块, 每个 module 的只执行一次 fixture 函数
      -- "session": 作用于整个 session , 一次 session 只运行一次 fixture
    • params: list 类型,默认 None, 接收参数值,对于 param 里面的每个值,fixture 都会去遍历执行一次。
    • autouse: 是否自动运行,默认为 false, 为 true 时此 session 中的所有测试函数都会调用 fixture

    scope参数

    • function:设置为function,表示每个测试方法都要执行一次
    # function:设置为function,表示每个测试方法都要执行一次
    import pytest
    
    @pytest.fixture(scope='function')
    # @pytest.fixture() # 和上面等价
    def foo():
        print('foo')
    
    
    def test_1(foo):
        print('普通测试用例111111')
    
    def test_2():
        print('普通测试用例22222')
    
    
    class TestClass(object):
        def test_one(self, foo):
            print('类实例方法测试用例111111')
    
        def test_two(self, foo):
            print('类实例方法测试用例22222')
    

    运行如下:

    test_9_scope_function.py foo
    普通测试用例111111
    .普通测试用例22222
    .foo
    类实例方法测试用例111111
    .foo
    类实例方法测试用例22222
    .
    
    ==================================================================== 4 passed in 0.03s ====================================================================
    
    • class:设置为 class 时代表这个类中只会执行一次
    import pytest
    
    @pytest.fixture(scope='class')
    def foo():
        print('foo')
    
    
    def test_1(foo):
        print('普通测试用例111111')
    
    def test_2(foo):
        print('普通测试用例22222')
    
    
    class TestClass(object):
        def test_one(self, foo):
            print('类实例方法测试用例111111')
    
        def test_two(self, foo):
            print('类实例方法测试用例22222')
    

    运行如下:

    test_10_scope_class.py foo
    普通测试用例111111
    .foo
    普通测试用例22222
    .foo
    类实例方法测试用例111111
    .类实例方法测试用例22222
    .
    
    ==================================================================== 4 passed in 0.03s ====================================================================
    
    • module:设置为 module 时代表这个模块中只会执行一次
    • session:整个 session 都只会执行一次
    # module:只会在最开始的时候传入参数执行1次
    # session:只会在session开始传入参数的时候执行1次
    import pytest
    
    @pytest.fixture(scope='module')
    # @pytest.fixture(scope='session')
    def foo():
        print('foo')
    
    
    def test_1(foo):
        print('普通测试用例111111')
    
    def test_2(foo):
        print('普通测试用例22222')
    
    
    class TestClass(object):
        def test_one(self, foo):
            print('类实例方法测试用例111111')
    
        def test_two(self, foo):
            print('类实例方法测试用例22222')
    

    运行如下:

    test_11_scope_module.py foo
    普通测试用例111111
    .普通测试用例22222
    .类实例方法测试用例111111
    .类实例方法测试用例22222
    .
    
    ==================================================================== 4 passed in 0.03s ====================================================================
    

    params参数

    • pytest.fixture(params=None) 的params参数接收list类型的参数
    • 对于param里面的每个值,fixture函数都会去遍历执行一次
    • 相应的每次都会驱动使用fixture函数的测试函数执行一次。
    import pytest
    
    
    def check_password(password):
        """
        检查密码是否合法
    
        :param password: 长度是 8 到 16
        :return:
        """
        pwd_len = len(password)
        if pwd_len < 8:
            return False
        elif pwd_len > 16:
            return False
        else:
            return True
    
    
    @pytest.fixture(params=['1234567', '12345678', '123456789', '123456789012345', '1234567890123456', '12345678901234567'])
    def password(request):
        return request.param
    
    
    def test_check_password(password):
        print(password)
        print(check_password(password))
    

    运行如下:

    test_13_params.py 1234567
    False
    .12345678
    True
    .123456789
    True
    .123456789012345
    True
    .1234567890123456
    True
    .12345678901234567
    False
    .
    
    ==================================================================== 6 passed in 0.03s ====================================================================
    
    import pytest
    
    
    @pytest.fixture(params=['admin', 'zhangsan', 'lisi'])
    def username(request):
        return request.param
    
    
    @pytest.fixture(params=['1234567', '12345678', '123456789', '123456789012345', '1234567890123456', '12345678901234567'])
    def password(request):
        return request.param
    
    
    def test_check_regist(username, password):
        print(username, '=====', password)
    
    if __name__ == '__main__':
        pytest.main(['-s', 'test_14_params2.py'])
    

    运行如下:

    test_14_params2.py                                     [100%]
    
    ============================== 18 passed in 0.06s ==============================
    
    Process finished with exit code 0
    .admin ===== 1234567
    .admin ===== 12345678
    .admin ===== 123456789
    .admin ===== 123456789012345
    .admin ===== 1234567890123456
    .admin ===== 12345678901234567
    .zhangsan ===== 1234567
    .zhangsan ===== 12345678
    .zhangsan ===== 123456789
    .zhangsan ===== 123456789012345
    .zhangsan ===== 1234567890123456
    .zhangsan ===== 12345678901234567
    .lisi ===== 1234567
    .lisi ===== 12345678
    .lisi ===== 123456789
    .lisi ===== 123456789012345
    .lisi ===== 1234567890123456
    .lisi ===== 12345678901234567
    

    autouse参数

    pytest.fixture(autouse=False) 的autouse参数默认为False, 不会自动执行;设置为True时,当前运行的所有测试函数在运行前都会执行fixture函数

    import pytest
    
    
    @pytest.fixture(autouse=True)
    def before():
        print('\nbefore each test')
    
    class Test2:
        def test_1(self):
            print('test_5')
    
        def test_2(self):
            print('test_6')
    

    运行如下:

    test_15_autouse.py 
    before each test
    test_5
    .
    before each test
    test_6
    .
    
    ==================================================================== 2 passed in 0.03s ====================================================================
    

    pytest.mark标记

    pytest.mark下提供了标记装饰器,除了之前我们使用的pytest.mark.usefixtures()装饰器以外,还有一些常用的标记装饰器

    装饰器 作用
    pytest.mark.xfail() 将测试函数标记为预期失败。
    pytest.mark.skip() 无条件地跳过测试函数
    pytest.mark.skipif() 有条件地跳过测试函数
    pytest.mark.parametrize() 参数化Fixture方法和测试函数。
    pytest.mark.usefixtures() 使用类、模块或项目中的Fixture方法。

    标志预期失效

    • 要测试的功能或者函数还没有实现,这个时候执行测试一定是失败的。我们通过 xfail 来标记某个测试方法一定会失败
    • xfail(condition=True, reason=None, raises=None, run=True, strict=False)
      -- condition:标记预期失败的条件,如果条件为 False,那么这个标记无意义
      -- reason:标记预期失败的原因说明
    import pytest
    
    class Test_ABC:
        def setup_class(self):
            print("\nsetup")
    
        def teardown_class(self):
            print("\nteardown")
    
        def test_a(self):
            print("\ntest_a")
    
        @pytest.mark.xfail(condition=False, reason="预期失败")
        def test_b(self):
            print("\ntest_b")
            assert 0
    
        @pytest.mark.xfail(condition=True, reason="预期失败")
        def test_c(self):
            print("\ntest_c")
            assert 0
    
    if __name__ == '__main__':
        pytest.main(['-s', 'test_22.py'])
    

    运行如下:

    test_22.py 
    setup
    .
    test_a
    F
    test_b
    
    test_22.py:12 (Test_ABC.test_b)
    self = <test_22.Test_ABC object at 0x7fc2295bfc18>
    
        @pytest.mark.xfail(condition=False, reason="预期失败")
        def test_b(self):
            print("\ntest_b")
    >       assert 0
    E       assert 0
    
    test_22.py:16: AssertionError
    x
    test_c
    
    self = <test_22.Test_ABC object at 0x7fc2295bfef0>
    
        @pytest.mark.xfail(condition=True, reason="预期失败")
        def test_c(self):
            print("\ntest_c")
    >       assert 0
    E       assert 0
    
    test_22.py:21: AssertionError
    
    teardown
    
    Assertion failed
                                                               [100%]
    
    =================================== FAILURES ===================================
    _______________________________ Test_ABC.test_b ________________________________
    
    self = <test_22.Test_ABC object at 0x7fc2295bfc18>
    
        @pytest.mark.xfail(condition=False, reason="预期失败")
        def test_b(self):
            print("\ntest_b")
    >       assert 0
    E       assert 0
    
    test_22.py:16: AssertionError
    ----------------------------- Captured stdout call -----------------------------
    
    test_b
    =========================== short test summary info ============================
    FAILED test_22.py::Test_ABC::test_b - assert 0
    ==================== 1 failed, 1 passed, 1 xfailed in 0.08s ====================
    

    跳过测试函数

    无条件跳过

    使用场景: 根据特定条件、不执行标识的测试函数

    • skip(reason=None)
      ---reason: 标注原因
    import pytest
    import pytest
    
    
    class Test_ABC:
        def setup_class(self):
            print("\nsetup")
    
        def teardown_class(self):
            print("\nteardown")
    
        def test_a(self):
            print("test_a")
    
        # 开启跳过标记
        @pytest.mark.skip(reason="无条件跳过不执行,就是任性顽皮")
        def test_b(self):
            print("test_b")
    
    
    if __name__ == '__main__':
        pytest.main(['-s', 'test_23.py'])
    

    运行效果如下:

    test_23.py 
    setup
    .test_a
    s
    Skipped: 无条件跳过不执行,就是任性顽皮
    
    teardown
                                                                [100%]
    
    ========================= 1 passed, 1 skipped in 0.03s =========================
    

    有条件跳过

    使用场景: 根据特定条件、不执行标识的测试函数

    • skipif(condition, reason=None)
      --- condition: 跳过的条件,必传参数
      --- reason: 标注原因
    import pytest
    
    class Test_ABC:
        def setup_class(self):
            print("\nsetup")
    
        def teardown_class(self):
            print("\nteardown")
    
        def test_a(self):
            print("test_a")
    
        # 开启跳过标记
        @pytest.mark.skipif(condition=1, reason="有条件跳过不执行,依旧任性顽皮")
        # @pytest.mark.skipif(condition=0, reason="条件跳不成立,无法跳过")
        def test_b(self):
            print("test_b")
    
    
    if __name__ == '__main__':
        pytest.main(['-s', 'test_24.py'])
    

    运行效果如下:

    test_24.py 
    setup
    .test_a
    s
    Skipped: 有条件跳过不执行,依旧任性顽皮
    
    teardown
                                                                [100%]
    
    ========================= 1 passed, 1 skipped in 0.03s =========================
    

    参数化

    • 使用场景:需要测试一组不同的数据,而测试过程是一样的,这种情况下我们可以写一个测试方法,并且测试方法通过参数接受数据。通过遍历数据并且调用测试方法来完成测试。

    • 作用: 参数化fixture方法和测试函数, 方便测试函数对测试属性的获取。

    • parametrize(argnames, argvalues, indirect=False, ids=None, scope=None)

    --- argnames:参数名, 以逗号分隔的字符串,表示一个或多个参数名称,或参数字符串的列表/元组. 参数名为几个,就会运行几次。
    --- argvalues:
    ----参数对应值,类型必须为 list
    ----当参数为一个时,参数格式:[value1,value2,...]
    ----当参数个数大于一个时,格式为: [(param_value1,param_value2),...]

    import pytest
    
    
    class Test_ABC:
        def setup_class(self):
            print("setup")
    
        def teardown_class(self):
            print("teardown")
    
        def test_a(self):
            print("test_a")
    
        @pytest.mark.parametrize("a", [3, 6])
        def test_b(self, a):
            print(f"test_b data:a={a}")
    
    
        @pytest.mark.parametrize(["a","b"],[(1,2),(3,4)])
        def test_c(self, a, b):
            print(f"test_c a: {a}; b: {b}")
    
    if __name__ == '__main__':
        pytest.main(['-s', 'test_25.py'])
    

    运行效果如下

    test_25.py                                                          [100%]
    
    ============================== 5 passed in 0.04s ===============================
    
    Process finished with exit code 0
    setup
    .test_a
    .test_b data:a=3
    .test_b data:a=6
    .test_c a: 1; b: 2
    .test_c a: 3; b: 4
    teardown
    

    相关文章

      网友评论

          本文标题:pytest测试框架(三)

          本文链接:https://www.haomeiwen.com/subject/dzhilrtx.html