Pytest----fixture高级应用

简介: Pytest----fixture高级应用

【原文链接】

一、通过request获取模块及文件中的属性

通过request可以获取到测试文件及模块的属性值,这样就可以动态的对测试文件做一些操作或者控制,本质上就是pytest中的反射

如下在test_demo.py的代码,其中定义了一个变量name

name="张无忌"

def test_func3():
    assert 10==9

如下为conftest.py代码,这里定义了一个module级的fixture,在这个fixture中可以获取到用例中的name属性,在实际应用中,比如就可以通过获取到的属性值进一步做一些判断或者处理等等,这里仅仅打印出来展示一下

import pytest

@pytest.fixture(autouse=True,scope="module")
def session_fixture(request):
    name=getattr(request.module,"name",None)
    print(name)

执行结果如下:

$ pytest -s
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests, configfile: pytest.ini
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 1 item                                                                                                                                                        

test_demo.py 张无忌
F

=============================================================================== FAILURES ===============================================================================
______________________________________________________________________________ test_func3 ______________________________________________________________________________

    def test_func3():
>       assert 10==9
E       assert 10 == 9

test_demo.py:6: AssertionError
======================================================================= short test summary info ========================================================================
FAILED test_demo.py::test_func3 - assert 10 == 9
========================================================================== 1 failed in 0.10s ===========================================================================

二、通过marker向fixture传值

可以通过request.node获取到marker对象,进而获取到其参数,从而可以向fixture传递此参数,具体示例如下

test_demo.py代码如下:

import pytest


@pytest.fixture()
def fixt(request):
    marker = request.node.get_closest_marker("fixt_data")
    if marker is None:
        data = None
    else:
        data = marker.args[0]
    return data


@pytest.mark.fixt_data(42)
def test_fixt(fixt):
    assert fixt == 42

执行结果为:

$ pytest
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests, configfile: pytest.ini
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 1 item                                                                                                                                                        

test_demo.py .                                                                                                                                                    [100%]

=========================================================================== warnings summary ===========================================================================
test_demo.py:14
  D:\src\blog\tests\test_demo.py:14: PytestUnknownMarkWarning: Unknown pytest.mark.fixt_data - is this a typo?  You can register custom marks to avoid this warning - for
 details, see https://docs.pytest.org/en/stable/mark.html
    @pytest.mark.fixt_data(42)

-- Docs: https://docs.pytest.org/en/stable/warnings.html
===================================================================== 1 passed, 1 warning in 0.02s =====================================================================

三、将工厂函数作为fixture

可以将工厂函数作为fixture,这样可以在测试用例中很方便的构造一些对象,如下

test_demo.py代码如下:

import pytest

@pytest.fixture
def make_customer_record():
    def _make_customer_record(name):
        return {"name": name, "orders": []}

    return _make_customer_record


def test_customer_records(make_customer_record):
    customer_1 = make_customer_record("Lisa")
    customer_2 = make_customer_record("Mike")
    customer_3 = make_customer_record("Meredith")
    print("\n")
    print(customer_1)
    print(customer_2)
    print(customer_3)

执行结果如下:

$ pytest -s
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests, configfile: pytest.ini
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 1 item                                                                                                                                                        

test_demo.py

{'name': 'Lisa', 'orders': []}
{'name': 'Mike', 'orders': []}
{'name': 'Meredith', 'orders': []}
.

========================================================================== 1 passed in 0.03s ===========================================================================

四、支持参数化的fixture

定义fixture中使用params,然后再fixture中通过request.param抛出,则可以实现参数化的过程,即params中有几个参数就可以执行几个测试用例,亦即数据驱动,如下params中有三个参数,虽然这里只有一个测试函数,但从执行结果可以看出仍然显示是执行了三个测试用例

test_demo.py代码如下

import pytest

@pytest.fixture(scope="function",params=[1,2,3])
def f1(request):
    print("in f1 fixture ...")
    yield request.param

def test_func(f1):
    print(f1)
    assert f1>0

执行结果如下

$ pytest -s
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests, configfile: pytest.ini
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 3 items                                                                                                                                                       

test_demo.py in f1 fixture ...
1
.in f1 fixture ...
2
.in f1 fixture ...
3
.

========================================================================== 3 passed in 0.04s ===========================================================================

五、参数化的fixture指定用例id

如下,通过pytest --collect-only 查看id

test_demo.py的代码如下:

import pytest

@pytest.fixture(scope="function",params=[1,2,3])
def f1(request):
    print("in f1 fixture ...")
    yield request.param

def test_func(f1):
    print(f1)
    assert f1 > 0

执行结果如下

$ pytest --collect-only
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests, configfile: pytest.ini
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 3 items                                                                                                                                                       

<Module test_demo.py>
  <Function test_func[1]>
  <Function test_func[2]>
  <Function test_func[3]>

====================================================================== 3 tests collected in 0.02s ======================================================================

修改test_demo.py代码如下,通过ids设置用例id

import pytest

@pytest.fixture(scope="function",params=[1,2,3],ids=["test_01", "test_02","test_03"])
def f1(request):
    print("in f1 fixture ...")
    yield request.param

def test_func(f1):
    print(f1)
    assert f1 > 0

然后执行,结果如下,可以发现,此时用例id已经发生了变化

$ pytest --collect-only
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.7, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: D:\src\blog\tests, configfile: pytest.ini
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 3 items                                                                                                                                                       

<Module test_demo.py>
  <Function test_func[test_01]>
  <Function test_func[test_02]>
  <Function test_func[test_03]>

====================================================================== 3 tests collected in 0.03s ======================================================================

六、参数化的fixture指定某个参数使用skip标记

在使用参数的fixture的时候,对一些被测数值也可以像普通的测试函数一样使用skip标记跳过,如下

test_demo.py代码如下:

import pytest

@pytest.fixture(scope="function",params=[1,2,pytest.param(3,marks=pytest.mark.skip)])
def f1(request):
    print("in f1 fixture ...")
    yield request.param

def test_func(f1):
    print(f1)
    assert f1 > 0

执行结果如下:

$ pytest -v
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.6, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 -- D:\python39\python.exe
cachedir: .pytest_cache
hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase('G:\\src\\blog\\tests\\.hypothesis\\examples')
rootdir: G:\src\blog\tests
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 3 items                                                                                                                                                       

test_demo.py::test_func[1] PASSED                                                                                                                                 [ 33%]
test_demo.py::test_func[2] PASSED                                                                                                                                 [ 66%]
test_demo.py::test_func[3] SKIPPED (unconditional skip)                                                                                                           [100%]

===================================================================== 2 passed, 1 skipped in 0.19s =====================================================================

七、调用参数化的fixture可以实现两组数据的全排列组合测试数据

当在一个测试函数中调用两个参数化的fixture,可以实现两组参数的全排列组合,比如一个参数列表为[1,2],另一个fixture的参数列表为[10,20],当一个测试函数同时调用此两个fixture时,则会产生[(1,10),(1,20),(2,10),(2,20)]这样四组被测数据

test_demo.py代码如下

import pytest

@pytest.fixture(scope="function",params=[1,2])
def f1(request):
    print("in f1 fixture setup...")
    yield request.param
    print("in f1 fixture teardown...")

@pytest.fixture(scope="function",params=[10,20])
def f2(request):
    print("in f2 fixture setup...")
    yield request.param
    print("in f2 fixture teardown...")

def test_func(f1,f2):
    assert f2<f1

执行结果如下:

$ pytest -s
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.6, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: G:\src\blog\tests
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 4 items                                                                                                                                                       

test_demo.py in f1 fixture setup...
in f2 fixture setup...
Fin f2 fixture teardown...
in f1 fixture teardown...
in f1 fixture setup...
in f2 fixture setup...
Fin f2 fixture teardown...
in f1 fixture teardown...
in f1 fixture setup...
in f2 fixture setup...
Fin f2 fixture teardown...
in f1 fixture teardown...
in f1 fixture setup...
in f2 fixture setup...
Fin f2 fixture teardown...
in f1 fixture teardown...


=============================================================================== FAILURES ===============================================================================
___________________________________________________________________________ test_func[1-10] ____________________________________________________________________________

f1 = 1, f2 = 10

    def test_func(f1,f2):
>       assert f2<f1
E       assert 10 < 1

test_demo.py:16: AssertionError
___________________________________________________________________________ test_func[1-20] ____________________________________________________________________________

f1 = 1, f2 = 20

    def test_func(f1,f2):
>       assert f2<f1
E       assert 20 < 1

test_demo.py:16: AssertionError
___________________________________________________________________________ test_func[2-10] ____________________________________________________________________________

f1 = 2, f2 = 10

    def test_func(f1,f2):
>       assert f2<f1
E       assert 10 < 2

test_demo.py:16: AssertionError
___________________________________________________________________________ test_func[2-20] ____________________________________________________________________________

f1 = 2, f2 = 20

    def test_func(f1,f2):
>       assert f2<f1
E       assert 20 < 2

test_demo.py:16: AssertionError
======================================================================= short test summary info ========================================================================
FAILED test_demo.py::test_func[1-10] - assert 10 < 1
FAILED test_demo.py::test_func[1-20] - assert 20 < 1
FAILED test_demo.py::test_func[2-10] - assert 10 < 2
FAILED test_demo.py::test_func[2-20] - assert 20 < 2
========================================================================== 4 failed in 0.20s ===========================================================================

八、通过usefixtures为一个测试类调用fixture

假如想为一个测试类中的每个测试函数都去调用一个fixture,则此时可以在类上通过使用@pytest.mark.usefixtures()来指定

test_demo.py代码如下:

import pytest

@pytest.fixture(scope="function")
def f1():
    print("in f1 fixture setup...")
    yield
    print("in f1 fixture teardown...")

@pytest.mark.usefixtures("f1")
class TestDemo(object):
    def test_01(self):
        print("in test_01...")
        assert 1==1

    def test_02(self):
        print("in test_02...")
        assert 1==1

    def test_03(self):
        print("in test_03...")
        assert 1==1

执行结果如下:

$ pytest -s
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.6, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: G:\src\blog\tests
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 3 items                                                                                                                                                       

test_demo.py in f1 fixture setup...
in test_01...
.in f1 fixture teardown...
in f1 fixture setup...
in test_02...
.in f1 fixture teardown...
in f1 fixture setup...
in test_03...
.in f1 fixture teardown...


========================================================================== 3 passed in 0.04s ===========================================================================

九、fixture覆盖重写

fixture的覆盖重写简单点来说就是遵循就近原则,即在测试函数中总是调用离测试函数最近的fixture,具体点说就是:

(1)内层的conftest.py中的fixture会覆盖外层conftest.py中的fixture

(2)测试模块中的fixture会覆盖conftest.py中的同名fixture

(3)参数化中的参数会覆盖测试模块中的同名fixture

目录结构如下:

tests
  |----conftest.py
  |----demo
        |----__init__.py
        |----conftest.py
        |----test_demo.py

其中tests/conftest.py内容如下:

import pytest

@pytest.fixture()
def conf_fixture():
    print("in outer conftest.py fixture...")

tests/demo/conftest.py内容如下:

import pytest

@pytest.fixture()
def conf_fixture():
    print("in inner conftest.py fixture...")

@pytest.fixture()
def module_fixture():
    print("in conftest.py module fixture...")

test_demo.py内容如下:

import pytest

@pytest.fixture()
def module_fixture():
    print("in test_demo.py module fixture...")


@pytest.fixture()
def f1():
    return "hello f1 function"

def test_01(conf_fixture):
    print("in test_01...")

def test_02(module_fixture):
    print("in test_02")

@pytest.mark.parametrize("f1",["hello zhangwuji"])
def test_03(f1):
    print("in test_03...")
    print(f1)

执行结果如下,可以看出,test_01函数调用了内层的conftest.py中的fixture,test_02函数调用了test_demo.py中的fixture,test_03函数则直接调用了参数化的参数,即均遵循就近原则

$ pytest -s
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.6, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: G:\src\blog\tests
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collected 3 items                                                                                                                                                       

demo\test_demo.py in inner conftest.py fixture...
in test_01...
.in test_demo.py module fixture...
in test_02
.in test_03...
hello zhangwuji
.

========================================================================== 3 passed in 0.07s ===========================================================================
目录
相关文章
|
测试技术
pytest conftest.py和fixture的配合使用
pytest conftest.py和fixture的配合使用
|
测试技术
pytest学习和使用9-fixture中conftest.py如何使用?
pytest学习和使用9-fixture中conftest.py如何使用?
131 0
pytest学习和使用9-fixture中conftest.py如何使用?
|
测试技术 Python
pytest学习和使用6-fixture如何使用?
pytest学习和使用6-fixture如何使用?
110 0
【pytest】(四) pytest的一些其他的运行用法
【pytest】(四) pytest的一些其他的运行用法
|
开发工具
Pytest----生成allure执行报告
Pytest----生成allure执行报告
265 0
Pytest----生成allure执行报告
|
测试技术
Pytest----fixture基础应用
Pytest----fixture基础应用
142 0
|
测试技术
Pytest----如何正确使用pytest的日志功能
Pytest----如何正确使用pytest的日志功能
515 0
|
运维 测试技术
Pytest----如何使用经典的setup和teardown
Pytest----如何使用经典的setup和teardown
140 0
|
IDE 测试技术 Linux
Pytest----如何执行pytest自动化测试脚本
Pytest----如何执行pytest自动化测试脚本
500 0