3 ms·
The author doesn't like pytest fixtures, but personally they're one of my favorite features of pytest. Here's an example use case: I have a test suite that tes
by returningfory2 6y ago
The author doesn't like pytest fixtures, but personally they're one of my favorite features of pytest.
Here's an example use case: I have a test suite that tests my application's interactions with the DB. In my experience, the most tedious part of these kinds of tests is setting up the initial DB state. The initial DB state will generally consist of a few populated rows in a few different tables, many linked together through foreign keys. The initial DB state varies in each test.
My approach is to create a pytest fixture for each row of data I want in a test. (I'm using SQLAlchemy, so a row is 1-1 with a populated SQLAlchemy model.) If the row requires another row to exist through a foreign key constraint, the fixture for the child row will depend on the fixture for the parent. This way, if you add the child test fixture to insert the child row, pytest will automatically insert the parent row first. The fixtures ultimately form a dependency tree.
Finally in a test, creating initial DB state is simple: you just add fixtures corresponding to the rows you want to exist in the test. All dependencies will be created automatically behind the scenes by pytest using the fixtures graph. (In the end I have about ~40 fixtures which are used in ~240 tests.)
- epage 6y agoI'm mixed on fixtures. One one hand, I've been impressed with how they compose and have let me do some great things. For example, I had system tests that needed hardware identifiers. I had a `conftest.py` to add CLI args for them. I then made fixtures to wrap the lookup of these. In the fixture, I marked it as Skip if the arg was missing. This was then propagated to all of the tests, only running the ones the end-user had the hardware for. On the other hand, when I need to vary the data between tests and that data is an input to something that I'd like to abstract the creation of, fixtures break down and I have to instead use a function call.
- emptysea 6y agoOne thing I've encounter with pytest fixtures is they have a tendency to balloon in size. We started out with like 50 fixtures, but now we have a conftest.py file that has `institution_1`, ..., `institution_10`. My end conclusion is that fixtures are nice for some things, like managing mocks, and clearing the databases after tests, but for data it's better to write some functions to create stuff. So instead of `def test_something(institution_with_some_flag_b)` you'd write in your test body: def test_something() -> None: institution = create_institution(some_flag="b") Also another benefit is you can click into the function whereas fixtures you have to grep.
- sirlantis 6y agoI’ve rewritten a bunch of our tests to this factory pattern last week, too (the factory is a fixture though - FactoryBoy is worth a look). I’d argue that too many global fixtures in conftest have a high risk of becoming a “Mystery Guests” or too general fixtures. For a test reader it’s impossible to know the semantics of “institution_10”. I believe this to be rooted in DRY obsession leading to coupling of tests: “We need a second institution in two modules? Let’s lift it up to global!”
- codethief 6y agoI'm the exact opposite, I absolutely hate pytest fixtures. They are effectively global state, so adding a fixture somewhere in your code base might affect the tests in a completely different location. This gets even worse with every fixture you add because, being global state, fixtures can interact with one another – often in unexpected ways. Finally, readers unfamiliar with your code won't know where the arguments for a given `test_xy()` function come from, i.e. the dependency injection is completely unclear and your IDE won't help you much. There are so many other (better) ways to achieve the same goal, such as decorators or – as already mentioned by emptysea in their sibling comment – explicitly invoking some function from within the test to do the setup/teardown.