- 8.1 Package Setup
- 8.2 Initial Test Coverage
- 8.3 Red
- 8.4 Green
- 8.5 Refactor
8.2 Initial Test Coverage
Now that we’ve set up our basic package structure, we’re ready to get started testing. Because the necessary pytest package has already been installed (Listing 8.6), we can actually run the (nonexistent) tests immediately:
(venv) $ pytest ============================= test session starts ============================= platform darwin -- Python 3.10.6, pytest-7.1.3, pluggy-1.0.0 rootdir: /Users/mhartl/repos/python_package_tutorial collected 0 items ============================ no tests ran in 0.00s ============================
Exact details will differ (and will be omitted in future examples for that reason), but your results should be similar.
Now let’s write a minimal failing test and then get it to pass. Because we’ve already created a tests directory with the test file test_phrase.py (Listing 8.2), we can begin by adding the code shown in Listing 8.9.
Listing 8.9: The initial test suite. RED
test/test_phrase.py
Listing 8.9 defines a function containing one assertion, which asserts that something has a boolean value of True, in which case the assertion passes, and fails otherwise. Because Listing 8.9 literally asserts that False is True, it fails by design:
Listing 8.10: RED
By itself, this test isn’t useful, but it demonstrates the concept, and we’ll add a useful test in just a moment.
Many systems, including mine, display failing tests in the color red, as shown in Figure 8.3. Because of this, a failing test (or collection of tests, known as a test suite) is often referred to as being RED. To help us keep track of our progress, the captions of code listings corresponding to a failing test suite are labeled RED, as seen in Listing 8.9 and Listing 8.10.
Figure 8.3 The RED state of the initial test suite.
To get from a failing to a passing state, we can change False to True in Listing 8.9, yielding the code in Listing 8.11.
Listing 8.11: A passing test suite. GREEN
test/test_phrase.py
As expected, this test passes:
Listing 8.12: GREEN
Because many systems display passing tests using the color green (Figure 8.4), a passing test suite is often referred to as GREEN. As with RED test suites, the captions of code listings corresponding to passing tests will be labeled GREEN (as seen in Listing 8.11 and Listing 8.12).
Figure 8.4 A GREEN test suite.
In addition to asserting that true things are True, it is often convenient to assert that false things are not False, which we can accomplish using not (Section 2.4.1), as shown in Listing 8.13.
Listing 8.13: A different way to pass. GREEN
test/test_phrase.py
As before, this test is GREEN:
Listing 8.14: GREEN
8.2.1 A Useful Passing Test
Having learned the basic mechanics of GREEN and RED tests, we’re now ready to write our first useful test. Because we mainly want to test the Phrase class, our first step is to fill in phrase.py with the source code for defining phrases. We’ll start with just Phrase itself (without TranslatedPhrase), as shown in Listing 8.15. Note that for brevity we’ve also omitted the iterator code from Section 5.3.
Listing 8.15: Defining Phrase in a package.
~/src/palindrome/phrase.py
At this point, we’re ready to try importing Phrase into our test file. With the package structure as in Listing 8.1, the Phrase class should be importable from the palindrome package, which in turn should be available using palindrome.-phrase.4 The result appears in Listing 8.16, which also replaces the example test from Listing 8.13.
Listing 8.16: Importing the palindrome package. RED
test/test_phrase.py
Unfortunately, the test suite doesn’t pass even though there’s no longer even a test that could fail:
Listing 8.17: RED
The issue is that our package needs to be installed in the local environment in order to perform the import in Listing 8.16. Because it hasn’t been installed yet, the test suite is in an error state. Although this is technically not the same as a failing state, an error state is still often called RED.
To fix the error, we need to install the palindrome package locally, which we can do using the command shown in Listing 8.18.
Listing 8.18: Installing an editable package locally.
As you can learn from running pip install --help (or by viewing the pytest documentation), the -e option installs the package in editable mode, so it will update automatically when we edit the files. The location of the installation is in the current directory, as indicated by the . (dot).
At this point, the test suite should be, if not quite GREEN, at least no longer RED:
(venv) $ pytest ============================= test session starts ============================= collected 0 items ============================ no tests ran in 0.00s ============================
Now we’re ready to start making some tests to check that the code in Listing 8.15 is actually working. We’ll start with a negative case, checking that a non-palindrome is correctly categorized as such:
def test_non_palindrome(): assert not Phrase("apple").ispalindrome()
Here we’ve used assert to assert that "apple" should not be a palindrome (Figure 8.55).
Figure 8.5 The word “apple”: not a palindrome.
In similar fashion, we can test a literal palindrome (one that’s literally the same forward and backward) with another assert:
def test_literal_palindrome(): assert Phrase("racecar").ispalindrome()
Combining the code from the above discussion gives us the code shown in Listing 8.19.
Listing 8.19: An actually useful test suite.
test/test_phrase.py
Now for the real test (so to speak):
Listing 8.20: GREEN
The tests are now GREEN, indicating that they are in a passing state. That means our code is working!
8.2.2 Pending Tests
Before moving on, we’ll add a couple of pending tests, which are placeholders/ reminders for tests we want to write. The way to write a pending test is to use the skip() function, which we can include directly from the pytest package, as shown in Listing 8.21.
Listing 8.21: Adding two pending tests. YELLOW
test/test_phrase.py
We can see the result of Listing 8.21 by rerunning the test suite:
Listing 8.22: YELLOW
Note how the test runner displays the letter s for each of the two “skips”. Sometimes people speak of a test suite with pending tests as being YELLOW, in analogy with the red-yellow-green color scheme of traffic lights (Figure 8.6), although it’s also common to refer to any non-RED test suite as GREEN.
Figure 8.6 A YELLOW (pending) test suite.
Filling in the test for a mixed-case palindrome is left as an exercise (with a solution shown in Listing 8.25), while filling in the second pending test and getting it to pass is the subject of Section 8.3 and Section 8.4.
8.2.3 Exercises
By filling in the code in Listing 8.23, add a test for a mixed-case palindrome like “RaceCar”. Is the test suite still GREEN (or YELLOW)?
In order to make 100% sure that the tests are testing what we think they’re testing, it’s a good practice to get to a failing state (RED) by intentionally breaking the tests. Change the application code to break each of the existing tests in turn, and then confirm that they are GREEN again once the original code has been restored. An example of code that breaks the test in the previous exercise (but not the other tests) appears in Listing 8.24. (One advantage of writing the tests first is that this RED–GREEN cycle happens automatically.)