views:

183

answers:

8

Reading this question has helped me solidify some of the problems I've always had with unit-testing, TDD, et al.

Since coming across the TDD approach to development I knew that it was the right path to follow. Reading various tutorials helped me understand how to make a start, but they have always been very simplistic - not really something that one can apply to an active project. The best I've managed is writing tests around small parts of my code - things like libraries, that are used by the main app but aren't integrated in any way. While this has been useful it equates to about 5% of the code-base. There's very little out there on how to go to the next step, to help me get some tests into the main app.

Comments such as "Most code without unit tests is built with hard dependencies (i.e.'s new's all over the place) or static methods." and "...it's not rare to have a high level of coupling between classes, hard-to-configure objects inside your class [...] and so on." have made me realise that the next step is understanding how to de-couple code to make it testable.

What should I be looking at to help me do this? Is there a specific set of design patterns that I need to understand and start to implement which will allow easier testing?

+1  A: 

Design patterns aren't directly relevant to TDD, as they are implementation details. You shouldn't try to fit patterns into your code just because they exist, but rather they tend to appear as your code evolves. They also become useful if your code is smelly, since they help resolve such issues. Don't develop code with design patterns in mind, just write code. Then get tests passing, and refactor.

Grant Palin
+1  A: 

A lot of problems like this can be solved with proper encapsulation. Or, you might have this problem if you are mixing your concerns. Say you've got code that validates a user, validates a domain object, then saves the domain object all in one method or class. You've mixed your concerns, and you aren't going to be happy. You need to separate those concerns (authentication/authorization, business logic, persistence) so you can test them in isolation.

Design patterns help, but a lot of the exotic ones have very narrow problems to which they can be applied. Patterns like composite, command, are used often, and are simple.

The guideline is: if it is very difficult to test something, you can probably refactor it into smaller problems and test the refactored bits in isolation. So if you have a 200 line method with 5 levels of if statements and a few for-loops, you might want to break that sucker up.

So, start by seeing if you can make complicated code simpler by separating your concerns, and then see if you can make complicated code simpler by breaking it up. Of course if a design pattern jumps out at you, then go for it.

hvgotcodes
+4  A: 

I'd say you need mainly two things to test, and they go hand in hand:

  • Interfaces, interfaces, interfaces
  • dependency injection; this in conjunction with interfaces will help you swap parts at will to isolate the modules you want to test. You want to test your cron-like system that sends notifications to other services? instanciate it and substitute your real-code implementation for everything else by components obeying the correct interface but hard-wired to react in the way you want to test: mail notification? test what happens when the smtp server is down by throwing an exception

I myself haven't mastered the art of unit testing (and i'm far from it), but this is where my main efforts are going currently. The problem is that i still don't design for tests, and as a result my code has to bend backwards to accomodate...

samy
+1  A: 

Michael Feather's book Working Effectively With Legacy Code is exactly what you're looking for. He defines legacy code as 'code without tests' and talks about how to get it under test.

As with most things it's one step at a time. When you make a change or a fix try to increase the test coverage. As time goes by you'll have a more complete set of tests. It talks about techniques for reducing coupling and how to fit test pieces between application logic.

As noted in other answers dependency injection is one good way to write testable (and loosely coupled in general) code.

Paul Rubel
A: 

I would suggest that read about Dependency Injection as well as IoC containers.

Okie , this is hard to understand at first glance but don't give up at very first ,try and learn.

I would say , see Martin fowler web site . Try to identify what your class intend to do and what services your class require to finish it's job. Again Dependency injection as well as well structured code is an art which comes with practice.

saurabh
A: 

Gerard Meszaros' xUnit Test Patterns: Refactoring Test Code is chock full of patterns for unit testing. I know you're looking for patterns on TDD, but I think you will find a lot of useful material in this book

The book is on safari so you can get a really good look at what's inside to see if it might be helpful: http://my.safaribooksonline.com/9780131495050

Mark Irvine
+1  A: 

have made me realise that the next step is understanding how to de-couple code to make it testable.

What should I be looking at to help me do this? Is there a specific set of design patterns that I need to understand and start to implement which will allow easier testing?

Right on! SOLID is what you are looking for (yes, really). I keep recommending these 2 ebooks, specially the one on SOLID for the issue at hand.

You also have to understand that its very hard if you are introducing unit testing to an existing code base. Unfortunately tightly coupled code is far too common. This doesn't mean not to do it, but for a good time it'll be just like you mentioned, tests will be more concentrated in small pieces.

Over time these grow into a larger scope, but it does depend on the size of the existing code base, the size of the team and how many are actually doing it instead of adding to the problem.

eglasius
A: 

Dependency Injection/IoC. Also read up on dependency injection frameworks such as SpringFramework and google-guice. They also target how to write testable code.

walters