pytest测试框架(三)

pytest.fixture

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

fixture基本使用

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

import pytest
import requests

# [email protected]装饰函数
@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 = 

    @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 = 

    @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 = 

    @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测试框架(三))