python-pytest测试框架结构

  1. pytest中的测试装置
  2. fixture 装置
    1. 在fixtures中控制setup与teardown
    2. fixture 的参数scope
    3. 通过conftest.py共享fixture
    4. 给fixture传参(params)
    5. 调用fixture的三种方法

pytest中的测试装置

<名称> <作用范围>
setup_module/teardown_module 全局模块级
setup_class/teardown_class 类级,只在类中前后运行一次
setup_function/teardown_function 函数级,每个函数前后运行一次
setup_method/teardown_method 方法级,类中每个方法前后运行一次
setup/teardown 方法级,类中每个方法前后运行一次

test_sample.py:

def setup_module():
    print("\n模块级装置 setup_module")


def teardown_module():
    print("\n模块级装置 teardown_module")


def setup_function():
    print("\n函数级装置 setup_function")


def teardown_function():
    print("\n函数级装置  teardown_function")


def test_f1():
    print("执行测试函数-1")


def test_f2():
    print("执行测试函数-2")


class TestSample(object):

    def setup_class(self):
        print("\n类级装置 setup_class")

    def teardown_class(self):
        print("\n类级装置 teardown_class")

    def setup(self):
        print("\n方法级装置 setup")

    def teardown(self):
        print("\n方法级装置 teardown")

    def test_f1_cls(self):
        print('执行测试方法-1')

    def test_f2_cls(self):
        print('执行测试方法-2')

执行用例:

pytest test_sample.py -vs

执行日志:

================================================================================ test session starts ================================================================================
platform win32 -- Python 3.8.10, pytest-5.4.2, py-1.11.0, pluggy-0.13.1 -- e:\workspace\rftestapi\venv\scripts\python.exe
cachedir: .pytest_cache
rootdir: E:\workspace\rftestapi\aitest\demo_pathlib2
plugins: repeat-0.8.0
collected 4 items                                                                                                                                                                    

test_sample.py::test_f1
模块级装置 setup_module

函数级装置 setup_function
执行测试函数-1
PASSED
函数级装置  teardown_function

test_sample.py::test_f2
函数级装置 setup_function
执行测试函数-2
PASSED
函数级装置  teardown_function

test_sample.py::TestSample::test_f1_cls
类级装置 setup_class

方法级装置 setup
执行测试方法-1
PASSED
方法级装置 teardown

test_sample.py::TestSample::test_f2_cls
方法级装置 setup
执行测试方法-2
PASSED
方法级装置 teardown

类级装置 teardown_class

模块级装置 teardown_module


================================================================================= 4 passed in 0.12s =================================================================================

从结果可以看出用例执行顺序:

  • 在函数上:
    setup_module->setup_function->执行测试函数-1->teardown_function->teardown_module

  • 在类中:
    setup_module->setup_class->setup->执行测试方法-1->teardown->teardown_class->teardown_module

fixture 装置

在fixtures中控制setup与teardown

** 通过addfinalizer注册teardown

test_sample.py:

import pytest

class TestSample(object):
    @pytest.fixture()
    def count(self,request):
        print("\ninit count")

        def fin():
            print("\nteardown count")
        request.addfinalizer(fin)      # teardown

        return 10  # provide the fixture value

    def test_answer_1(self, count):
        print('test_answer_1 get count: %s' % count)
        assert count == 10

    def test_answer_2(self, count):
        print('test_answer_2 get count: %s' % count)
        assert count == 10

** 通过yield呼唤teardown操作

test_sample.py:

import pytest


class TestSample(object):
    @pytest.fixture()
    def count(self):
        print("\ninit count")
        yield 10
        print("\nteardown count")

    def test_answer_1(self, count):
        print('test_answer_1 get count: %s' % count)
        assert count == 10

    def test_answer_2(self, count):
        print('test_answer_2 get count: %s' % count)
        assert count == 10

执行用例:

pytest test_sample.py -vs

执行日志:

================================================================================ test session starts ================================================================================
platform win32 -- Python 3.8.10, pytest-5.4.2, py-1.11.0, pluggy-0.13.1 -- e:\workspace\rftestapi\venv\scripts\python.exe
cachedir: .pytest_cache
rootdir: E:\workspace\rftestapi\aitest\demo_pathlib2
plugins: repeat-0.8.0
collected 2 items                                                                                                                                                                    

test_sample.py::TestSample::test_answer_1
init count
test_answer_1 get count: 10
PASSED
teardown count

test_sample.py::TestSample::test_answer_2
init count
test_answer_2 get count: 10
PASSED
teardown count


================================================================================= 2 passed in 0.08s =================================================================================

通过@pytest.fixture()注释会在执行测试用例之前初始化操作.然后直接在测试用例的方法中就可以拿到初始化返回的参数(参数名要和初始化的方法名一样)

在这个实例中,初始化的方法名为count,初始化后返回 10。当在测试用例方法中传入与初始化方法名同为count的参数时,相对于传入参数count=10

  • 在fixture方法中, request.addfinalizer(),会将某函数作为teardown,在用例执行之后执行。
  • 在fixture方法中, yield 会返回setup的值,yield之后的语句作为teardown执行。

fixture 的参数scope

fixture 有一个域(scope)的概念,用来指定该 fixture 的使用范围:session > module > class > function
它有五种作用域,如session/module/class/function(default).

  • function:每个测试函数之前执行一次
  • class:每个测试类之前执行一次
  • module:每个module之前执行一次
  • session:每次session之前执行一次,即每次测试执行一次

通过conftest.py共享fixture

如果一个fixture需要共享,不用在每个py模块中写一遍,写在conftest.py文件中就好。

conftest.py同级目录,或者其子目录中的模块,均可以直接引用conftest.py中使用@pytest.fixture装饰的函数,无需显式import

conftest.py

import pytest


@pytest.fixture(scope="session")
def count():
    print("init count")
    yield 10
    print("teardown count")

test_sample.py:

class TestSample(object):
    def test_answer_1(self, count):
        print('test_answer_1 get count: %s' % count)
        assert count == 10

    def test_answer_2(self, count):
        print('test_answer_2 get count: %s' % count)
        assert count == 10

给fixture传参(params)

import pytest


@pytest.fixture(scope="module",params=["admin1","admin2"])
def login(request):
    user = request.param
    print("使用账户[%s]进行登录..." % user)
    return user + "_logined"


def test_login(login):
    '''登录用例'''
    ret = login
    print("login的返回值:%s" % ret)
    assert "logined" in ret

import pytest


@pytest.fixture(scope="module")
def login(request):
    user = request.param
    print("使用账户[%s]进行登录..." % user)
    return user + "_logined"

# indirect=True 声明login是个函数
@pytest.mark.parametrize("login", ["admin1", "admin2"], indirect=True)
def test_login(login):
    '''登录用例'''
    ret = login
    print("login的返回值:%s" % ret)
    assert "logined" in ret

添加indirect=True参数是为了把login当成一个函数去执行,而不是一个参数

执行用例:

pytest test_sample.py -vs

执行日志:

test_sample.py::test_login[admin1] 使用账户[admin1]进行登录...
login的返回值:admin1_logined
PASSED
test_sample.py::test_login[admin2] 使用账户[admin2]进行登录...
login的返回值:admin2_logined
PASSED

================================================================================= 2 passed in 0.06s =================================================================================

调用fixture的三种方法

  • 函数或类里面方法直接传fixture的函数参数名称
  • 使用装饰器@pytest.mark.usefixtures()修饰
  • autouse=True自动使用
import pytest


@pytest.fixture(scope="session", params=["admin1", "admin2"])
def login_session(request):
    user = request.param
    print("使用账户[%s]进行登录..." % user)
    return user + "_logined"


@pytest.fixture(scope="class", params=["admin3", "admin4"])
def login_cls(request):
    user = request.param
    print("使用账户[%s]进行登录..." % user)
    return user + "_logined"


#  autouse=True 会自动执行,不需要传入log参数
@pytest.fixture(scope="function", autouse=True)
def log(request):
    print("开始执行用例:function:%s " % request.function.__name__)


# 用例传fixture参数,可获得fixture返回值
def test_login(login_session):
    """登录用例"""
    ret = login_session
    print("login的返回值:%s" % ret)
    assert "logined" in ret


# usefixture 无法获取到fixture返回值
@pytest.mark.usefixtures("login_cls")
class TestCls:
    def test_01(self):
        print('-----------用例01--------------')

    def test_02(self):
        print('-----------用例02--------------')

执行结果 pytest -vs

collected 6 items                                                                                                                                                                    

test_sample.py::test_login[admin1] 使用账户[admin1]进行登录...
开始执行用例:function:test_login
login的返回值:admin1_logined
PASSED

test_sample.py::test_login[admin2] 使用账户[admin2]进行登录...
开始执行用例:function:test_login
login的返回值:admin2_logined
PASSED

test_sample.py::TestCls::test_01[admin3] 使用账户[admin3]进行登录...
开始执行用例:function:test_01
-----------用例01--------------
PASSED

test_sample.py::TestCls::test_02[admin3] 开始执行用例:function:test_02
-----------用例02------------
PASSED

test_sample.py::TestCls::test_01[admin4] 使用账户[admin4]进行登录...
开始执行用例:function:test_01
-----------用例01--------------
PASSED

test_sample.py::TestCls::test_02[admin4] 开始执行用例:function:test_02
-----------用例02------------
PASSED

================================================================================= 6 passed in 0.19s =================================================================================

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。
My Show My Code