本文介绍了基于参数化夹具的 Py.Test 参数化的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个类范围的参数化夹具,它为其参数获取 3 个数据库并返回到每个数据库的连接.

I have a class scoped parametrized fixture that gets 3 databases for its params and returns a connection to each one.

类中的测试使用此夹具来测试每个数据库连接属性.

Tests in a class uses this fixture to test each DB connection attributes.

现在我有一个带有数据库表测试的新类,我想使用上面的夹具但要在每个连接表上进行参数化.

Now I have a new class with database tables tests that I want to use the above fixture but to be parametrized on each connection tables.

对实现这一点的 pytest 方式有什么建议吗?我找不到基于已经参数化的元素进行参数化的方法.

Any suggestion on the pytest way to implement this? I can't find a way to parametrize based on an already parametrized element.

谢谢

推荐答案

测试类用于:

  • 为测试用例提供设置和拆卸功能
  • 在测试过程中分享一些共同的价值观

使用 pytest 这不是必需的,因为可以在夹具级别完成设置和拆卸.

With pytest this is not necessary as setup and teardown can be done on fixture level.

出于这个原因,我的解决方案不使用类(但它可能与它们一起使用).

For this reason my solution does not use classes (but it could be probably used with them).

为了表明,(假)连接被创建,然后关闭观察标准输出上的输出.诀窍是使用 @pytest.yield_fixture,它不是使用 return 而是 yield 来提供在注入测试用例的参数.执行第一个 yield 语句之后的任何内容作为拆卸代码.

To show, that the (fake) connections are created and then closed watch the output on stdout. The trick isto use @pytest.yield_fixture, which is not using return but yield to provide the value used inthe parameter injected into test case. Whatever is following first yield statement is executedas teardown code.

对于 py.test 来说,第一种情况很自然,其中所有夹具变体都组合在一起.

The first case is natural to py.test, where all fixture variants are combined.

因为它有 M x N 测试用例运行,我称之为矩形".

As it has M x N test case runs, I call it "rectangle".

我的测试在 tests/test_it.py:

import pytest


@pytest.yield_fixture(scope="class", params=["mysql", "pgsql", "firebird"])
def db_connect(request):
    print("\nopening db")
    yield request.param
    print("closing db")


@pytest.fixture(scope="class", params=["user", "groups"])
def table_name(request):
    return request.param


def test_it(db_connect, table_name):
    print("Testing: {} + {}".format(db_connect, table_name))

如果您需要更多像 test_it 这样的测试用例,只需使用另一个名称创建它们即可.

If you need more test cases like test_it, just create them with another name.

运行我的测试用例::

$ py.test -sv tests
========================================= test session starts =========================================
platform linux2 -- Python 2.7.9 -- py-1.4.30 -- pytest-2.7.2 -- /home/javl/.virtualenvs/stack/bin/python2
rootdir: /home/javl/sandbox/stack/tests, inifile:
collected 6 items

tests/test_it.py::test_it[mysql-user]
opening db
Testing: mysql + user
PASSEDclosing db

tests/test_it.py::test_it[pgsql-user]
opening db
Testing: pgsql + user
PASSEDclosing db

tests/test_it.py::test_it[pgsql-groups]
opening db
Testing: pgsql + groups
PASSEDclosing db

tests/test_it.py::test_it[mysql-groups]
opening db
Testing: mysql + groups
PASSEDclosing db

tests/test_it.py::test_it[firebird-groups]
opening db
Testing: firebird + groups
PASSEDclosing db

tests/test_it.py::test_it[firebird-user]
opening db
Testing: firebird + user
PASSEDclosing db


====================================== 6 passed in 0.01 seconds =======================================

从一个灯具到N个相关灯具的爆炸三角形"

思路如下:

  • 使用参数化夹具生成几个 db_connect 夹具
  • 为每个 db_connect 生成 N 个 table_name 固定装置的变体
  • test_it(db_connect, table_name) 只被 db_connecttable_name.
  • generate couple of db_connect fixtures, using parametrize fixture
  • for each db_connect generate N variants of table_name fixtures
  • have test_it(db_connect, table_name) being called only by proper combinatins of db_connect andtable_name.

这根本行不通

唯一的解决方案是使用某种场景,这些场景明确定义了哪些组合是正确.

The only solutions is to use some sort of scenarios, which explicitly define, which combinations arecorrect.

我们必须参数化测试函数,而不是参数化装置.

Instead of parametrizing fixtures, we have to parametrize test function.

通常,参数值按原样直接传递给测试函数.如果我们想要一个装置(名为作为参数名称)来照顾创建要使用的值,我们必须指定参数作为间接.如果我们说indirect=True,所有参数都将被这样处理,如果我们提供参数名称列表,只有指定的参数将被传递到夹具中,其余的将被传递因为他们正在进入测试功能.这里我使用了间接参数的显式列表.

Usually, the parameter value is passed directly to test function as is. If we want a fixture (namedas the parameter name) to take care of creating the value to use, we have to specify the parameteras indirect. If we say indirect=True, all parameters will be treated this way, if we providelist of parameter names, only specified parameters will be passed into fixture and remaining will goas they are into the test fuction. Here I use explicit list of indirect arguments.

import pytest

DBCFG = {"pgsql": "postgresql://scott:tiger@localhost:5432/mydatabaser",
         "mysql": "mysql://scott:tiger@localhost/foo",
         "oracle": "oracle://scott:[email protected]:1521/sidname"
}


@pytest.yield_fixture(scope="session")
def db_connect(request):
    connect_name = request.param
    print("\nopening db {connect_name}".format(connect_name=connect_name))
    assert connect_name in DBCFG
    yield DBCFG[connect_name]
    print("\nclosing db {connect_name}".format(connect_name=connect_name))


@pytest.fixture(scope="session")
def table_name(request):
    return "tabname-by-fixture {request.param}".format(request=request)


scenarios = [
    ("mysql", "myslq-user"),
    ("mysql", "myslq-groups"),
    ("pgsql", "pgsql-user"),
    ("pgsql", "pgsql-groups"),
    ("oracle", "oracle-user"),
    ("oracle", "oracle-groups"),
]
@pytest.mark.parametrize("db_connect,table_name",
                         scenarios,
                         indirect=["db_connect", "table_name"])
def test_it(db_connect, table_name):
    print("Testing: {} + {}".format(db_connect, table_name))

运行测试套件:

$ py.test -sv tests/test_indirect.py
py.test========================================= test session starts ==================================
=======
platform linux2 -- Python 2.7.9, pytest-2.8.7, py-1.4.31, pluggy-0.3.1 -- /home/javl/.virtualenvs/stack
/bin/python2
cachedir: tests/.cache
rootdir: /home/javl/sandbox/stack/tests, inifile:
collected 6 items

tests/test_indirect.py::test_it[mysql-myslq-user]
opening db mysql
Testing: mysql://scott:tiger@localhost/foo + tabname-by-fixture myslq-user
PASSED
closing db mysql

tests/test_indirect.py::test_it[mysql-myslq-groups]
opening db mysql
Testing: mysql://scott:tiger@localhost/foo + tabname-by-fixture myslq-groups
PASSED
closing db mysql

tests/test_indirect.py::test_it[pgsql-pgsql-user]
opening db pgsql
Testing: postgresql://scott:tiger@localhost:5432/mydatabaser + tabname-by-fixture pgsql-user
PASSED
closing db pgsql

tests/test_indirect.py::test_it[pgsql-pgsql-groups]
opening db pgsql
Testing: postgresql://scott:tiger@localhost:5432/mydatabaser + tabname-by-fixture pgsql-groups
PASSED
closing db pgsql

tests/test_indirect.py::test_it[oracle-oracle-user]
opening db oracle
Testing: oracle://scott:[email protected]:1521/sidname + tabname-by-fixture oracle-user
PASSED
closing db oracle

tests/test_indirect.py::test_it[oracle-oracle-groups]
opening db oracle
Testing: oracle://scott:[email protected]:1521/sidname + tabname-by-fixture oracle-groups
PASSED
closing db oracle


====================================== 6 passed in 0.01 seconds =======================================

我们看到它有效.

无论如何,有一个小问题 - db_connect 范围会话"没有得到尊重,它是在函数级别实例化和销毁.这是已知问题.

Anyway, there is one small issue - the db_connect scope "session" is not honored and it isinstantiated and destroyed at function level. This is known issue.

这篇关于基于参数化夹具的 Py.Test 参数化的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-31 12:51