views:

568

answers:

7

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?

A: 

You are right with the Mocking part, if you actually are doing what you said.

As for the interfaces, my personal method of development considering this point is that I first write what my application's main path should look like (using my ideal mock API that does nothing). Then after looking through my Mock implementation and refining it a few times, I start the actual class design. At this stage "know" how I can test each method, and write a test for it, and if for some reason I come across a method I can't easily figure out how to test, it means the method is not good, most likely it has too many responsibilities, and too many intermediary states I can't test, so I break the method up (interface wins over class design here). And keep on trickling down until I have a workable application.

Robert Gould
+2  A: 

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.

ewakened
+1  A: 

You're heading in the right direction but don't get too crazy. You don't need an interface for every object you want to test for example.

Understand that the main motivation for both techniques you describe are to reduce coupling in the system. Tightly coupled systems are hard to test because there's no place for me to detect correct/incorrect behavior and I can't prevent side-effects in the real system (accessing files, databases, etc).

(It turns out that tight coupling also makes systems difficult to maintain, so the effort to break coupling for the purpose of testing also brings other benefits, which is why so many people stress the design benefits of unit testing in general and TDD in particular.)

Jeffrey Fredrick
+7  A: 

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.

Travis
+1  A: 

If you apply TDD, you'll obtain a design that is testable - and tested - as you'll grow your unit tests suite alongside your code. The design emerges, driven by the test.

Find a rough design (*) to solve the problem at hand, then start with a class that does not depend on another class, implement it, tests first. Then you can either implement a class that has no dependency too, or a class that depends on a class you already implemented.

When implementing a class that depends on another one, you have two possibilities for testing it : either you use an instance of the other class, or you mock it, in which case having an interface may help. With a language that does not support interface, you can just subclass the class you want to mock.

It's best to program against an interface when you don't want the class you're coding to depend on another class, merely not to depend to another class to belongs to another layer or another component.

(*) You do not have to design everything as test-driving your code may lead you into another direction: see the bowling game article which demonstrates TDD and emergent design in a few pages.

philippe
+5  A: 

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)

Gishu
+1 for "nice" answer
Robert Gould
@Gishu Buying test driven development by example right now, it is a bit hard to find the books that you need here in AUS at a good price sometimes. $86 for Code Complete but only about $40 incl post from amazon.com, but the exchange rate to the US is shit..so there goes that idea.
Nathan W
@Nathan.. well I live in India.. where most of the new books don't show up at all. Yeah its expensive.. but I'd buy some of these books without blinking because they are that good.. and if I factor in the time they save by making me better at my work.. it pays off handsomely. TDDbEis one of them
Gishu
@Gishu I totally agree that its worth buying good books, its just most of the time I don't have the money to buy them, but hey I WILL buy this one no matter what....I might even be able to get work to buy it for me ;)
Nathan W
+2  A: 

First, read the Google Guide to Writing Testable Code which has just been released on November 24th.

It is a great compilation of testability best practices by Miško Hevery, testing evangelist at Google.

Franck
This is a great site, I have been reading it all day ;)
Nathan W