Earlier this year, I participated in a meeting to recommend testing best practices. Being fairly passionate about TDD, and working in an environment where unit testing is fundamentally an afterthought, I assumed the group would certainly recommended TDD as a best practice. Amazingly, TDD failed to get the official endorsement. Many of the committee members stated that TDD was too hard and required too much up front work. Too hard? Really? I was very surprised by their rational, especially since many of them were seasoned developers. The image to the right has nothing to do with software development, but I love the message! I thought there was a rather subtle correlation to TDD: Thinking first can prevent problems. Think First is probably a little misleading, surely, everyone claims to think before embarking on a task. For me, the real value behind TDD is the thought process; I like to connect thinking with the design process, not the coding process
I had the opportunity to participate in a SD Times webinar on Wednesday called “Leaders of Agile: Practical Agile with Test-Driven Development” by Kent Beck and Mark Seemann. I took some notes on points I thought were relevant to truly adopting TDD.
Concept | Observation |
Write a little test, write a little code, iterate. – Don’t spend hours writing code – TDD tells you where you stand |
I have worked with a lot of developers that literally spent hours writing code without ever executing it. Once they feel like they are done coding, they start to think about testing. Many times, this testing is done in the same manner as their development, which I will describe as “without a plan”. Some even avoid structured, assertion-based testing frameworks, they simply spin up the container and execute the code. This approach will certainly work, but would you really say this is the most effective approach? You end up with a huge pile of un-validated, un-executed code. Unless you are perfect and never make mistakes, you have no clue how many bugs are buried in the code. Even worse, if you discover a design problem or a more elegant approach, how much code will you have to change or toss away? This is not to say that refactoring or eliminating code is bad. The point is, this effort could have been minimized or even prevented by thinking first. Another benefit of starting from the test perspective, is you always know where you stand in the process; the test works or it does not. If the test does not work, you are probably not done. |
TDD allows you to defer design decisions – Refactoring and Retrofitting – Working test first, Design as you go |
If you think about TDD from an iterative perspective, you are basically working from the outside in. You can almost compare it to the Object Oriented principle of encapsulation, hiding or postponing design decisions. Unit test should be focused on the behavior of the class, not the actual implementation. Because the unit test is focused on behavior, you have the opportunity to defer some of the design/implementation decisions to a later iteration. This later iteration might only be a couple of hours later, but, by thinking first about the desired behavior and interaction, you are not required to commit to an actual design or implementation. Obviously refactoring can be a big part of TDD and is hopefully not considered a deterrent. Refactoring does have a cost; I believe this cost is justified and minimized by the confidence provided by a good unit test suite and the long term supportability of a elegant design. |
TDD = API Design | For me, TDD is more about the design process than the actual unit test. TDD forces me to concentrate on how the class will be utilized, enabling me to focus on the requirements of the class, implementing no more functionality than is actually necessary. Implementing the class first, you are basically guessing at the API, only imagining how the class could be used. Adding and subtracting methods as you go, based on how you feel the class might be invoked. I have to believe that this approach will generally not result in the cleanest API. Focusing on the external interaction and dependencies provides a simpler and more exact view of the actual implementation requirements. |
Tests are First Class Citizens. | This one is very near and dear to my heart. I can’t tell you how many times developers have pushed back on the enforcement of coding standards and quality as it pertains to the unit test suite. Why would anyone want to allow or encourage bad coding practices in the unit test code, that are not allowed in the real code? Do developers have a quality switch that can be turned on and off? Writing good, clean code over here, and sloppy code over there? It should be all about consistency and supportability. Unit tests need to be as understandable and maintainable as the mainline code; they will be maintained as long as the mainline code and over time, could provide more value (comprehension) than the mainline code. |
Testable = Loosely Coupled | Testability is another topic I find very interesting. I just love the Testability Explorer tool, but was never able to get others excited about this concept. If not designed effectively, unit tests have the potential to become a roadblock of change. Unfortunately, the same point can be made about the actual code; there are certain designs and implementation that can impede future change. The goal is to use patterns and tools that support change, such as dependency injection. Some people think that TDD can encourage coding without thinking; I have the complete opposite view, thinking first, to establish the design. It is my opinion that TDD is just one small tool that helps flush out designs; doing so without significant overhead and simultaneously building a safety net. |
I was not sure how these “virtual conferences” actually worked, but this one was very well organized and seemed to go off perfectly. I was very impressed with both of the speakers and recommend subscribing to their blogs. They each made numerous points, but I wanted to share a point from each of them that I found encouraging. I will probably paraphrase them poorly, but hopefully you get the point!
Speaker | Thoughts… |
Kent Beck – “Everything is hard. You need to be eager and willing to learn.” | I thought it was refreshing to hear the words, “eager and willing”. Unfortunately, we are not always eager and willing! You can’t force anyone to learn or change. There has to be a willingness; we need to be open-minded to absorb and process new thoughts. The benefits are not always immediate and may cause some pain, but in the end, learning is what makes us better. |
Mark Seemann – “I tend to do a lot of thinking.” | I thought this was great! I never hear people say they do a lot of thinking. Many of us tend to jump into the fire, probably because we are not given sufficient time to evaluate at the situation. My challenge to you, demand the time, take the time. Think First! |
March 4th, 2011 8:08 am
[…] testing is fundamentally an afterthought, I assumed the group would certainly recommended TDD… [full post] Phil Soapbox Rants and Raves software developmenttesting 0 0 0 […]
March 7th, 2011 8:46 am
I’ve been giving TDD a try lately after reading a book. I am implementing the command pattern for part of the program, and I found it relatively straightforward to practice TDD. However the other part of the program involved implementing a huge API specification written by a standards body. I found myself constantly making major design changes as I would implement more of the specification and build a deeper understanding of it. It was thousands of pages long so you can’t expect me to absorb it all at once. If I would have followed TDD for this, I think I would have been constantly throwing out a lot of tests. That felt like a waste so I finished the code before beginning the tests. This also felt wrong to me. I want to be a TDD expert. I suspect part of my problem is that testing the API specification is not simple. The implementation for a seemingly simple method call might involve creating several large sophisticated classes to handle the prescribed behavior, often asynchronous and in a separate thread. Maybe after I write the unit tests for this program I will feel more confident about testing this kind of code and I will find TDD on any kind of code possible.