Py.Test parametrizing based on parametrized fixture

余生颓废 提交于 2019-12-11 10:22:52

问题


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.

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

Thanks


回答1:


Test classes are used to:

  • provide setup and teardown functions for test cases
  • share some common values during testing

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).

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

"rectangle" style: M x N test runs by two parametrized fixtures

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

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

My tests are in 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))

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

Running my test case::

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

"Exploding triangles" from one fixture to N dependent fixtures

The idea is as follows:

  • 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 and table_name.

This simply does not work

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

"Scenarios": indirect parametrization at test function level

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

Usually, the parameter value is passed directly to test function as is. If we want a fixture (named as the parameter name) to take care of creating the value to use, we have to specify the parameter as indirect. If we say indirect=True, all parameters will be treated this way, if we provide list of parameter names, only specified parameters will be passed into fixture and remaining will go as 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:tiger@127.0.0.1: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))

Running the test suite:

$ 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:tiger@127.0.0.1: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:tiger@127.0.0.1:1521/sidname + tabname-by-fixture oracle-groups
PASSED
closing db oracle


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

we see it works.

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



来源:https://stackoverflow.com/questions/35542653/py-test-parametrizing-based-on-parametrized-fixture

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!