The limitations of unit testing

Any test that involves more than one unit is automatically not a unit test. That matters because the results of unit tests tend to be particularly clear about what a problem is and where to find it.

When you test multiple units at once, the results of the various units get mixed together. In the end, you have to wonder about both what the problem is (is the mistake in this piece of code, or is it correctly handling bad input from some other piece of code?), and where the problem is (this output is wrong, but how do the involved units work together to create the error?).

Empirical scientists must perform experiments that check only one hypothesis at a time, whether the subject at hand is Chemistry, Physics, or the behavior of a body of program code.

Example – identifying units

Imagine for a moment that one of your coworkers has written the following code, and it's your responsibility to test it:

class Testable:
    def method1(self, number):
        number += 4
        number **= 0.5
        number *= 7
        return number

    def method2(self, number):
        return ((number * 2) ** 1.27) * 0.3

    def method3(self, number):
        return self.method1(number) + self.method2(number)

    def method4(self):
        return 1.713 * self.method3(id(self))

Here are some things to think about: Which sections of this code are the units? Is there only one unit consisting of the entire class? Is each method a separate unit? What about each statement, or maybe each expression?

In some sense, the answer is subjective because part of the definition of a unit is that it is meaningful. You can say that the whole class is a single unit, and in some circumstances that might be the best answer. However, it is easy to subdivide most classes into methods, and normally methods make better units because they have well-defined interfaces and partially isolated behaviors, and because their intent and meaning should be well understood.

Statements and expressions don't make good units because they are almost never particularly meaningful in isolation. Furthermore, statements and expressions are difficult to target: unlike classes and methods, they don't have names or easy ways to focus a test on them.

Here are some things to think about: What will be the consequences of choosing a different definition of unit for this code? If you have decided that methods are the best units, what would be different if you had picked classes? Likewise, if you picked classes, what would be different if you'd picked methods?

Here are some things to think about: Take a look at method4. The result of this method depends on all of the other methods working correctly; worse, it depends on the unique ID of the self object. Can method4 be treated as a unit? If we could change anything except method4, what is that we have to change to allow it to be tested as a unit and produce a predictable result?

Choosing units

You can't organize a suite of unit tests until you decide what constitutes a unit. The capabilities of your chosen programming language affect this choice. For example, C++ and Java make it difficult or impossible to treat methods as units (because you can't access a method without first instantiating the class it's part of); thus, in these languages each class is usually treated as a single unit, or metaprogramming tricks are used to force the methods into isolation so that they can be tested as units. C, on the other hand, doesn't support classes as language features at all, so the obvious choice of unit is the function. Python is flexible enough that either classes or methods can be considered as units and, of course, it has standalone functions as well; it is also natural to think of them as units.

The smaller the units are, the more useful the tests tend to be because they narrow down the location and nature of bugs more quickly. For example, if you choose to treat the Testable class as a unit, tests of the class will fail if there is a mistake in any of the methods. This tells you that there's a mistake in Testable, but not that it's in method2, or wherever it actually is. On the other hand, there is a certain amount of rigamarole involved in treating method4 and its like as units, to such an extent that the next chapter of the book is dedicated to dealing with such situations. Even so, I recommend using methods and functions as units most of the time because it pays off in the long run.

When you were thinking about method4, you probably realized that the function calls to id and self.method3 were the problem, and that the method can be tested as a unit if they didn't invoke other units. In Python, replacing the functions with stand-ins at runtime is fairly easy to do, and we'll be discussing a structured approach to this in the next chapter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.139.224