Do you automatically get better design with TDD? Does an otherwise average software developer produce superior designs if they write the tests first rather than afterwards? Does it make a difference what style of TDD you use?

incident #1

I was at a session at XP2012 with J.B. Rainsberger called “Architecture without Trying”. He demonstrated how he could develop a software system for Point-of-Sale terminals using TDD, and how the design naturally tended towards an MVC pattern as he did it. He claimed that purely by doing TDD, and focussing on two things, (removing duplication and improving names) that a good design would naturally emerge.

incident #2

I heard a talk by Luca Minudel at Agile Testing Days 2011 called “TDD with Mock Objects: Design Principles and Emergent Properties”. He was talking about a study he had done where he got people with varying levels of experience at TDD to do four short exercises. He also got them to answer a questionnaire about their knowledge of SOLID principles, and TDD. He then evaluated how well the designs they came up with in the exercises adhered to SOLID principles, and tried to correlate that with their TDD skill. He found that the people skilled in TDD did better in the exercises than those who only knew the theory of SOLID principles. The practice of TDD seems to help people with design. Luca also found that those more experienced with the London School of TDD did even better than other TDDers.

incident #3

I was working at a client recently when I met a developer from a different department. He came to see me several times over a period of a couple of weeks, and asked for advice about TDD. On about his fourth visit he told me he had written some code and now it was basically working, he wanted to write tests for it. He said he was having difficulty since he’d written a lot of static “helper” methods. I advised him that static methods make code quite hard to test, and can often be a sign of a not very good object oriented design.

He suggested we should invest in a fancy mocking tool that would enable him to easily replace these static methods in the tests. I told him a better investment would be for him to learn to write the tests first, get better at OO design, and not use static methods in the first place. I was probably a bit blunt, and he was quite polite, all things considered. He protested that he shouldn’t have to change the production code in order to get it under test, then left. That was the last time he came to me for advice.

Discussion

So does doing TDD guarantee better design? Well it should certainly help. I’ve presented before about the way TDD gives you early feedback on your design and plenty of opportunities to refactor. It’s less help though if you don’t know what a good design looks like in the first place. I think J.B. goes too far in his claims – if you don’t know MVC or SOLID principles then I’d be surprised if they started turning up in your code with any consistency.

No tool nor technique can survive inadequately trained developers”ย 

(A quote attributed to Steve Freeman). I think you do need to invest in learning good design techniques independently of TDD. If you lack basic OO design skills you probably won’t be able to do TDD in the first place, London School or otherwise.

I’ve been learning and improving my practice of TDD, including the London School, for many years now, and I was intrigued by Luca’s claims that it led to better adherence to SOLID principles than classic TDD. The London School involves an outside-in approach to design, that makes heavy use of mocks to check interactions between objects. This is in contrast to a more classic TDD style that prefers to verify the code works by checking the state of an object after an interaction.ย  I wouldn’t claim to be an expert in the London School of TDD, but I think I understand the basics and can adopt this style when I feel the problem is appropriate for it.

I tried out Luca’s four problems, (here on github) to see how I did. Luca very kindly gave me some feedback on my code, and I found hadn’t done as well as I had hoped to in adhering to SOLID principles. I’d got the code under test, but in a few places I could have improved the design more. I also slightly misunderstood the requirements for two of the problems, which led me to fork the repo and improve the instructions ๐Ÿ™‚

I think in the cases where I could have done better with the design, it’s possible using the London School of TDD would have led to the improvements. I’m feeling there might be something in Luca’s conjecture. On the other hand, these problems might be so small and abstract, that I didn’t behave the same as I would in a real codebase. Certainly in one case I felt it wasn’t worth extracting an interface when there was only one implementation for it. In a real system maybe it would be more obvious that more implementations were likely, and that adding the interface would lead to a more decoupled design. Or then again maybe I’m just too used to python where expicit interface classes don’t tend to be used. Or maybe I’m just making excuses! In any case, doing these exercises has made me more interested to improve my knowledge and practice of the London School TDD style.ย 

I think these exercises are interesting little code katas in their own right, quite apart from Luca’s study on TDD. I think you can use them to learn about the SOLID principles, and practice some of the refactorings you often have to do to get badly designed code under test.

I’m working on a python translation of the exercises so we can try them out at the Gothenburg Python User Group meeting next week. Feel free to fork the repo and have a go at them yourself.

21 Comments

  1. J. B. Rainsberger says:

    “It’s less help though if you don’t know what a good design looks like in the first place.” We can’t agree on what good design looks like — several discussion threads on various mailing lists have shown that. I think it’s impossible for someone to refactor code with absolutely no idea whether the result is good or not, because in order to want to refactor code, the programmer would generally need some concept of good and poor design. I have both witnessed and experienced incidents of refactoring, having a certain unexpected structure emerge, then realising what’s good about it without having seen that pattern before. In particular, this led me to question my previous ideas about data hiding/encapsulation.

    I’m not sure which claims I go too far in making, but I try to teach that TDD can help the programmer deepen understanding of the principles you’ve cited, which assumes some prior knowledge of them. I believe that TDD can help the programmer uncover these principles, which assumes access to someone who can say, “Why yes, that’s DIP you’ve discovered. Let me tell you about it.” I’ve seen both numerous times.

  2. Emily Bache says:

    J.B. – In what was, admittedly a rather short session at XP2012, I felt you were making some rather bold claims. Your comment here is substantially more nuanced ๐Ÿ™‚

    I think we both agree that doing TDD can give you design insights, and that learning design principles will help you to do TDD, particularly the refactoring part. It certainly helps to get feedback on your code (teaching!) from someone more experienced.

    I’m skeptical that there is some kind of shortcut where you can produce a good design by only knowing and applying a couple of simple rules. Good design is hard.

  3. Wojtek says:

    This comment has been removed by the author.

  4. Wojtek says:

    Great post.

    I had similar experiences.

    When I learned about SOLID I wanted to do it. It was very hard in the beginning. I ended up following simple TDD rules to come up with code that was more likely to be compliant with SOLID rules. Then I saw some patterns emerging. Now I am more often sure in cretin situations how to write SOLID code, TDD taught me that.

    http://test-driven-development.com/2012/04/09/tdd-is-like-cooking-hehe/

  5. Luca says:

    I take this opportunity to ask you a pair of questions here and see what’s your opinion based on your experience.

    Suppose that we all 3 agree that SOLID are good design principles.
    And then we talk with real teams in organizations, we look at the team members that better know about SOLID, we talk with them and we discover that many of them have a good abstract understanding of the principles. When we show them real lines of code and we ask them to spot principle’s violation (and so possible improvements) we discover that many of them spot very few violations of the principles and don’t see even many of basic/major violations.

    Question 1) In a context like this don’t you think that even few of the basic practices required to make the code GOOS testable like
    – passing collaborators and dependencies via the constructor instead of internal instantiation
    – extracting and reference interfaces of dependencies & collaborators instead of concrete types
    – breaking down big classes with too many responsibility;
    can produce a visible and tangible improvement of the design?

    Question 2) given that writing unit tests is another way to understand and reuse in isolation small parts of the code (that is the same goal of good OO design) don’t you think that the effort of writing good test & testable code itself points to the direction of finding/discovering what good design is for the code one is testing ?

  6. Wojtek says:

    Luca: I think you answered your questions already.

    Ad. 1: There is that. There is also a lot more that can be done. Following SOLID, etc. will pay in future. Just remember about making informed decisions.

    Ad. 2: Yes. You can reach a point where you don’t need to drive your design with unit tests. But it takes time ๐Ÿ™‚ And if you work in the team you are not on your own, there is always somebody that is less competent ๐Ÿ™‚

  7. Emily Bache says:

    Wojtek – thanks for the link to your post. I’m glad I’m not the only one who’s noticed coding is like cooking ๐Ÿ™‚

  8. Emily Bache says:

    Luca – about your questions

    1) That practicing GOOS TDD leads to a SOLID design seems like a reasonable working hypothesis.

    2) testability and good design are closely related, so if the code has unit tests it will naturally have some desirable design characteristics. I don’t think TDD is the only way to reach a good design though.

  9. tab says:

    Quite valuable thouhts outlined in depth (as we know it from you ๐Ÿ˜‰ Thank you Emily!

    When you write

    He protested that he shouldn’t have to change the production code in order to get it under test, then left. That was the last time he came to me for advice.

    I was reminded of Jurgen Appelo’s post “The Trojan Form of Change” this week:
    http://www.noop.nl/2012/09/the-trojan-form-of-change.html
    Unfortunately too often we don’t have the time or capacity to help sb. going into deep. But the message is clear and this should be our everytime goal:

    inject an idea virus in a traditional organization

    Although this advice is valuable it is only half of the story: it talks about the WHAT not about the HOW.
    Yet, may we all keep going!

  10. J. B. Rainsberger says:

    Emily: Odd, since I’ve been practising and delivering those claims for years, that I wouldn’t have delivered them the same way at XP2012. I don’t remember enough to be able to say one way or another. I believe that one can reduce the SOLID principles (among others) to some linear combination of removing duplication and improving names, which explains why I focus on those two techniques.

    Your skepticism, I conjecture wildly, assumes that it’s possible to remove duplication and improve names diligently and not notice what’s happening. I think you’d have to work hard to avoid doing that, and much like the kid who decides to cheat by writing down all the answers to the test on her shoe, some of the right answers sneak into her memory.

    It usually goes like this: someone tries to explain SRP to you, and you just don’t get it, or you get a very superficial understanding of it. You focus on removing duplication and improving names. Without actually /trying/ to understand SRP better, you will. It’s the “Karate Kid” effect: when you practise the moves, even though you don’t understand their larger purpose, you build the ability to see the larger purpose. Assuming you try to notice what’s happening, the connection to SRP will happen.

    I just don’t think the amount of up-front explanation of SOLID is needed. Being extreme, I think we can get away with none. ๐Ÿ™‚

  11. J. B. Rainsberger says:

    Luca: To (1) I answer “yes” and I usually teach removing duplication first, because it encourages people to break down large structures. To (2) I answer “absolutely yes” and I’m surprised I haven’t emphasised that point in my workshops before. Thank you for that.

  12. Roberto Guerra says:

    I agree with Emily that some claims are a little exaggerated. I started doing ‘TDD’ before I knew or understood SOLID. When I look back at those tests, they were just plain wrong. I would try to use some testing tools that would do crazy stuff just so I could test my code. I wasn’t thinking about design nor SOLID. After I read about SOLID(especially after watching Uncle Bob’s screencasts) things snapped in my brain. I started applying those principles in my tests and my code started looking better. It wasn’t the other way around: testing and then SOLID appeared.

    But that is just my personal experience.

  13. Alcides Flores says:

    This comment has been removed by the author.

  14. Alcides Flores says:

    Hello everybody.

    In this topic about TDD & SOLID design principles, I found the arguments of Mark Seemann here to be very interesting/illustrative as he compares TDD against each one of the SOLID principles to see how much TDD really enforces them.

    Also I think the results of the mentioned experiments/incidents are not definitive and far more detail and research in the field is needed.

    Greetings.

  15. Emily Bache says:

    J.B. You’re saying basically that you when you teach TDD you concentrate on these two simple ideas, and that people pick up SOLID along the way. That sounds reasonable. I’m sure you teach TDD very well and naturally teach design at the same time.

    What I react to is the claim that you can get “architecture without trying”. Good design is hard.

    I count myself as reasonably proficient at both removing duplication and naming, and yet I didn’t do all that well on a couple of Luca’s exercises. I don’t think concentrating on only those two practices is sufficient for someone to learn good design.

  16. Emily Bache says:

    Alcides – Thanks for the link to the article. Mark Seemann concludes that TDD doesn’t encourage a SOLID design very much. I think what Luca is arguing is that if you use the London School of TDD it encourages SOLID a lot more than with the standard kind of TDD that Mark is referring to. I think Luca’s arguments have merit.

  17. Luca says:

    @Alcides

    I exchanged some mail with Mark Seemann about the post you linked and the experiences documented by me (at the link to github posted by Emily).

    Here two excerpts from Mark’s mail :
    1)”Iโ€™ve seen many times that teams generally just falter and fail with TDD. Their test code base becomes harder and harder to maintain as time goes by, until a crisis emerges that causes the team to finally ditch the tests.”

    2) “To succeed with TDD you need a critical mass of skilled programmers. That could actually form an interesting hypothesis in itself: Given a team of inadequately skilled programmers, TDD will not yield any improvements, but given a critical mass of adequately skilled programmers, TDD will yield the effects described in your paper.”

    This is congruent with the quote from Steve Freeman in Emily post:
    “No tool nor technique can survive inadequately trained developers”

    Indeed no one is claiming that the practice of TDD can force an inadeguately trained and skilled dev team into producing better design. Nor training on design theory could either.

    Instead the original claim that is congruent also with Mark Seemann point of view is that skilled programmers can improve the design of the code they produce more with adeguate training and practice of TDD then they can just with studing and training of design theory and principles.

    So to be precise, if we rate design quality from 1 to 10, looking among many real dev teams in real organizatons that are better at design the majority of them are at level 3,in this case proper training and practice of TDD can take them from 3 to level 6.

    Now if we name good design as between level 8-10, extra theory and then extra practice is needed to achieve good design. TDD practice is more effective as initial improvement (3->6), while the theory helps better after the practice provided with TDD.

    What is surprising here if you want, is that the dev team that are better in design on average are at level 3. Their ability to discuss and argue about theory of design is only partially reflected in the code they produce.

  18. Dieter Cailliau says:

    Static helper methods hard to test? IMO static methods are the most decoupled code blocks possible. No dependencies at all. It’s static state that should be avoided at all times.

  19. Emily Bache says:

    Dieter – it’s not that static methods are hard to test by themselves. What is difficult is if you have a normal method that you want to test, that uses the static one. It’s hard to change the behaviour of the static method for the test. Take a look at the TurnTicketDispenser problem for example.

  20. Roberto Guerra says:

    I probably did something wrong, but in the TurnTicketDispenser example, I didn’t have to change the static method. In java there would be more restrictions, but I didn’t find that to be the case in Python. I can still initialize the sequence generator and that instance was able to call the static method. Something that you can’t do in Java. That is why I didn’t change its behaviour in the python exercise. Or maybe, as usual, I did something awfully wrong.

  21. Fredrik Wendt says:

    I’m a little late to the game here and I really only have one side note: Yes, dear Emily, having done a couple of sessions of programming with you over the years, I absolutely feel that you’re coding style is heavily influenced from your background of coding Python. ๐Ÿ™‚
    I’ve yet to dig into C# code world, but comparing what I’ve seen in Java to that of Python and Javascript, the way you’re forced to expose what you want to access in Java makes you look away from exposing “state” just to make it testable. This is also one of the reasons GOOS or outside-in (I’ve referred to it as mockist vs stubborn) makes much more sense to a programmer used to a statically typed language with “real” hidden member fields, I think.

    I always stress that the last D in TDD is Development (as in implementing a system), not Design. TDD is not a silver bullet, it’s not some magic you apply to a junior developer and out comes flawless design and error free code.