Thursday, June 28, 2012

Unit Testing and Assumptions

When we write a unit test, for better or worse, we're locking down a section of code. This is good when we want to make sure the logic doesn't change. This can be bad, however, when our tests make it difficult to make good changes. Maybe you need to fix a bug, or change the details of an algorithm.

The problem is, sometimes unit tests make too many assumptions about the code under test. We assume that an algorithm will be implemented in a certain way. Perhaps the method contains a lot of side effects.

In my mind, the best-case scenario involves feeding input to a function and getting something in return. I give you a sentence, and you capitalize every word for me. I don't care how you do it, just that the output is what I expect:

def test_upper(self):
  input = "this is a sentence."
  output = upper(input)
  self.assertEqual("This Is A Sentence.", output)

These tests are short, easy to write, and they make no assumptions about the underlying code. It turns out that the less side effects a method has, the easier it is to test.

Cosider the following code:

class box:
  def __init__(self, length, width):
    self.__length = length
    self.__width = width

  def compute_area(self):
    return self.__length * self.__width

The constructor just sets two private fields. This is difficult to test without either accessing the internals of the class or exposing the fields through getters. Even then, we would be testing implementation details that are subject to change. I would argue that we should test the constructor indirectly by testing compute_area as follows:

def test_compute_area(self):
  my_box = box(4, 5)
  assertEqual(20, my_box.compute_area())

What we're really interested in is not that two private fields get set in the constructor, but that the object can compute its area.

No comments:

Post a Comment