Monday, June 22, 2009

The "right way" to write the first test for an object

You've probably come across someone who is teaching unit testing through a book or web page saying something along the following lines: "After writing a test, you should do the bare minimum to make sure the test passes."

Imagine that you're writing a simple calculator class. So many times, someone might say that if the first test is...

def test_adds(self):
c = Calculator()
r = c.add(2,3)
self.assertEquals(r, 5)

then your calculator class should look like...

class Calculator()
def add(self, a, b):
return 5

On the one hand, yes, just having "return 5" makes you test pass. Of course, "bare minimum" depends on what criterion you're using. "return 5" is less than "return a + b" in terms of character length, right? Complexity too, "5" is simpler than the operation "a + b".

The thing is, your calculator program can't add, although you have a test that says "test_can_add". I always found it funny that I could do this again...

def test_can_still_add(self):
c = Calculator()
r = c.add(6,3)

self.assertEquals(r, 9)

Ok, how about...

class Calculator(object):
def add(self, a, b):
if a == 6:
return 9
return 5

Now, this isn't right, obviously. You're not actually building your class to do what it should do, you're just making it pass the tests. What more, it's more complex than just...

class Calculator(object):
def add(self, a, b):
return a + b

I know this seems like drivel, but how you write this first test shows what you think the point of unit testing is. If you really think the first thing should be "return 5", then your test shouldn't be called "can_add", it should be "can_add_two_and_three", and you have your work cut out ahead of you to finish writing this calculator class. Some people will do something like this...

def test_can_add(self):
c = Calculator()
r1 = c.add(2,3)
r2 = c.add(3,4)

self.assertEquals(r1, 5)
self.assertEquals(r2, 7)

I guess this is acceptable. You no longer can just "return 5". Of course, along with the "do the simplest thing that works" mantra there is the other misunderstood mantra of "one assertion per test". Some people might think that the above breaks this second rule. I don't think it does, you're making the same assertion in both cases of 2+3 and 3+4 (that the object can add correctly), you're just doing it with different data.

However, I still wouldn't do it this way. If every time you write a test, you need to write two versions of the same test, you've doubled your code. And really, I don't see the point, other than to feel good because you think you're doing things the Right Way.

The main reason you might see code with multiple exercises of the same test (such as the example above, exercising the add method twice) is for edge cases. I'd prefer to keep edge cases to their own tests. For example, I would have one test for testing division, and another testing that dividing by zero raises an exception.

Of course, nothing is absolute, (except that (and that (and that...))), so go with what you find to make things simple, and still makes your tests worthwhile.

No comments:

Post a Comment