- 8.1 Package Setup
- 8.2 Initial Test Coverage
- 8.3 Red
- 8.4 Green
- 8.5 Refactor
In this sample chapter from Learn Enough Python to Be Dangerous, you will obtain an introduction to testing in Python, including a first look at test-driven development, or TDD.
Although rarely covered in introductory programming tutorials, automated testing is one of the most important subjects in modern software development. Accordingly, this chapter includes an introduction to testing in Python, including a first look at test-driven development, or TDD.
Test-driven development came up briefly in Section 6.5.1, which promised that we would use testing techniques to add an important capability to finding palindromes, namely, being able to detect complicated palindromes such as “A man, a plan, a canal—Panama!” (Figure 6.5) or “Madam, I’m Adam.” (Figure 8.11). This chapter fulfills that promise.
Figure 8.1 The Garden of Eden had it all—even palindromes.
As it turns out, learning how to write Python tests will also give us a chance to learn how to create (and publish!) a Python package, another exceptionally useful Python skill rarely covered in introductory tutorials.
Here’s our strategy for testing the current palindrome code and extending it to more complicated phrases:
Set up our initial package (Section 8.1).
Write automated tests for the existing ispalindrome() functionality (Section 8.2).
Write a failing test for the enhanced palindrome detector (RED) (Section 8.3).
Write (possibly ugly) code to get the test passing (GREEN) (Section 8.4).
Refactor the code to make it prettier, while ensuring that the test suite stays GREEN (Section 8.5).
8.1 Package Setup
We saw as early as Section 1.5 that the Python ecosystem includes a large number of self-contained software packages. In this section, we’ll create a package based on the palindrome detector developed in Chapter 7. As part of this, we’ll set up the beginnings of a test suite to test our code.
Python packages have a standard structure that can be visualized as shown in Listing 8.1 (which contains both generic elements like pyproject.toml and non-generic elements like palindrome_YOUR_USERNAME_HERE). The structure includes some configuration files (discussed in just a moment) and two directories: a src (source) directory and a tests directory. The src directory in turn contains a directory for the palindrome package, which includes a special required file called __init__.py and the palindrome_YOUR_USERNAME_HERE module itself.2 (It is possible to flatten the directory structure by eliminating the package directory, but the structure in Listing 8.1 is fairly standard and is designed to mirror the official Packaging Python Projects documentation.) The result of the structure in Listing 8.1 will be the ability to include the Phrase class developed in Chapter 7 using the code
from palindrome_mhartl.phrase import Phrase
Listing 8.1: File and directory structure for a sample Python package.
We can create the structure in Listing 8.1 by hand using a combination of mkdir and touch, as shown in Listing 8.2.
Listing 8.2: Setting up a Python package.
At this point, we’ll fill in a few of the files with more information, including the project configuration file pyproject.toml (Listing 8.3), a README file README.md (Listing 8.4), and a LICENSE file (Listing 8.5).3 Some of these files are only templates, so you should replace things like <username> in pyproject.toml with your own username, the url field with the planned URL for your project, etc. (Being able to do things like this is an excellent application of technical sophistication.) To see a concrete example of the files in this section, see the GitHub repo (https://github.com/mhartl/python_package_tutorial) for my version of this package.
Listing 8.3: The project configuration for a Python package.
~/python_package_tutorial/project.toml
Listing 8.4: A README file for the package.
~/python_package_tutorial/README.md
Listing 8.5: A license template for a Python package.
~/python_package_tutorial/LICENSE
With all that configuration done, we’re now ready to configure the environment for development and testing. As in Section 1.3, we’ll use venv for the virtual environment. We’ll also be using pytest for testing, which we can install using pip. The resulting commands are shown in Listing 8.6.
Listing 8.6: Setting up the package environment (including testing).
At this point, as in Section 1.5.1, it’s a good idea to create a .gitignore file (Listing 8.7), put the project under version control with Git (Listing 8.8), and create a repository at GitHub (Figure 8.2). This last step will also give you URLs for the configuration file in Listing 8.3.
Figure 8.2 The package repository and README at GitHub.
Listing 8.7: Ignoring certain files and directories.
.gitignore
Listing 8.8: Initializing the package repository.
8.1.1 Exercise
If you haven’t already, update Listing 8.3 with the right package name and fill the url and Bug Tracker fields with the corresponding GitHub URLs (the tracker URL is just the base URL plus /issues). Likewise, update the license template in Listing 8.5 with your name and the current year. Commit and push your changes up to GitHub.