6.5. Testing for sanity

Often, you will find that a unit of code contains a set of reciprocal functions, usually in the form of conversion functions
where one converts A to B and the other converts B to A. In these cases, it is useful to create a “sanity check” to make
sure that you can convert A to B and back to A without losing decimal precision, incurring rounding errors, or triggering
any other sort of bug.

We’ve seen the range function before, but here it is called with two arguments, which returns a list of integers starting at the first argument (1) and counting consecutively up to but not including the second argument (4000). Thus, 1..3999, which is the valid range for converting to Roman numerals.

I just wanted to mention in passing that integer is not a keyword in Python; here it’s just a variable name like any other.

The actual testing logic here is straightforward: take a number (integer), convert it to a Roman numeral (numeral), then convert it back to a number (result) and make sure you end up with the same number you started with. If not, assertEqual will raise an exception and the test will immediately be considered failed. If all the numbers match, assertEqual will always return silently, the entire testSanity method will eventually return silently, and the test will be considered passed.

fromRoman should only accept uppercase Roman numerals (i.e. it should fail when given lowercase input).

In fact, they are somewhat arbitrary. We could, for instance, have stipulated that fromRoman accept lowercase and mixed case input. But they are not completely arbitrary; if toRoman is always returning uppercase output, then fromRoman must at least accept uppercase input, or our “sanity check” (requirement #6) would fail. The fact that it only accepts uppercase input is arbitrary, but as any systems integrator will tell you, case always matters, so it’s worth specifying
the behavior up front. And if it’s worth specifying, it’s worth testing.

The most interesting thing about this test case is all the things it doesn’t test. It doesn’t test that the value returned
from toRoman is right or even consistent; those questions are answered by separate test cases. We have a whole test case just to test for uppercase-ness. You might
be tempted to combine this with the sanity check, since both run through the entire range of values and call toRoman.[13] But that would violate one of our fundamental rules: each test case should answer only a single question. Imagine that you combined this case check with the sanity check, and
then that test case failed. You would have to do further analysis to figure out which part of the test case failed to determine
what the problem was. If you have to analyze the results of your unit testing just to figure out what they mean, it’s a sure
sign that you’ve mis-designed your test cases.

There’s a similar lesson to be learned here: even though “we know” that toRoman always returns uppercase, we are explicitly converting its return value to uppercase here to test that fromRoman accepts uppercase input. Why? Because the fact that toRoman always returns uppercase is an independent requirement. If we changed that requirement so that, for instance, it always
returned lowercase, the testToRomanCase test case would have to change, but this test case would still work. This was another of our fundamental rules: each test case must be able to work in isolation from any of the others. Every test case is an island.

Note that we’re not assigning the return value of fromRoman to anything. This is legal syntax in Python; if a function returns a value but nobody’s listening, Python just throws away
the return value. In this case, that’s what we want. This test case doesn’t test anything about the return value; it just
tests that fromRoman accepts the uppercase input without raising an exception.