8.3 Red
In this section, we’ll take the important first step toward being able to detect more complex palindromes like “Madam, I’m Adam.” and “A man, a plan, a canal—Panama!”. Unlike the previous strings we’ve encountered, these phrases—which contain both spaces and punctuation—aren’t strictly palindromes in a literal sense, even if we ignore capitalization. Instead of testing the strings as they are, we have to figure out a way to select only the letters, and then see if the resulting letters are the same forward and backward.
The code to do this is fairly tricky, but the tests for it are simple. This is one of the situations where test-driven development particularly shines (Box 8.1). We can write our simple tests, thereby getting to RED, and then write the application code any way we want to get to GREEN (Section 8.4). At that point, with the tests protecting us against undiscovered errors, we can change the application code with confidence (Section 8.5).
We’ll start by writing a test for a palindrome with punctuation, which just parallels the tests from Listing 8.19:
def test_palindrome_with_punctuation(): assert palindrome.ispalindrome("Madam, I'm Adam.")
The updated test suite appears in Listing 8.25, which also includes the solution to a couple of exercises in Listing 8.23 (Figure 8.76).
Figure 8.7 “RaceCar” is still a palindrome (ignoring case).
Listing 8.25: Adding a test for a punctuated palindrome. RED
test/test_phrase.py
As required, the test suite is now RED (output somewhat streamlined):
Listing 8.26: RED
At this point, we can start thinking about how to write the application code and get to GREEN. Our strategy will be to write a letters() method that returns only the letters in the content string. In other words, the code
Phrase("Madam, I'm Adam.").letters()
should evaluate to this:
"MadamImAdam"
Getting to that state will allow us to use our current palindrome detector to determine whether the original phrase is a palindrome or not.
Having made this specification, we can now write a simple test for letters() by asserting that the result is as indicated:
assert Phrase("Madam, I'm Adam.").letters() == "MadamImAdam"
The new test appears with the others in Listing 8.27.
Listing 8.27: Adding a test for the letters() method. RED
test/test_phrase.py
Meanwhile, although we aren’t yet ready to define a working letters() method, we can add a stub: a method that doesn’t work, but at least exists. For simplicity, we’ll simply return nothing (using the special pass keyword), as shown in Listing 8.28.
Listing 8.28: A stub for the letters() method. RED
src/palindrome/phrase.py
The new test for letters() is RED as expected (which also shows that the pass in Listing 8.28 just returns None):
Listing 8.29: RED
With our two RED tests capturing the desired behavior, we’re now ready to move on to the application code and try getting it to GREEN.
8.3.1 Exercise
Confirm that commenting out the letters() stub in Listing 8.28 yields a failing state rather than an error state. (This behavior is relatively unusual, with many other languages distinguishing between a non-working method and one that’s missing altogether. In Python, though, the result is the same failing state in either case.)