Designing for Testability

EDIT:  What I should really say is that it isnt’ just Designing for Testability, it’s Designing with Testability 


From a question on my Passive View blog post


 “should we design for testability, or should we try and test what’s designed (perhaps designed badly, so we refactor later)?”


Here’s my take:


Done, done, done” isn’t just writing code.  It’s writing code and verifying that that code works correctly.  You don’t ship until the code is proven to work (hopefully).  Designing for testability might cost you extra time in coding (which I would actually dispute somewhat), but can easily save time in the whole by cutting down on the time spent debugging and testing.  I see testability design as a way to optimize the time to deliver, even if it ups the time spent on design or coding.


One of the very painful truths that TDD newbies learn the hard way (myself included) is that retrofitting automated tests to existing code can be very difficult.  Just trying to test what’s designed may not work out very well, and frankly, I have yet to see a codebase that wasn’t built with TDD that was easy to test.


What is Testability design anyway?  Granted, there are some things I do like opening up more public accessors or pulling out more interfaces strictly for testing that could arguably described as “bad.”  However, most of what constitutes designing for testability, or using testability as a design heuristic, is a matter of how best to assign responsibilities by following older design principles that predate TDD by many years.  Achieving testability is mostly a matter of separation of concerns, coupling between classes and subsystems, and cohesion.  Exactly the design qualities that we’ve always strived for to make our code maintainable.  It’s what we’ve been trying to do anyway.  If you practice traditionally good design, you might already be there to testability. 


Testability is, in my opinion, the ultimate design smell detector at the granular level.  If you’re finding it hard to unit test your code, you’ve likely got a coupling or cohesion problem.  Testability is yet one more design tool to stick in your design toolbox right between UML/CRC modeling and code smells.  At least think of it this way, driving an application design at least partially through tests is yet another example of starting with the end in mind.  How will I know that this code works correctly?  How will I know that I’m done?


Yes, using TypeMock or switching to a dynamic typed language will let you more readily create testing seams with less conscious effort, but that’s not the entire ballgame.  Throw testability and orthogonality out the window to write a ball of mud and no amount of TypeMock magic is going to help you out.


 


Anyway, go ahead and start arguing with me.  As always, comments are open.

About Jeremy Miller

Jeremy is the Chief Software Architect at Dovetail Software, the coolest ISV in Austin. Jeremy began his IT career writing "Shadow IT" applications to automate his engineering documentation, then wandered into software development because it looked like more fun. Jeremy is the author of the open source StructureMap tool for Dependency Injection with .Net, StoryTeller for supercharged acceptance testing in .Net, and one of the principal developers behind FubuMVC. Jeremy's thoughts on all things software can be found at The Shade Tree Developer at http://codebetter.com/jeremymiller.
This entry was posted in Test Driven Development. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://codebetter.com/blogs/jeremy.miller Jeremy D. Miller

    @ulu,

    I’ll try to shoot you an answer in the morning

    Jeremy

  • ulu

    Hi Jeremy,

    I need an advice on TDD, and I thought I could ask my question here.

    Suppose I want to implement a ComplexOperation method of my Controller class. Being converted to TDD recently, I break this into three smaller steps, and then write a test for it, mocking the calls corresponding to these steps. I end up with a test containing just 3 expectations and no asserts. Now, I write my ComplexOperation method with calls to these 3 steps, I’ve got a green, and I’m happy.

    A couple of things look wrong here. First, my test doesn’t help me to find mistakes with my code. If I forget a step, it will show both in my test and in my code. So, the test is not really testing anything. Second, if I choose to change my design, I’ll have to change the test as well. There’s no “refactor” step in the test-code-refactor trio.

    The problem is that my test duplicates my code. It doesn’t test the result, it tests the process. I don’t see any way to include any assertions in this test, since it would involve implementation of my three steps. The question is, do I really need a test here? Or should I change my design?

    Thanks a lot for your advice.

  • ulu

    @Jeremy,

    No, I won’t argue with you. I completely agree.

    Perhaps I didn’t make myself clear. I didn’t mean testing the existing code. By “testing what’s designed” I meant “first design, diagrams and stuff, then write tests, then write code”. I was thinking that we shouldn’t design just for the sake of testability. But when testability is used as a criterion for a good design, yes, I’m all for it.

    However, I’d rather treat testability as an advice, not as an absolute rule, and I don’t like being forced by testability, even when it enforces something good ;)

    Frankly, you got me converted to the view-presenter separation idea, I’m coding my first presenter now (or is it a supervising controller? i’m still learning these terms), and I feel much better about it. Still avoid these interfaces and dependency injections, my tools let me test everything without them, and I don’t see any god they could make me in my case. Perhaps on the next project..

  • http://gil-zilberfeld.blogspot.com Gil Zilberfeld

    Right on!

    I was actually discussing this with a colleague yesterday. She’s starting with automated testing (not even TDD), and she’s trying to put tests around the existing code.

    It is hard. We need to unlearn the “produce code cause it’s faster” for the things we learned in the university.

    Sorry, but you won’t get arguments from me.

  • Colin Kershaw

    “I have yet to see a codebase that wasn’t built with TDD that was easy to test.”
    +1

    My experience has been that mocking frameworks are often used to test “untestable” designs – treating symptoms rather than the cause (tightly coupled, loosely cohesive code).

    Another great post – keep ‘em coming.

  • Chuggle

    I agree completely with you – every since I started using TDD Ive found my designs have lower coupling and higher cohesion

    Gotta love TypeMock….it should come with a warning though :)