Test Driven Development
TDD is an evolutionary approach released in the 2000s which combines test-first development where you write a test before you write just enough production code to fulfill that test and refactoring.
Kent Beck has written a book "Test Driven Development: By Example"1, in which he follows two TDD projects from start to finish, illustrating techniques programmers can use to increase the quality of their work. The examples are followed by references to the featured TDD patterns and refactorings. This book emphasises on agile methods and fast development strategies.
Another interesting book written by David Astels "Test-Driven Development: A Practical Guide"2, it presents TDD from the perspective of the working programmer: real projects, real challenges, real solutions, ...real code. Dave Astels explains TDD through a start-to-finish project written in Java and using JUnit. He introduces powerful TDD tools and techniques; shows how to utilize refactoring, mock objects, and "programming by intention"; even introduces TDD frameworks for C++, C#/.NET, Python, VB6, Ruby, and Smalltalk. Invaluable for anyone who wants to write better code... and have more fun doing it!
What is the primary goal of TDD ?
One view is the goal of TDD is specification and not validation. In other words, it’s one way to think through your requirements or design before your write your functional code (implying that TDD is both an important agile requirements3 and agile design4 technique).
Another view is that TDD is a programming technique. As Ron Jeffries likes to say, the goal of TDD is to write clean code that works5.
What is TDD ?
The steps of test first development (TFD) are overviewed in the UML activity diagram. The first step is to quickly add a test, basically just enough code to fail. Next you run your tests, often the complete test suite although for sake of speed you may decide to run only a subset, to ensure that the new test does in fact fail. You then update your functional code to make it pass the new tests. The fourth step is to run your tests again. If they fail you need to update your functional code and retest. Once the tests pass the next step is to start over (you may first need to refactor any duplication out of your design as needed, turning TFD into TDD).
TDD = TFD + Refactoring
TDD completely turns traditional development around. When you first go to implement a new feature, the first question that you ask is whether the existing design is the best design possible that enables you to implement that functionality. If so, you proceed via a TFD approach. If not, you refactor it locally to change the portion of the design affected by the new feature, enabling you to add that feature as easy as possible. As a result you will always be improving the quality of your design, thereby making it easier to work with in the future.
Instead of writing functional code first and then your testing code as an afterthought, if you write it at all, you instead write your test code before your functional code. Furthermore, you do so in very small steps – one test and a small bit of corresponding functional code at a time. A programmer taking a TDD approach refuses to write a new function until there is first a test that fails because that function isn’t present. In fact, they refuse to add even a single line of code until a test exists for it. Once the test is in place they then do the work required to ensure that the test suite now passes (your new code may break several existing tests as well as the new one). This sounds simple in principle, but when you are first learning to take a TDD approach it proves require great discipline because it is easy to “slip” and write functional code without first writing a new test. One of the advantages of pair programming is that your pair helps you to stay on track.
The two TDD levels
|Acceptance TDD (ATDD): With ATDD you write a single acceptance test, or behavioral specification depending on your preferred terminology, and then just enough production functionality/code to fulfill that test. The goal of ATDD is to specify detailed, executable requirements for your solution on a just in time (JIT) basis. ATDD is also called Behavior Driven Development (BDD).||Developer TDD: With developer TDD you write a single developer test, sometimes inaccurately referred to as a unit test, and then just enough production code to fulfill that test. The goal of developer TDD is to specify a detailed, executable design for your solution on a JIT basis. Developer TDD is often simply called TDD.|
This diagram shows us how ATDD and developer TDD fit together. Ideally, you'll write a single acceptance test, then to implement the production code required to fulfill that test you'll take a developer TDD approach. This in turn requires you to iterate several times through the write a test, write production code, get it working cycle at the developer TDD level. This diagram assumes that you're doing both, although it is possible to do either one without the other. In fact, some teams will do developer TDD without doing ATDD, although if you're doing ATDD then it's pretty much certain you're also doing developer TDD. The challenge is that both forms of TDD require practitioners to have technical testing skills, skills that many requirement professionals often don't have (yet another reason why generalizing specialists are preferable to specialists).
TDD with testing framework
An underlying assumption of TDD is that you have a testing framework available to you. For acceptance TDD people will use tools such as Fitnesse6 or RSpec7 and for developer TDD agile software developers often use the xUnit family of open source tools, such as JUnit8 or VBUnit, although commercial tools are also viable options. Without such tools TDD is virtually impossible.
Kent Beck, who popularized TDD in eXtreme Programming 9, defines two simple rules for TDD 10. First, you should write new business code only when an automated test has failed. Second, you should eliminate any duplication that you find. Beck explains how these two simple rules generate complex individual and group behavior:
- You develop organically, with the running code providing feedback between decisions.
- You write your own tests because you can't wait 20 times per day for someone else to write them for you.
- Your development environment must provide rapid response to small changes (e.g you need a fast compiler and regression test suite).
- Your designs must consist of highly cohesive, loosely coupled components (e.g. your design is highly normalized) to make testing easier (this also makes evolution and maintenance of your system easier too).
For developers, the implication is that they need to learn how to write effective unit tests. Beck’s experience is that good unit tests:
- Run fast (they have short setups, run times, and break downs).
- Run in isolation (you should be able to reorder them).
- Use data that makes them easy to read and to understand.
- Use real data (e.g. copies of production data) when they need to.
- Represent one step towards your overall goal.
Test Driven Development (TDD) Vs. Agile Model Driven Development (AMDD)
|TDD shortens the programming feedback loop||AMDD shortens modeling feedback loop.|
|TDD is detailed specification||AMDD works for bigger issues|
|TDD promotes development of high-quality code||AMDD promotes high quality communication with stakeholders and developers.|
|TDD speaks to programmers||AMDD talks to business analyst, stakeholders, and data professionals.|
|TDD non-visually oriented||AMDD visually oriented|
|TDD has limited scope to software works||AMDD has a broad scope including stakeholders. It involves working towards common understanding|
|Both supports evolutionary development||--------------------------------------------|
Benefits of TDD
- Early bug notification: Developers tests their code but in the database world, this often consists of manual tests or one-off scripts. Using TDD you build up, over time, a suite of automated tests that you and any other developer can rerun at will
- Better Designed, cleaner and more extensible code:
- It helps to understand how the code will be used and how it interacts with other modules.
- It results in better design decision and more maintainable code.
- TDD allows writing smaller code having single responsibility rather than monolithic procedures with multiple responsibilities. This makes the code simpler to understand.
- TDD also forces to write only production code to pass tests based on user requirements.
- Confidence to Refactor:
- If you refactor code, there can be possibilities of breaks in the code. So having a set of automated tests you can fix those breaks before release. Proper warning will be given if breaks found when automated tests are used.
- Using TDD, should results in faster, more extensible code with fewer bugs that can be updated with minimal risks.
- Good for teamwork: In the absence of any team member, other team member can easily pick up and work on the code. It also aids knowledge sharing, thereby making the team more effective overall.
- Good for Developers: Though developers have to spend more time in writing TDD test cases, it takes a lot less time for debugging and developing new features. You will write cleaner, less complicated code.
1. “Test Driven Development: By Example”, Kent Beck, https://www.amazon.fr/Test-Driven-Development-Kent-Beck/dp/0321146530 ↩
2. “Test-Driven Development: A Practical Guide”, David Astels, https://www.amazon.fr/Test-Driven-Development-Practical-Guide/dp/0131016490 ↩
3. “Agile Requirements Modeling”, www.agilemodeling.com, http://www.agilemodeling.com/essays/agileRequirements.htm ↩
4. “Agile Design”, www.agilemodeling.com, http://www.agilemodeling.com/essays/agileDesign.htm ↩
5. “TDD on the Diamond Problem”, Ron Jeffries, https://ronjeffries.com/articles/tdd-diamond ↩
6. “The fully integrated standalone wiki and acceptance testing framework”, Fitnesse, http://fitnesse.org ↩
7. “Behaviour-Driven Development tool for Ruby programmers”, RSpec, https://relishapp.com/rspec ↩
8. “The programmer-friendly testing framework for Java 8”, JUnit5, http://junit.org/junit5 ↩
9. “Extreme Programming Explained: Embrace Change”, Kent Beck, https://www.amazon.com/exec/obidos/ASIN/0201616416/ambysoftinc ↩
10. “Test Driven Development: By Example”, Kent Beck, https://www.amazon.com/exec/obidos/ASIN/0321146530/ambysoftinc ↩