Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to design code for testability

I have been looking at using TDD and implementing proper testing (only just started to learn how much better it makes your life) in any of my projects that I create in the future. So for the last couple of days I have been floating around on SO trying to learn about how to design your application for testability, but I still seem to be struggling with some of the ideas.

I have read a lot that you should program against interfaces rather than classes. The main problem I'm having is, how many interfaces should you create? Should you have one for everything you want to test? or am I reading this wrong?

Another thing is use lots of dependency injection, so you can mock the parts that you are injecting rather than use the real things. Is this correct? or am I way off here too?

like image 466
Nathan W Avatar asked Nov 25 '08 06:11

Nathan W


3 Answers

I think you have the right idea, but I think you are making this into a bigger deal than it is. If you start doing TDD, your first reaction will probably be 'is this it?'. And then later, you should hopefully say 'aha'!

The main thing is that you get nUnit, learn the tutorial, and then make sure you write a test for everything you do before you write the implementation. You might skip over writing tests for property accessors, but anything that requires any calculations, you should write a test first.

So, pretend you're testing a calculator's Add(int 1, int 2), the first thing you think is, 'how can I break this'. My ideas where things could go wrong are: negative numbers, zeros, and overflows. The trick is to imagine every mistake the person who is going to create the Add() method might make, and then write a test against it. So, I might write:

Assert.AreEqual(5, calc.Add(2, 3), "Adding positives not as expected");
Assert.AreEqual(-5, calc.Add(-2, -3), "Adding negatives not as expected");
Assert.AreEqual(-2, calc.Add(-3, 2), "Adding one positive and one negative not as expected");

// your framework might provide a cleaner way of doing this:
try {
  int result = calc.Add(Int32.Max, 5);
  Assert.Fail("Expected overflow error. Received: " + result);
} catch(Exception e) {
  // This should be a more specific error that I'm not looking up
}

So, as you can see, what I've tried to do is figure out how the Add() method might not work, and then test against it. I've also looked for interesting corner cases and explicitly defined the behaviour that I'm expecting. And then now I'm free to go off and code the Add() method.

Now, while that's not all that great, you know that your Add() method will be rock-solid for when you start creating complex math functions that combine your Sin() method with your Sqrt() method along with your Add() method.

That, for better or worse, is Test Driven Development. Don't get too hung up on the interfaces or dependency injection for now. That can come later, if you need it.

like image 152
tsimon Avatar answered Nov 18 '22 16:11

tsimon


First off.. TDD is No Magic/Silver Bullet. Invest some time with this.. it'll be worth your while. Don't try and pick up TDD on-the-fly or off-SO-posts. Pick up a good book (Kent Beck / Dave Astels) and work your way up.. (it's depressing to see people thrashing around when all they need is to take a step back and read.)

Design for testability:

  • Write code test first.. your testable design shall emerge.. one passing test at a time. You get it as a by-product.. more like a treat for doing it right.
  • Wishful thinking: Imagine that the thing you want to build already exists. How would you talk to it? This simple simulation should hand you the external interface definition of 'the thing'
  • Keep it Simple / YAGNI / Hold the hammer: Invest some time to see through the practices (Patterns, DI, Mocks, you'll find lots etc.) to find why they exist in the first place. Evaluate if you need them and only then proceed to use them. Another good thing to look for is 'where not to use them?'. If it sounds like a bad idea.. it probably is. (e.g. do I need an interface to do PR for every class that I write.. probably not. Do I need to Spring.net every class I create? ) Ask the following questions at each checkpoint (K. beck's definition of a simple design)

    • Do the code and tests communicate everything I need to communicate?
    • Is there any duplication? (Eliminate if any)
    • Does it contain the fewest possible classes?... the fewest possible methods? Is there anything that I could take away from this design and still preserve behavior.
  • Refactor mercilessly: listen to Martin Fowler and keep the book handy

(it's an interesting process trying to distill what you do to a select few steps :) Thanks for the opportunity to reflect)

like image 27
Gishu Avatar answered Nov 18 '22 15:11

Gishu


I like using Kent Beck's rule of thumb.

Write a test that describes your ideal Object API. In other words write the code that you wish someone else could write to make something happen. You might not be able to actually implement it that way in the end, but you may as well start off with the BEST interface at the get go rather than something else.

Also, just a little Testing tip... don't over think it. There are lots of people who will tell you HOW to test, but in my experience you are the only person who can truely make that decision. Just know that the very fact you are testing is making your code better, and do what feels right. As you look at other well tested code, you will start to develop a style and be a pro in no time.

Hope this helps.

like image 43
ewakened Avatar answered Nov 18 '22 16:11

ewakened