DevOps Zone is brought to you in partnership with:

I am the senior IT manager at the Macau Productivity and Technology Transfer Center. I've written popular books on agile and web technologies and created an open source testing library for Wicket. kent is a DZone MVB and is not an employee of DZone and has posted 7 posts at DZone. You can read more from them at their website. View Full User Profile

TDD adapted for mere mortals

06.24.2011
| 8550 views |
  • submit to reddit

I’ve been teaching and practicing agile for several years and there is definitely a problem with TDD: People find it very difficult to use. I believe there are certain points, either in the TDD itself or in people’s interpretation of it, that should adapted (at least for mere mortals):

Writing test before code

It is definitely a very good practice to interweave coding and testing. This is what we programmers want to do; we feel the urge to test run a certain piece of code as it feels complicated. However, writing the test before code is not a natural way in many cases. For example, let’s consider a BookService class. You’d like to implement its borrow(String borrowerId, String callNo) method. If you insists on writing the test first, you’ll have to think very hard what collaborators the BookService object will use. It is not only difficult, but most likely incorrect. A much more effective way is to write the borrow() method first, then you can see what collaborators it needs (e.g., a BookDAO, a BorrowerDAO, a system clock and etc).

Most TDD demos don’t have this problem because they work on classes that need no collaborators, for example, stacks, calculators.

Note that I am not advocating writing the complete code before writing the test; we should build the functionality in suitable steps. For mere mortals, try to implement the basic functionality first, then test it, then write more code and then more test.

My suggestion is to replace "writing test before code" with "interweaving coding and testing".

Take the smallest step that makes the test pass

I agree that we shouldn’t write too much code without test-running the code. If we do, it’s difficult to isolate the bug. But why always take the smallest step if we are pretty sure that it is going to work? The size of the step depends on the complexity of the code. We shouldn’t take too large a step (hard to isolate a bug), nor too small a step (waste of time).

The whole idea is a well-established principle in testing: Risk-based testing. That is, we should put more effort on testing high-risk code, and less on low-risk code. Programmer’s effort is the most scarce resource in software development projects. So, we should prioritize its use wisely.

My suggestion is to replace "take the smallest step possible" with "take the smallest step before you’re worried with the correctness of the code".

If you aren’t doing TDD, you aren’t professional

This is not defined in TDD, but many people believe it. I think this is against the agile manifesto which says we should value people over process. Forcing TDD into people’s throats is exactly the opposite. If people have tried but it doesn’t help them, they will simply not use it. It’s that simple. People should have every right to use whatever works best for them.

In fact, most programmers like testing: they feel the urge to test run the code if it gets complicated. It’s just that writing the test before code is so difficult and against their nature. Therefore, our process should work with their nature, not against it.

My suggestion is to replace "every professional programmer should do TDD" with "every professional programmer should keep looking for their own best practices".

TDD helps you design the API of your code

This doesn’t make much sense to me at all. The user requirement guides you in implementing your UI classes. When implementing the UI classes, you are guided to design the API of your service classes. When implementing your service classes, you are guided to design the API of your DAO classes.

The real design aspect of TDD is not about the design of the API, but to make sure your code is loosely-coupled and thus is easy to test.

My suggestion is to replace "TDD helps you design the API" with "testing helps make your code loosely-coupled".

References
Published at DZone with permission of kent tong, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Heiko Mausolf replied on Sat, 2011/06/25 - 6:34am

Very good article, thank you. One thing that makes it difficult for mere mortals like me to adapt a new paradigma like TDD is that this requires a complete new way of thinking that I am just not used to. It was hard for me to find my way into OO programming. I had learnt structural programming with PL/1 and Pascal, so it was quite painful to come to grips with thinking in objects, classes and methods. So obviously, it took me a while to embrace OO.

Since my early beginnings in the 80s it was good practice to test my programs and systems after I had written them (more likely, someone else had to run tests on what I had written). For me, TDD sounded great from the first day I read about it. But it was hard (and sometimes still is) to strictly follow it. I'm still in the process of internalizing TDD. That makes articles like yours so important: it offers me another point of view to let TDD become one of my natural habits.

Mladen Girazovski replied on Sat, 2011/06/25 - 11:03am

Writing test before code It is definitely a very good practice to interweave coding and testing. This is what we programmers want to do; we feel the urge to test run a certain piece of code as it feels complicated. However, writing the test before code is not a natural way in many cases. For example, let’s consider a BookService class. You’d like to implement its borrow(String borrowerId, String callNo) method. If you insists on writing the test first, you’ll have to think very hard what collaborators the BookService object will use. It is not only difficult, but most likely incorrect. A much more effective way is to write the borrow() method first, then you can see what collaborators it needs (e.g., a BookDAO, a BorrowerDAO, a system clock and etc). Most TDD demos don’t have this problem because they work on classes that need no collaborators, for example, stacks, calculators. Note that I am not advocating writing the complete code before writing the test; we should build the functionality in suitable steps. For mere mortals, try to implement the basic functionality first, then test it, then write more code and then more test. My suggestion is to replace "writing test before code" with "interweaving coding and testing".

I don't mean to offend, but i think you missed some very important aspects of TDD.

Of course tests have to be written before the code if you're working test driven, and of course this goes against the "classical" way of development we were tought, changing our way of programming is the hardest part of TDD, not because it is unnatural, but because it goes against our habbit of writing code first.

Test driving a class that uses collaborators that haven't been written yet is one of the biggest strengths of TDD, it's called "interface discovery". You'll use mocks and fakes to define the interface of the collaborators.

A TDD demo that does not show this aspect is incomplete.
This is where TDD helps you to design the API of your App.

Whats wrong with having your BookService use an BookDao and whatever other objects it would need? Use mocks, fake implementations etc. to have a test double for your BookService Test.

This is where TDD helps you to design the API of your App.

Kim David Hagedorn replied on Sat, 2011/06/25 - 11:32am

Your approach is a good and practical approach to testing but it's not TDD. It's as simple as that. Some other thoughts that came to my mind:

 1) TDD works! know that from personal experience.

 2) TDD works especially good, if your API or some business functionality is not clearly defined, because you're looking at the system under test from a collaborators perspective.

 3) TDD is hard to learn. Don't try to learn it 'on the job' while you're building some functionality that needs to be shipped and there's probably a deadline up your neck. Take yourself some time dedicated to learn TDD and practice, using a code kata or some hobby project. Start with a fresh codebase. Maybe stick a note saying 'test first!' and 'red-green-refactor' to your monitor.

 4) Yes you will get things wrong in your tests when you write them first and you will need to adapt both your code and your tests.

 5) My last thought might be a little bit heretic, but you're absolutely right to not test-drive anything. TDD is expensive and will take more time, so it is best used for the hard and/or unclear parts of a project.

Loren Kratzke replied on Sat, 2011/06/25 - 2:33pm

Good article. I am (ehem) no fan of TDD and it should come as no surprise that I don't enjoy writing tests that much either. I really enjoy writing API's though. I have been writing Java since 1.0 was in beta and have seen many facets of what makes for great software design.

If I could tell every TDD person just one thing, I would tell them "It's the interfaces, stupid.". These have been and always will be the gold standard of API design. Given requirements, even vague requirements, a team can sit down and design most of a very large scale project by defining the interfaces. Interfaces are contracts, tests are more like lawyers.

Given said interfaces a team can quickly stub out implementation classes so that the project can be wired, compiled, and run. At that point the team breaks out into smaller groups or individuals and writes full implementation of the interfaces. Then through continuous integration and the occassional shout over the cube ("Update if you want to pick up new account screen!"), the project comes to life.

My point is that the interfaces are the contract, not the tests. You can form much clearer thought around an interface and there is no reason why test and implementation code can not then be written concurently once an interface is defined. That is what really bugs me about TDD - it only makes sense if all you care about is tests.

Lund Wolfe replied on Sat, 2011/06/25 - 3:00pm

I totally agree that we need to understand the principles of TDD. There are TDD nazis and probably TDD shops who are just typing tests and typing code. That is the impression you get from the ideal examples of trivial apps or do-it-in-your-sleep feature enhancements to applications designed for testing.

TDD is not a substitute for considered design and quality code. You need to be comfortable with your design and you should design with modular testing in mind, especially for critical/core code, so that users and developers won't be deathly afraid of significant refactoring when the code grows in complexity and features over time. Don't write tests that will have to be thrown away because the basic API is still changing. TDD is appropriate for adding features and fixing bugs because it does force you to think about the method level API and testability before you can write specific tests before you touch any actual application code.

Code that will be getting enhancements needs to be built with testability in mind. You don't need 100% coverage and maybe you can skip complex GUI end to end tests if the underlying code paths and logic is being tested. Bad and brittle code does need tests. Tests should be added as the bugs are fixed. These tests will be a comfort if it becomes obvious (repeated test failures) that refactoring is required as well as just pointing out the bugs as soon as they appear in the code.

Unfortunately, the weaker the design and the weaker the developers, the more necessary testing/refactoring becomes, since the code will degrade faster. The more necessary testing/refactoring becomes, the harder it will be to do testing/refactoring. This worst case scenario is very common and this is probably why they say if you aren't doing TDD you aren't a professional.

Witold Szczerba replied on Sat, 2011/06/25 - 4:14pm

I would like to take this opportunity to propose the book: Grownig Object-Oriented Software, Guided by Tests to anyone who hasn't read it yet. Authors presents all the practices they developed in many many years, so it is rather a pragmatic point of view, and I must say - they are convincing.

Another source of great amout of practice and knowledge in this subject is the blog (especially entries from 2008-2009) and many presentations of Misko Hevery, the Agile Coach at Google. One of ma favorite talk:

http://vimeo.com/19726832
the video quality is bad, but voice is clear and the slides are available here:
https://docs.google.com/present/view?id=dgvs5xhc_115hm5rpc3n
I would really really urge you to listen to the presentation (how could you invest in yourself 37 minutes of your life better?), slides alone do not make a whole picture.

I support the idea that one of the root cause of problems with unit testing is the badly written, untestable code and, as pointed out in the presentation above (the "Red flags all around" slide), it is surprisingly easy to detect and eliminate during, even brief, code reviews. I would like to encourage you to print the excercise with the code which was described as "Designed to slap you around!" and show it to your colleagues at work to check if they would identify the "red flags" in the code - and to those who did not figured it out - to ask them to watch the presentatio or even better, to make an offer to present the idea behind that talk by yourself.

Nicolas Bousquet replied on Mon, 2011/06/27 - 4:05am

Honestly I don't buy the whole TDD thing. I can understand that it work for some, and that's nice. What I really like in this article is the sort of  "do it the way that works for you".

 Some say that TDD is good for API design. I do not agree there. If you design an API this is not some unit test that will make a good API. Here a good API will come from experience, and good design applied before testing and coding. This will also come from lot of refactoring till the API sound good. (And so implies lot of code and tests rewrite too).

Some say that TDD is good for debugging. I do not agree neither. On a complex codebase, reading the code carefully, reproducing the bug with a debugger will make you understand what happens. Then making a small code change (likely only a few line of code, maybe just one, at the right place) will solve the bug. You check not by writing a unit test that may fail to reproduce, but by trying to reproduce again. After that you may want to make a test for the bug. You can make the unit test before, but this is not a proof. What you want in reallity is to solve the bug, not make the test pass. This one is just a technical artifact.

Last, like written in the article it is difficult to write test before the code being tested. Don't get me wrong. In theory you can build a black box test that is independant of the implementation and that work anyway. In pratice through, for dependancies, edges cases and exceptions, we do perform white box tests. Test that are dependant of the specific implementation of the code. Otherwise we would not be interrested by code coverage. And that kind of tests you need to know exactly what the code do (or will do) to make them.

That's mean that TDD with test first is like writting tests before, but having written the code in your head before, at least for me. And between writting the test in my head or writing down to the computer, I prefer the last

Last testing is costly. You can make a small static method of 3 or 4 line that work just great and use another static or a singleton. 3 line of code. Then you want to make it testable, you have to extract dependancies, be able to mock it and introduce interface and IOC everywhere. 3 line of code become 2 or 3 interface and a configuration file. You just made you code more complex and more costly to maintain. yes you likely found some bug, but you'll have found them anyway with manual tests... And even if you perform automated test, you still need manual tests.

That's why after many throughts I tend to see unit test as a waste of time except maybe for an helper to write complex code in isolation. Other than that I would prefer a suite of automated integration test that run the full application and check that the real application work as intended.

Robert Lauer replied on Mon, 2011/06/27 - 6:48am

If you know what you're building, there's nothing wrong with writing a test for a component and adding collaborators where needed. The added benefit is that you can precisely specify what the expected behavior of the collaborator is, and when you realize that it should have more responsibilities you can extend your test code.

If you don't exactly know what you're building (80% of all cases): Try something first (don't even need tests for that). Don't hesitate to go back.

If you want to deconstruct behavior (i.e. get rid of a feature), do that test-last: I.e. remove the feature, watch the appropriate test fail, then remove the test.

If you have crappy legacy interfaces, don't try to directly connect them to your code-under-test (you'll go crazy doing that). Instead, write appropriate wrappers and tests against those.

My 2 cents.

Walter Simba replied on Mon, 2011/06/27 - 7:14am

"If you insists on writing the test first, you’ll have to think very hard what collaborators the BookService object will use"? What happed to mocking and dependency injection? I smell something in this rationale.

 

Otherwise, it's a well written piece.

Nicolas Bousquet replied on Mon, 2011/06/27 - 4:32pm

"What happed to mocking and dependency injection?"

 You'll need advenced patterns and concept to perform the simpliest thing just because of fancy testing. Thus increasing complexity and maintenance cost for your software.

Claus Luethje replied on Tue, 2011/06/28 - 7:19am in response to: Mladen Girazovski

+1

TDD leads to better architecture and higher maintainability. I observe this a lot in our projects, where different developers work on each others code over years with months of inactivity between releases. This couldn't be handled with a bunch of uninspired unit tests, which just check input validity.

TDD helps every developer to understand and build clean "modules".

 

Lars replied on Wed, 2011/06/29 - 7:26am in response to: Loren Kratzke

Interfaces are contracts, tests are more like lawyers.

That's a great quote! But then I would argue that you need lawyers to write contracts ;-)

Interfaces are abstract, tests are concrete examples. This makes tests much easier to grasp. I think most people have some concrete examples in mind when they design interfaces or implement anything. TDD makes those examples explicit.

Mikael Couzic replied on Wed, 2011/06/29 - 5:39pm in response to: Nicolas Bousquet

Did you just call mocking and dependency injection "advenced patterns and concept", that "increase complexity and maintenance cost" ?
I consider them part of the basic skills of a developer, and certainly don't share your point of view.
Of course DI takes a little time to learn, but once you understand it, it greatly improves code readability and overall comprehension of the design, and I'm not even talking about testing here.

Learning TDD is a real challenge, I'm not halfway there yet but I already feel with absolute confidence that it will make me a better developer. I have also been reading the book "Growing Object-Oriented Software, Guided by Tests", and it's really enlightning.

By the way, I've just stumbled upon the Transformation-Priority-Premise on Uncle Bob's blog :
http://cleancoder.posterous.com/the-transformation-priority-premise
I really feel this is gonna help me !

Tomas Ramirez replied on Sat, 2011/07/02 - 11:50am

Nice article. Reminds me of one of master Ueshiba's teachings. Also, I see how TDD can help with the API, but perhaps it's better to say that it helps you develop the API. We recently designed a new API, and through TDD discovered a lot of places where it needed to be better defined, and places where it had logical errors.

Tero Kadenius replied on Sun, 2011/07/24 - 11:00am

Test after is better than no test. However, it is not test driven anymore. Ie. nothing to do with TDD.

TDD doesn't solve every single problem and there are cases where it may not be the best approach. This often seems to provoke "Told you so. It doesn't work." -responses. In my personal experience, I've found that developers who dislike TDD tend to base their opinion on false assumptions. They either haven't learned the practice sufficiently in order to reap the benefits or they haven't overcome their bias against it in the first place

Sirikant Noori replied on Sun, 2012/01/15 - 12:29pm

In your first point I think you’re missing the point entirely.

You do not write a test for the whole borrow method, you write a test for one thing it does, then you make that test pass, then you refactor. Then you write a test for the next bit.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.