Thursday, July 23, 2009

TDD Part 1: Why Use It?

new_coke_great_taste

Anytime you are going try something new, there should be a reason, right?  You wouldn’t just go out and spend your hard earned money on a can of New Coke unless you thought it was going to enhance your soft drink refreshment experience.  You have an expectation that the taste could be better, so you try it.  Maybe not a great example since New Coke didn’t work out so well, but you get the idea.

So that begs the question: Why use TDD?

I’m sure you could find millions of reasons why you should (or shouldn’t) use TDD out there.  But for me, it comes down to three main points:

  • To drive the design of your application.
  • So you write better code.
  • So your code is “covered”.

Wow that sounds good.  Tell me more.

TDD – It’s Not Just About Testing
This was one of those “huh?” things the first time I heard it.  TDD is as much (if not more) about design as it is about testing.  The idea is that you write your tests first and then write just enough code to satisfy the test (more on this in the next post).

regret-testing

Now this was a little tricky for us because we are not an agile shop.  We’re more of a waterfall kinda place.  But TDD can still work well even in when using a waterfall methodology.  The key is to not design every tiny detail before you begin coding, but instead to define the basic structure.

Using our sample described in the introduction, let’s look at a couple of different ways we could write a design for a method that calculates shipping:

You could do it like this:

InvoiceManager.CalculateShipping():

  1. Receives an OrderDetails parameter.
  2. Returns a decimal.
  3. Shipping = OrderDetails.Products.Count * 3
  4. If OrderDetails.OrderTotal > 100, Shipping is Shipping *.9.
  5. If Shipping > 30, Shipping is 30.

Or like this:

Calculating Shipping:

  1. Create a module that will calculate and return the shipping for an order:
    - Order quantity * 3
    - If order total > 100, reduce shipping by 10%
    - Maximum amount for shipping is 30

Now although these are very similar, and basically define the same thing (we need to calculate shipping for an order) the first one is much more restrictive, much more dictator-like:

  • You must create a class called InvoiceManager!
  • You must create an OrderDetails object, and this object will have an OrderTotal and a Products collection – do you understand??
  • If you do not create a CalculateShipping() method that takes in an OrderDetails and returns a decimal, you will be promptly shot!

The second approach leaves you a little bit of room.  What if you write your CalculateShipping() method and you <gasp> change your mind?  You want to take in the order total and order quantity instead of an entire OrderDetails object?  What if you decide you don’t want to name your class InvoiceManager because you realize it’s a stupid name?  You have some room to move with regard to the design of the code, but the end result will be the same: something that correctly calculates shipping.

Now when you’re writing your tests, you can flush out these details as they are needed.  It will help you avoid over-engineering something right from the start.  And it will most likely result in cleaner, less complex code.

Write Better Code
In my experience, using TDD helps my code turn out better in a few ways:

  • No Extraneous Code – using the “only-write-the-minimum-code-required” principle, you don’t end up with extra stuff.  All code you write is there because there was a failing test that forced you to write it.
  • Smaller, Simpler Objects – I’ve found that in order to test something, it needs to be simple to test.  This naturally drove me to writing smaller objects that were less complex and basically did only 1 thing (SRP, anyone?).
  • Separation of Concerns – Similar to the previous point, in order to easily test something you need to have good separation of concerns so you are only testing 1 thing.  More on this in a future post.

Don’t Worry, You’re Covered
Warm and fuzzy.  That’s how well written unit tests can make you feel.  You know that your code is solid and, more importantly, that it can be modified without fear.  If you have tests that verify all of the required functionality for some module, you can refactor, modify, extend or otherwise mess with that module – and you know you’re covered.  If you break some existing functionality, your tests will smack you in the face.  It’s a wonderful thing.

0 comments: