brunogirin

brunogirin

Python Testing with pytest, Second Edition: explain the rationale behind the change in the fixture code (pages 143, 144)

In the “Testing at Multiple Layers to Avoid Mocking” section, the code for the cards_db fixture changes. In previous chapters, it was something like:

@pytest.fixture(scope="session")
def db(tmp_path_factory):
  """CardsDB object connected to a temporary database"""
  db_path = tmp_path_factory.mktemp('cards_db')
  db_ = cards.CardsDB(db_path)
  yield db_
  db_.close()

@pytest.fixture(scope="function")
def cards_db(db, request, faker):
  """CardsDB object that's empty"""
  db.delete_all()
  return db

When we reach this section, it changes to:

@pytest.fixture(scope="module")
def db_path(tmp_path_factory):
  db_path = tmp_path_factory.mktemp("cards_db")
  return db_path

@pytest.fixture()
def cards_db(db_path, monkeypatch):
  monkeypatch.setenv("CARDS_DB_DIR", str(db_path))
  db_ = cards.CardsDB(db_path)
  db_.delete_all()
  yield db_
  db_.close()

Explaning this code change could introduce some important testing concepts that junior developers may struggle with. By adding the monkeypatch.setenv line, you ensure that the high level API via the cards_db package will point to the same database as the one created manually via the cards.CardsDB class. This is necessary because your tests operate at two different levels at the same time. It might be useful to show it failing if you don’t set the environment variable and explain why.

This also introduces an important concept about how you design your tests. As mentioned elsewhere in the book, tests are meant to be independent and you need to ensure you know what they’re testing. One way this is often done is by designing tests such that the “When” part of the test is the only part that exercises the code under test (in this case, the cards_db package) while the “Given” and “Then” parts of the test are kept independent of the code under test. This way you know exactly what the “When” part is testing. However, when designing your tests that way, it means the “When” part is acting against a different layer from the “Given” and “Then” parts and you need to ensure those layers are configured the same.

This test design point introduces another point about making your app easily testable. In the case of the CardDB app, the fact that its main configuration option can be set via an environment variable is what makes this code possible.

Finally, this raises a point about test strategy. When I see code like this in my own projects, I immediately add a simple test suite to validate that the configuration options are properly handled. If those tests fail in the future, it is likely that a lot of other tests will fail in cascade. I typically set a special mark on those such as @pytest.mark.canary so if I suddently have a lot of tests failing, I check the canary ones first and make sure they are fixed as a priority on the basis that if those fail, a lot of others will fail too.

Apologies for such a complex and long-winded comment on this small change. When I started asking myself “why?” repeatedly when seeing that code change, I realised that there were a lot of logic behind that change that is natural to an experienced tester but not to a junior one. I don’t know if this would be better explained as an addition to this chapter, an aside, or any other form but I feel that it would be worthwhile to explain the rationale behind this code change.

0 705 1

Popular Prag Prog topics Top

telemachus
Python Testing With Pytest - Chapter 2, warnings for “unregistered custom marks” While running the smoke tests in Chapter 2, I get these...
5 2072 1
New
jamis
The following is cross-posted from the original Ray Tracer Challenge forum, from a post by garfieldnate. I’m cross-posting it so that the...
4 2348 3
New
lirux
Hi Jamis, I think there’s an issue with a test on chapter 6. I own the ebook, version P1.0 Feb. 2019. This test doesn’t pass for me: ...
10 1442 5
New
herminiotorres
Hi! I know not the intentions behind this narrative when called, on page XI: mount() |> handle_event() |> render() but the correc...
3 1296 17
New
conradwt
First, the code resources: Page 237: rumbl_umbrella/apps/rumbl/mix.exs Note: That this file is missing. Page 238: rumbl_umbrella/app...
1 1105 2
New
swlaschin
The book has the same “Problem space/Solution space” diagram on page 18 as is on page 17. The correct Problem/Solution space diagrams ar...
1 1068 1
New
adamwoolhether
When trying to generate the protobuf .go file, I receive this error: Unknown flag: --go_opt libprotoc 3.12.3 MacOS 11.3.1 Googling ...
0 4828 3
New
jskubick
I found an issue in Chapter 7 regarding android:backgroundTint vs app:backgroundTint. How to replicate: load chapter-7 from zipfile i...
0 3459 3
New
brunogirin
When running tox for the first time, I got the following error: ERROR: InterpreterNotFound: python3.10 I realised that I was running ...
0 1295 1
New
dtonhofer
@parrt In the context of Chapter 4.3, the grammar Java.g4, meant to parse Java 6 compilation units, no longer passes ANTLR (currently 4....
0 1000 3
New

Other popular topics Top

ohm
Which, if any, games do you play? On what platform? I just bought (and completed) Minecraft Dungeons for my Nintendo Switch. Other than ...
245 5071 101
New
DevotionGeo
I know that these benchmarks might not be the exact picture of real-world scenario, but still I expect a Rust web framework performing a ...
36 6151 11
New
AstonJ
poll poll Be sure to check out @Dusty’s article posted here: An Introduction to Alternative Keyboard Layouts It’s one of the best write-...
10 5055 12
New
AstonJ
Just done a fresh install of macOS Big Sur and on installing Erlang I am getting: asdf install erlang 23.1.2 Configure failed. checking ...
10 5353 8
New
AstonJ
Biggest jackpot ever apparently! :upside_down_face: I don’t (usually) gamble/play the lottery, but working on a program to predict the...
19 2830 11
New
PragmaticBookshelf
Rails 7 completely redefines what it means to produce fantastic user experiences and provides a way to achieve all the benefits of single...
32 2728 10
New
PragmaticBookshelf
Author Spotlight Rebecca Skinner @RebeccaSkinner Welcome to our latest author spotlight, where we sit down with Rebecca Skinner, auth...
106 10209 29
New
AstonJ
Chris Seaton, the creator of TruffleRuby has died. It appears from suicide :cry: He left this note on Twitter on the weekend: And one...
4 2126 3
New
DevotionGeo
I have always used antique keyboards like Cherry MX 1800 or Cherry MX 8100 and almost always have modified the switches in some way, like...
27 2388 10
New
CommunityNews
Will Swifties’ war on AI fakes spark a deepfake porn reckoning?
0 5638 0
New

Latest in PragProg

View all threads ❯