Debugging Test Failures
The find_all() test loops over every match and reports a problem only when a match does not hold the character being searched for, so a passing run prints nothing.
Reading a Test Before Running It
Many programs are checked by small tests. A test runs a function on known input and looks for anything unexpected in what comes back. The test in this article checks find_all(), a function that searches a string for every occurrence of one character. Before reading any explanation of how it works, read the test itself and notice what it does with each result it receives.
The test contains no list of expected output and no counting. It makes one promise: every position that find_all() reports should hold the letter 'a'. If that promise is broken even once, a message appears. The test is therefore about the quality of each individual result, not about how many results there are.
What do you think happens?
Run the find_all() test on the text Mary had a little lamb. What do you expect the test to print?
Reveal answer
Nothing is printed.
Every position that find_all() reports holds an 'a', so no check fails and the message never appears. A passing run prints nothing.
Tracing the Mary Had a Little Lamb Text
To see the test succeed in detail, trace the string by hand. Positions count from zero, which is how positions in a C++ string are numbered. The letter M sits at position 0, the letter a at position 1, and so on through the whole sentence. The letter 'a' appears at four positions in this text: 1, 6, 9, and 19. These are the matches find_all() should report, and each one should pass the test's check.
| Position | Character at that position | Does the check *p equal 'a' hold? | What the test does |
|---|---|---|---|
| 1 | a | Yes | Nothing is printed |
| 6 | a | Yes | Nothing is printed |
| 9 | a | Yes | Nothing is printed |
| 19 | a | Yes | Nothing is printed |
| 2 (only if find_all() were broken) | r | No | The message a bug! is printed |
A correct result at each reported position, with one broken result shown for comparison
What the Loop Variable Holds
The test loops over every match that find_all() returns. Inside the loop, a variable named p refers to one match at a time. The check inside the loop compares the character that p refers to with the character 'a'. The expression that reads that character is written *p, and it means the value stored at the place p refers to. The check is written in this form:
- *p reads the character that the iterator p refers to
- 'a' is the character every reported match should hold
- The message is printed only when the condition is true
Comparing the value that p refers to, rather than p itself, is what makes the test examine the characters and not the positions. The iterator is the thing that moves from match to match. The value is what the rule is about. Because the test reads the value, it can check whether each match holds the right letter, without caring where that match sits in the string.
Locating the First Divergent Match
Now suppose find_all() were broken and reported a position that holds a different letter, such as the r at position 2. The loop would reach that result, the check *p equal to 'a' would fail, and the message a bug! would appear. The output alone tells you that a mismatch happened, but not which result caused it. The divergence is at the single loop iteration where the character under p is not 'a'.
A practical way to pin the failure down is to print the position or the character inside the loop as well. This turns a single error message into a record of every match that was checked. Once you can see that sequence, the first unexpected entry marks where the result diverged from what you expected. In the broken example, the record would show positions 1, 2, 6, 9, and 19, and the entry for position 2 is where the trouble starts.
Why find_all() Is Not Limited to Strings
So far find_all() has been used on a string, but the idea behind it is not tied to strings. The principle that makes this possible is that iterators and standard algorithms behave the same way on every standard container for which their use makes sense. An algorithm does not need to know which kind of container it is working with, as long as the container provides the iterators the algorithm needs.
Because of this, the source suggests that find_all() can be generalized. Instead of searching only a string for a character, the same search can be written so that it works on other standard containers. The test you read earlier would still express the same expectation in that case: every match reported should satisfy the condition being searched for. The test does not change its promise when the container changes. Only the container being searched changes.
Mistakes When Reading a Silent Test
Common mistake Assuming a silent test never ran
What it looks likeThe test prints nothing for Mary had a little lamb and the learner concludes that the test did nothing.
Why it failsThe loop runs once for each match. A silent run still means
find_all()produced matches and each of them was checked.Do this insteadRemember what the loop does. Silence means every checked match passed.
Common mistake Assuming the test counts results
What it looks likeExpecting the test to report that four a characters were found.
Why it failsThe test contains no counting and no expected-output list. It only checks the quality of each result.
Do this insteadRead the test as a check on each individual match, not as a tally.
Common mistake Comparing the iterator instead of its value
What it looks likeWriting the check as p != 'a' instead of *p != 'a'.
Why it failsp refers to a position, while *p is the character stored there. Comparing p checks positions, not characters.
Do this insteadUse *p so the check reads the character that the iterator refers to.
Common mistake Stopping at the error message instead of printing the sequence of matches
Tracing a Broken Result
Suppose find_all() on Mary had a little lamb reports the positions 1, 6, 9, 13, and 19, where position 13 holds a different letter from 'a'. Using the position table from earlier, identify which reported position is the first divergent match, state what the loop prints for it, and explain how printing the position inside the loop would help you find it.
Hints
- Count from zero when you locate each reported position in the text
- Only the loop iteration whose value is not 'a' triggers the message
- Think about which entry in a printed record would stand out as the first unexpected one
Key Takeaways
- The
find_all()test loops over every match and reports a problem only when a match does not hold the character being searched for, so a passing run prints nothing. - The check compares the value that the loop iterator refers to, written *p, with 'a'. This is why the test examines characters rather than positions.
- A wrong match first appears at the single loop iteration where the value under p breaks the rule. Printing positions or characters inside the loop turns one error message into a record that shows where the result diverged.
- Standard iterators and algorithms behave the same way on every standard container for which their use makes sense, so
find_all()can be generalized beyond strings while the test's expectation stays the same.