I guess for what you really want to know, your example is not well suited, so let me describe a better one.
There are several programming problems where the most elegant, shortest implementation is not the one which just serves a few test cases, but one which solves the problem in a more general fashion than specified by the tests.
For example, one can implement a sorting algorithm incrementally by writing some test cases in a TDD fashion for sorting two, three or four elements. Maybe one starts with an implementation which can only sort two elements, like
int[] Sort(int[] list)
{
// ... maybe some handling for lists with 0 or 1 element here ...
if(list[0]>list[1])
return new[]{list[1],list[0]};
else
return new[]{list[0],list[1]};
}
Now, a test which passes 3 elements into this function will fail. However, when you extend this code to 3, 4 or more elements, you will quickly reach the point where a general algorithm like a bubble sort or insertion sort (which works for arbitrary list lengths) is simpler than an implementation which works only for a fixed number of elements. In fact, by doing TDD, you might have started with a convoluted algorithm which compared up to 4 elements one-by-one against each other, but in the refactoring step, you replaced this by a more general sorting algorithm, which makes the overall implementation simpler.
Now it should not be very astonishing when you add further tests with more elements, these tests won't fail, though you followed all the TDD rules literally, and though you did not implement more code than needed to satisfy all existing tests.
And yes, that is a real-world case, far more likely to happen as this answer pretends. I have encountered this situation lots of times for all different kind of problems in string processing, set manipulation, mathematical or geometrical algorithms: general implementations are very often simpler than specialized ones.
So what should one do in this case? Leaving out the additional tests with 5, 8 or 20 elements, just because the code "is already complete, and the additional tests won't induce any more code changes"? I would not recommend this - it is obviously good to have such additional tests for a complex algorithm, it will give you much more confidence into the correctness of the code.
The better alternative here is to make your additional tests artificially fail for a short time. The main purpose of seeing any tests failing first before they pass in TDD is to make sure the test is actually executed - it is a "test for the test". You could, for example, add a statement like
if(list.Length>=5)
return null;
somewhere inside the Sort
function, and remove it after you saw it the test failing. That proofs your new tests are actually executed, and you did not intermix it with some existing test.
Since you asked for another example: lets say you have a function
`string TrimNumeric(string value)`
which is expected to replace non-numeric characters from the beginning or end of the input string value
and return the result. What happens with non-numeric characters in the middle is not specified so far. The following test cases are already passing:
Assert.AreEqual("123",TrimNumeric("abc123"));
Assert.AreEqual("123",TrimNumeric("123xyz"));
Assert.AreEqual("456",TrimNumeric("abc456xyz"));
Now you get an additional requirement: non-numeric characters in the middle shall be stripped as well. You start by adding a test:
Assert.AreEqual("123",TrimNumeric("1a2b3cxyz"));
In such a case, if you don't know how TrimNumeric
is implemented internally, there is no indication if this test will fail or not. Indeed, if TrimNumeric
was implemented in a straightforward, simple, and general fashion, by iterating over all characters and keeping only the numeric ones, it is IMHO pretty likely this test will pass immediately. Nevertheless it should be clear why is necessary to write such a test. Maybe the test will not pass, if the implementation looks differently. But if it passes right from the start, make sure you make it fail at least temporarily, to be sure it gets executed.