Pytest fixture parametrization

for fun and profit
czEść!

-
Mam na imię Michał
-
Zajmuję się backendem, protokołami, bazami danych, czasem embedded
-
Obecnie pracuję w Vatix
-
Lubię chodzić po górach i DIY
the EXAMPLE: API
@dataclass
class API:
user: User
def list_todos(self): ...
def create_todo(self, name): ...
def share_todo(self, list_uuid, user, permissions): ...
def list_items(self, list_uuid): ...
def create_item(self, list_uuid, name): ...
def retrieve_item(self, list_uuid, item_uuid): ...
def update_item(self, list_uuid, item_uuid, status: Status): ...
the example: data model
TodoList
TodoItem
Organisation
User
Team
unittest
class MyTestCase(TestCase):
def setUp(self):
# prepare everything
def test_something(self):
...
def test_something_else(self):
...
def tearDown(self):
# clean up after tests
unittest
class APITestCase(TestCase):
def setUp(self):
super().setUp()
self.faker = Faker()
self.my_organisation = Organisation(name=faker.company())
self.my_team = Team(
name=faker.color(),
organisation=self.my_organisation
)
self.my_user = User(
name=faker.name(),
team=self.my_team
)
class TestListTodos(APITestCase):
def test_empty(self):
self.assertListEqual(self.my_user.api.list_todos(), [])
unittest
class TestMyListMixin:
def setUp(self):
super().setUp()
self.my_todo_list = TodoList(
owner=self.my_user,
name=self.faker.bs()
)
class TestListOwner(TestMyListMixin, APITestCase):
def test_owner(self):
self.assertListEqual(
self.my_user.api.list_todos(),
[self.my_todo_list]
)
unittest
class TestListMate(TestMyListMixin, APITestCase):
def setUp(self):
super().setUp()
self.my_team_mate = User(
name=self.faker.name(),
team=self.my_team,
role=Role.USER
)
def test_mate(self):
self.assertListEqual(self.my_team_mate.api.list_todos(), [])
unittest
class TestListManager(TestMyListMixin, APITestCase):
def setUp(self):
super().setUp()
self.my_team_manager = User(
name=self.faker.name(),
team=self.my_team,
role=Role.MANAGER
)
def test_manager(self):
self.assertListEqual(
self.my_team_manager.api.list_todos(),
[self.my_todo_list]
)
unittest
class TestListAdmin(TestMyListMixin, APITestCase):
def setUp(self):
super().setUp()
self.my_admin = User(
name=self.faker.name(),
team=Team(name="Admins", organisation=self.my_organisation),
role=Role.ADMIN
)
def test_admin(self):
self.assertListEqual(
self.my_admin.api.list_todos(),
[self.my_todo_list]
)
unittest
class TestOtherTeamMixin(TestMyListMixin):
def setUp(self):
super().setUp()
self.other_team = Team(
name=self.faker.color_name(),
organisation=self.my_organisation
)
class TestOtherOrganisationMixin(TestMyListMixin):
def setUp(self):
super().setUp()
self.other_organisation = Organisation(name=self.faker.company())
self.other_organisation_team = Team(
name=self.faker.color_name(),
organisation=self.other_organisation
)
pytest
@pytest.fixture
def something():
# prepare
yield "Something"
# cleanup
@pytest.fixture
def something_else():
# prepare
yield "Something else"
# cleanup
def test_something(something):
assert something == "Something"
def test_something_else(something_else):
assert something_else == "Something else"
pytest
@pytest.fixture
def my_organisation(faker: Faker):
return Organisation(name=faker.company())
@pytest.fixture
def my_team(faker: Faker, my_organisation: Organisation):
return Team(
name=faker.color_name(),
organisation=my_organisation
)
@pytest.fixture
def my_user(faker: Faker, my_team: Team):
return User(name=faker.name(), team=my_team, role=Role.USER)
def test_empty(my_user):
assert my_user.api.list_todos() == []
pytest
@pytest.fixture
def my_todo_list(faker: Faker, my_user: User):
return TodoList(owner=my_user, name=faker.bs())
def test_owner(my_user, my_todo_list):
assert my_user.api.list_todos() == [my_todo_list]
pytest
@pytest.fixture
def my_team_mate(faker: Faker, my_team: Team):
return User(name=faker.name(), team=my_team, role=Role.USER)
def test_mate(my_team_mate, my_todo_list):
assert my_team_mate.api.list_todos() == []
@pytest.fixture
def my_team_manager(faker: Faker, my_team: Team):
return User(name=faker.name(), team=my_team, role=Role.MANAGER)
def test_manager(my_team_manager, my_todo_list):
assert my_team_manager.api.list_todos() == [my_todo_list]
pytest
@pytest.fixture
def other_organisation():
return Organisation(...)
@pytest.fixture
def other_organisation_team(other_organisation: Organisation):
return Team(..., organisation=other_organisation)
@pytest.fixture
def other_organisation_mate(other_organisation_team):
return User(..., team=other_organisation_team, role=Role.USER)
@pytest.fixture
def other_organisation_manager(other_organisation_team: Team):
return User(..., team=other_organisation_team, role=Role.MANAGER)
trimming roots
my org
my team
my user
my team mate
my team manager
other team
other org
other org team
other team mate
other team manager
other org mate
other org manager
other org admin
my org admin
trimming roots: role?
my org
my team
my user
other user manager | user
other team
other org
other org team
other team mate
other team manager
other org mate
other org manager
other org admin
my org admin
PARAMETRIZE
@pytest.mark.parametrize(
'a,b,result',
[
(1,2,3),
(3,4,7),
]
)
def test_addition(a, b, result):
assert a + b == result
PARAMETRIZE: fixtures
@pytest.fixture
def result():
return 42
@pytest.mark.parametrize(
'a,b,result',
[
(1, 2, 3),
(3, 4, 7),
]
)
def test_addition(a, b, result):
assert a + b == result
PARAMETRIZE: indirect
@pytest.fixture
def result(request):
param = getattr(request, 'param', 41)
return param + 1
def test_42(result):
assert result == 42
@pytest.mark.parametrize(
'a,b,result',
[
(1, 2, 2),
(3, 4, 6),
],
indirect=['result']
)
def test_addition(a, b, result):
assert a + b == result
PARAMETRIZE: ROLE
@pytest.fixture
def other_user(request, my_team):
role = getattr(request, 'param', Role.USER)
return User(..., team=my_team, role=role)
@pytest.mark.parametrize('other_user', [
Role.USER
], indirect=['other_user'])
def test_mate(self, other_user, my_todo_list):
assert other_user.api.list_todos() == []
@pytest.mark.parametrize('other_user', [
Role.MANAGER
], indirect=['other_user'])
def test_manager(self, other_user, my_todo_list):
assert other_user.api.list_todos() == [my_todo_list]
trimming roots: team?
my org
my team
my user
other user
manager | user
other team
other org
other org team
other org mate
other org manager
other org admin
my org admin
parametrize: team
@pytest.fixture
def other_user(request):
team_or_fixture = getattr(request, 'param', my_team)
if hasattr(team_or_fixture, '_pytestfixturefunction'):
team = request.getfixturevalue(team_fixture.__name__)
else:
team = team_or_fixture
return User(..., team=team)
@pytest.mark.parametrize('other_user', [
other_team,
], indirect=True)
def test_other_manager(self, other_user, my_todo_list):
assert other_user.api.list_todos() == []
parametrize: team AND ROLE
@pytest.fixture
def other_user(request):
param = getattr(request, 'param', {})
role = param.get('role', Role.USER)
team_or_fixture = param.get('team', my_team)
if hasattr(team_or_fixture, '_pytestfixturefunction'):
team = request.getfixturevalue(team_fixture.__name__)
else:
team = team_or_fixture
return User(..., team=team, role=role)
@pytest.mark.parametrize('other_user', [
dict(team=other_team, role=Role.USER),
], indirect=True)
def test_other_manager(self, other_user, my_todo_list):
assert other_user.api.list_todos() == []
trimming roots: after
my org
my team
other team
other org
my user
other user
manager | user
sugar: dataclass
@dataclass
class OtherUserParam:
role: Role = field(default=Role.USER)
team: Team = field(default_factory=my_team) # ???
@pytest.fixture
def other_user(request):
param = make_param_model(request, OtherUserParam)
return User(..., team=param.team, role=param.role)
@pytest.mark.parametrize(
'other_user',
[
dict(team=other_team, role=Role.MANAGER)
],
indirect=True
)
def test_other_manager(self, other_user, my_todo_list):
assert other_user.api.list_todos() == []
sugar: decorator
@dataclass
class UserParam:
team: Team = field(default_factory=my_team)
role: Role = Role.USER
@parametrized_fixture(UserParam)
def my_user(request) -> User:
return User(
...,
team=request.param.team,
role=request.param.role
)
trimming branches
@pytest.mark.parametrize('other_user', [
dict(role=Role.USER)
], indirect=True)
def test_mate(self, other_user, my_todo_list):
assert other_user.api.list_todos() == []
@pytest.mark.parametrize('other_user', [
dict(role=Role.MANAGER)
], indirect=True)
def test_manager(self, other_user, my_todo_list):
assert other_user.api.list_todos() == [my_todo_list]
@pytest.mark.parametrize('other_user', [
dict(team=other_team, role=Role.MANAGER)
], indirect=True)
def test_other_manager(self, other_user, my_todo_list):
assert other_user.api.list_todos() == []
parametrize result?
@pytest.mark.parametrize('other_user, result', [
(
dict(role=Role.USER),
[]
),
(
dict(role=Role.MANAGER, team=other_team),
[]
),
(
dict(role=Role.MANAGER),
... # ???
),
], indirect=['other_user'])
def test_other_user(self, other_user, result):
assert other_user.api.list_todos() == result
parametrize: result
@pytest.fixture
def result(request):
func_or_value = getattr(request, 'param', None)
if not inspect.isfunction(func_or_value):
return func_or_value
try:
signature = inspect.signature(func_or_value)
parameters = signature.parameters.values()
except ValueError:
parameters = []
kwargs = {}
for parameter in parameters:
try:
kwargs[parameter.name] = \
request.getfixturevalue(parameter.name)
except pytest.FixtureLookupError:
continue
return func_or_value(**kwargs)
parametrize: result
@pytest.mark.parametrize('other_user, result', [
(
dict(role=Role.MANAGER),
lambda my_todo_list: [my_todo_list]
),
], indirect=['other_user', 'result'])
def test_other_user(self, other_user, result):
assert other_user.api.list_todos() == result
sugar: decorator
@parametrized_fixture
def result(request):
raise NotImplementedError
trimming branches: after
@pytest.mark.parametrize('other_user, result', [
(
dict(role=Role.USER),
[]
),
(
dict(role=Role.MANAGER, team=other_team),
[]
),
(
dict(role=Role.MANAGER),
lambda my_todo_list: [my_todo_list]
),
], indirect=['other_user', 'result']
)
def test_other_user(self, other_user, result):
assert other_user.api.list_todos() == result
glue: always indirect
def pytest_generate_tests(metafunc):
#
# use pytest API to find parametrization and add indirect=[]
# where parameters refer to our "magic" fixtures
#
# implementation is on github ;)
putting it all together
@pytest.mark.parametrize("other_user, result", [
(
my_user,
lambda my_todo_list: [my_todo_list]
),
(
dict(role=Role.USER),
[]
),
(
dict(role=Role.MANAGER, team=other_team),
[]
),
(
dict(role=Role.MANAGER),
lambda my_todo_list: [my_todo_list]
),
(
dict(role=Role.ADMIN),
lambda my_todo_list: [my_todo_list]
),
],
)
def test_same_organisation(self, other_team, other_user, my_todo_list, result):
assert other_user.api.list_todos() == result
putting it all together
@pytest.mark.parametrize("my_todo_list, other_user", [
(
dict(
collaborators=lambda other_user: {other_user: {Permission.CREATE}}
),
dict(
role=Role.USER,
team=lambda other_organisation: Team(organisation=other_organisation),
),
),
],
)
def test_share_across_organisations(self, other_user, my_todo_list):
item = other_user.api.create_item(my_todo_list.uuid, 'Buy milk')
assert item
assert my_todo_list.todoitem_set == [item]
challenge: layers
@pytest.mark.parametrize("other_user", [
dict(role=Role.USER)
])
class TestMates:
def test_same_organisation(self, other_user):
...
@pytest.mark.parametrize("other_user", [
dict(organisation=other_organisation)
])
def test_other_organisation(self, other_user):
...
ValueError: duplicate parametrization of 'other_user'

mrzechonek/fixture_parametrization
thank you
Fixture parametrization for fun and profit
By Michał Lowas-Rzechonek
Fixture parametrization for fun and profit
- 25