Shorter option names for pytest fixture variants with long names - python

I am testing a function with several incoming datasets defined as fixtures, but the fixture names get quite cumbersome to distinguish them from one another.
#pytest.fixture()
def dataset_with_foo():
pass
#pytest.fixture()
def dataset_with_bar():
pass
#pytest.fixture()
def dataset_with_foo_and_bar():
pass
def test_something(dataset_with_foo_and_bar):
pass
Is there a way to define some kind of alias for the option name to be shorter? For instance, something like:
#usesfixture("dataset_with_foo_and_bar", option_name="dataset")
def test_something(dataset):
pass

Create a super fixture and helper function to get desired fixture with one fixture.
import pytest
#pytest.fixture
def super_fixture(fixture1,fixture2,fixture3):
local_vars = locals()
def helper(fixture_name):
return local_vars.get(fixture_name)
return helper
def test_a(super_fixture):
# getting fixture1
assert super_fixture("fixture1")

Ok, the best way I could find to do it is by using deferred parametrized fixtures:
#pytest.fixture()
def dataset(request):
mapping = {
"with-foo": create_dataset_with_foo(),
"with-bar": create_dataset_with_bar(),
"with-foo-and-bar": create_dataset_with_foo_and_bar(),
}
return mapping[request.param]
def create_dataset_with_foo():
pass
def create_dataset_with_bar():
pass
def create_dataset_with_foo_and_bar():
pass
#pytest.mark.parametrize("dataset", ["with-foo"], indirect=True)
def test_something(dataset):
pass
#pytest.mark.parametrize("dataset", ["with-foo-and-bar"], indirect=True)
def test_something(dataset):
pass
There has been other attempts using pytest-lazy-fixture or specialized decorator, but I find it a bit too hacky..
https://gist.github.com/wcooley/7472b8de6edb1e8ceda560843c0519a8

Related

Pytest setup generate product for all test

I am working on pytest API automation project, and I need to get a random product from the DB. Is there a way that I could use the same random product for all the test cases in my class? I am using a setup class method but it generates a different product every test. Thank you.
class TestCreateOrdersSmoke:
#classmethod
def setup(cls):
cls.products_db = ProductsDao()
cls.orders_db = OrdersDao()
cls.orders_helper = OrdersHelper()
#pytest.mark.tcid48
def test_create_order_as_guest(self):
random_product = self.products_db.select_random_product_from_db()
random_product_id = random_product[0]['ID']
UPDATE:
So i used a pytest session fixture like seggested and it works so thank you! but i want to make sure that this is right practice so here is the updated code:
class TestCreateOrdersSmoke:
#pytest.fixture(scope="session")
def helpers(self):
products_db = ProductsDao()
orders_db = OrdersDao()
orders_helper = OrdersHelper()
random_product = products_db.select_random_product_from_db()
yield {'products_db':products_db,
'orders_db':orders_db,
'orders_helper':orders_helper,
'random_product':random_product}
#pytest.mark.tcid48
def test_create_order_as_guest(self, helpers):
random_product = helpers['random_product']
random_product_id = random_product[0]['ID']
#pytest.mark.tcid88
def test_create_order_with_new_user(self, helpers):
random_product = helpers['random_product']
random_product_id = random_product[0]['ID']
As you say, you need a fixture for all methods in class, so you can use "class" or "session" scope fixture #pytest.fixture(scope="class").
Here some ways to organize the code with the "class" or "session" fixtures.
First way: fixture with "class" scope can be inside a class, example is the same as you did with "session" fixture:
class TestCreateOrdersSmoke:
#pytest.fixture(scope="class")
Second way: fixture with "class" scope can be outside of the class, so then you can use it in different classes, example:
import logging
#pytest.fixture(scope="class")
def h():
logging.info('h')
class TestOne:
def test_one_one(self, h):
logging.info('test_one_one')
def test_one_two(self, h):
logging.info('test_one_two')
class TestTwo:
def test_two_one(self, h):
logging.info('test_two_one')
Third way: you can annotate class with a fixture by marking #pytest.mark.usefixtrures('fixture_name') and you don't need to pass fixture to each method of the class, it will be passed automatically. Example:
import logging
#pytest.fixture(scope="class")
def h():
logging.info('h')
#pytest.mark.usefixtures('h')
class TestOne:
def test_one_one(self):
logging.info('test_one_one')
def test_one_two(self):
logging.info('test_one_two')
And you can try to use 'autouse' fixture param, so you don't need to pass fixture inside methods, or annotate class with it. Example:
#pytest.fixture(scope="class", autouse=True)
def h():
logging.info('h')
class TestOne:
def test_one_one(self):
logging.info('test_one_one')
def test_one_two(self):
logging.info('test_one_two')
But i don't recommend to use autouse, be aware from use it.

Customize pytest collecting tests

I would like to avoid using the "test" prefix in classes and functions names and implement my own schema of the test parametrization.
I did the next code
test.py
import pytest
# class for inheritance to avoid "Test" prefix
class AtsClass:
__ATS_TEST_CLASS__ = True
# decorator to mark functions as tests (to avoid "Test" prefix)
def ats_test(f):
setattr(f, "__ATS_TEST_CLASS__", True)
return f
def test_1():
pass
#ats_test
def some_global_test():
pass
class MyClass(AtsClass):
def test_4(self):
pass
#ats_test
def some_func(self):
pass
conftest.py
import pytest
import inspect
# #pytest.hookimpl(hookwrapper=True)
def pytest_pycollect_makeitem(collector, name, obj):
# outcome = yield
# res = outcome.get_result()
if inspect.isclass(obj) and obj.__name__ != "AtsClass" and hasattr(obj, "__ATS_TEST_CLASS__") and obj.__ATS_TEST_CLASS__ == 1:
print("WE HAVE FOUND OUR CLASS")
return pytest.Class(name, parent=collector)
# outcome.force_result(pytest.Class(name, parent=collector))
if inspect.isfunction(obj) and hasattr(obj, "__ATS_TEST_CLASS__") and obj.__ATS_TEST_CLASS__ == 1:
print("WE HAVE FOUND OUR FUNCTION")
return pytest.Function(name, parent=collector)
# outcome.force_result([pytest.Function(name, parent=collector)])
def pytest_generate_tests(metafunc):
print("-->Generate: {}".format(metafunc.function.__name__))
In this case hook "pytest_pycollect_makeitem" creates test for function "some_global_test", but hook "pytest_generate_tests" is not executed for function "some_global_test".
I have found a solution, call collector._genfunctions(name, obj) from my hook. But I think it is not the right decision, cause _genfunctions is a private method and not declared.
Is there another way to solve my task?
So, nobody knows the answer and I decided to offer my solution (it can be useful for others):
class TestBaseClass:
__test__ = True
def mark_test(f):
setattr(f, "__test__", True)
return f
# using base class and decorator
class MyTestClass(TestBaseClass):
#mark_test
def some_func(self):
pass
Pytest uses attribute __test__ to detect nose-tests, so you can use nose-library or just use such base class and decorator.
If you want only to change prefix of tests you can set custom python_functionsand python_classes options at pytest.ini.
For more information follow a link.

Define a pytest fixture providing multiple arguments to test function

With pytest, I can define a fixture like so:
#pytest.fixture
def foo():
return "blah"
And use it in a test like so:
def test_blah(foo):
assert foo == "blah"
That's all very well. But what I want to do is define a single fixture function that "expands" to provide multiple arguments to a test function. Something like this:
#pytest.multifixture("foo,bar")
def foobar():
return "blah", "whatever"
def test_stuff(foo, bar):
assert foo == "blah" and bar == "whatever"
I want to define the two objects foo and bar together (not as separate fixtures) because they are related in some fashion. I may sometimes also want to define a fixture that depends on another fixture, but have the second fixture incorporate the result of the first and return it along with its own addition:
#pytest.fixture
def foo():
return "blah"
#pytest.multifixture("foo,bar")
def foobar():
f = foo()
return f, some_info_related_to(f)
This example may seem silly, but in some cases foo is something like a Request object, and the bar object needs to be linked to that same request object. (That is, I can't define foo and bar as independent fixtures because I need both to be derived from a single request.)
In essence, what I want to do is decouple the name of the fixture function from the name of the test-function argument, so that I can define a fixture which is "triggered" by a particular set of argument names in a test function signature, not just a single argument whose name is the same as that of the fixture function.
Of course, I can always just return a tuple as the result of the fixture and then unpack it myself inside the test function. But given that pytest provides various magical tricks for automatically matching names to arguments, it seems like it's not unthinkable that it could magically handle this as well. Is such a thing possible with pytest?
You can now do this using pytest-cases:
from pytest_cases import fixture
#fixture(unpack_into="foo,bar")
def foobar():
return "blah", "whatever"
def test_stuff(foo, bar):
assert foo == "blah" and bar == "whatever"
See the documentation for more details (I'm the author by the way)
note: this solution not working if your fixture depends on another fixtures with parameters
Don't really know if there are any default solution in pytest package, but you can make a custom one:
import pytest
from _pytest.mark import MarkInfo
def pytest_generate_tests(metafunc):
test_func = metafunc.function
if 'use_multifixture' in [name for name, ob in vars(test_func).items() if isinstance(ob, MarkInfo)]:
result, func = test_func.use_multifixture.args
params_names = result.split(',')
params_values = list(func())
metafunc.parametrize(params_names, [params_values])
def foobar():
return "blah", "whatever"
#pytest.mark.use_multifixture("foo,bar", foobar)
def test_stuff(foo, bar):
assert foo == "blah" and bar == "whatever"
def test_stuff2():
assert 'blah' == "blah"
So we defined pytest_generate_tests metafunction. This function
checks if multifixture mark is on the test
if the mark is on - it takes variables names "foo,bar" and fucntion foobar that will be executed on generation
#pytest.mark.multifixture("foo,bar", foobar)
You can do this with two pytest fixtures, like so:
import pytest
#pytest.fixture
def foo():
return [object()]
# value derived from foo
#pytest.fixture
def bar(foo):
return foo[0]
# totally independent fixture
#pytest.fixture
def baz():
return object()
def test_fixtures(foo, bar, baz):
assert foo[0] is bar
assert foo[0] is not baz
# both assertions will pass
Here the foo and bar fixtures have a specific relation between their values (referencing the same object). This is the same result as you wanted from your multi fixture. (the baz fixture is included for comparison, and uses an unrelated instance of object().
If both values are derived from some shared context you can put the shared context in a fixture, and then derive the final results independently.
#pytest.fixture
def shared():
return [object()]
#pytest.fixture
def derived_1(shared):
return shared[0]
#pytest.fixture
def derived_2(shared):
return shared[-1]
def test_derived(derived_1, derived_2):
assert derived_1 is derived_2

Overriding sub-fixtures in pytest

I'm using pytest with some complicated dependency-injected fixtures. I have fixtures that use other fixtures in a long chain. I'd like to be able to alter some fixtures in the middle of the chain for specific tests.
Given these (simplified) fixtures:
#pytest.fixture
def cache():
return Cache()
# Use cache fixture in a new fixture.
#pytest.fixture
def resource(cache):
return Resource(cache=cache, working=True)
# Use resource fixture in a new fixture.
#pytest.fixture
def service(resource):
return Service(resource=resource)
And some tests:
def test_service_when_resource_working(service):
assert service.status == "good"
def test_service_when_resource_broken(service):
assert service.status == "bad"
How can I override the resource fixture so that it's like this:
#pytest.fixture
def broken_resource(cache):
return Resource(cache=cache, working=False)
...but only for the test_service_when_resource_broken test case? I can create a broken_service that uses broken_resource, but the reality is that the dependency chain is long, and I want to re-use all the fixtures, but selectively change some of them in the middle for selected tests.
I want to do something like this (pseudocode):
#pytest.override_fixture('resource', 'broken_resource')
def test_service_when_resource_broken(service):
# service should have been instantiated with broken_resource instead of resource.
assert service.status == "bad"
You can use markers on your tests to achieve what you are expecting.
Basically, you mark the test for which you need a different behaviour. In the fixture method look for that marker from the requesting test context and process.
Here is how you can do it.
#pytest.fixture
def cache():
return Cache()
# Use cache fixture in a new fixture.
#pytest.fixture
def resource(request, cache):
working = True
marker = request.node.get_marker("broken")
if marker:
working = False
return Resource(cache=cache, working=working)
# Use resource fixture in a new fixture.
#pytest.fixture
def service(resource):
return Service(resource=resource)
def test_service_when_resource_working(service):
assert service.status == "good"
#pytest.mark.broken
def test_service_when_resource_broken(service):
assert service.status == "bad"

How to share object from fixture to all tests using pytest?

What is the best way to define an object in a fixture with session scope and autouse=True, so it will be available to all tests?
#pytest.fixture(scope='session', autouse=True)
def setup_func(request):
obj = SomeObj()
Next thing, I want some magic that previously created obj will appear in each test context without the need of each test to define the setup_func fixture.
def test_one():
obj.do_something_fancy()
My recommendation would to add the fixture to conftest.py and make sure to return the object you want to produce from the fixture.
As noted, this makes "autouse" kind of useless.
In the root directory for your tests, add the fixture to a file named conftest.py:
#pytest.fixture(scope='session', autouse=True)
def someobj(request):
return SomeObj()
Any test file beneath the root file will have access to this fixture (for example test_foo.py):
def test_foo(someobj):
assert isinstance(someobj, SomeObj)
Another approach, would be to use a global variable defined in the same test or imported from a module.
For example in conftest.py:
someobj = None
#pytest.fixture(scope='session', autouse=True)
def prep_someobj(request):
someobj = SomeObj()
Then in your test:
from . import conftest
def test_foo():
assert isinstance(conftest.someobj, SomeObj)
In my opinion this is less readable and more cumbersome than the first method.
A more general pattern for this is to return locals() at the end of your conftest and you'll be able to easily reference anything created in the fixture.
conftest.py
#pytest.fixture(scope='session')
def setup_func(request):
obj1 = SomeObj()
obj2 = SomeObj()
return locals()
test_stuff.py
def test_one(setup_func):
setup_func['obj1'].do_something_fancy()
def test_two(setup_func):
setup_func['obj2'].do_something_fancy()
Another possibility is to wrap your tests in a class and use class variables to only define the object instance once. This assumes you are able to wrap all tests in a single class and so this answer may address a less general, but similar use case. For example,
class SomeObj():
"""This object definition may exist in another module and be imported."""
def __init__(self):
self.x = 5
def do_something_fancy(self, y):
return self.x * y
class TestX():
# Object instance to share across tests
someobj = SomeObj()
def test_x(self):
assert TestX.someobj.x == 5
def test_fancy(self):
fancy_factor = 10
result = TestX.someobj.do_something_fancy(fancy_factor)
assert result == 50

Categories