The Importance of Test-Driven Development
March 6, 2019
Why Should I Test Before I Write My Code?
For years, the software development life cycle [SDLC] has followed a primary model: Plan -> Build -> Test -> Deploy -> Maintain. This model was built on a logical thought process of “Build something to the spec, test it from every angle and deploy that fully tested code to production.” I’d like to pull apart this sentence and explain where the fallacies lie.
“Build something to spec”
In some previous roles, I remember both reading and writing the holy scripture that was the Business Requirements Document (BRD) or specifications for features and applications. What’s interesting here are the heuristics that are implied in the documents themselves. There are conversations distilled through interpretation chains of many people. I would harken back to the childhood game of “Telephone” by which the last iteration of the oral telling of a statement is vastly different from the original. That is an extreme comparison, but just think about the dilution of the message over many translations. How can we create a more granular and contiguous communication of the features purpose from customer to business to product to engineering?
“Test it from every angle”
Heavy is the crown of the Quality Assurance [QA] engineer, tasked with creating endless scenarios around perfection. “Every angle” is the key phrase. Prevention of bugs equals success. The underlying and troubling assumption that drives this pressure is that elusive alliterative statement “Production is perfection.” But in reality, production is not perfect, and your best QA force is your user base. They are the ones extending your use case and moving the product into new areas. Also, without a pass from the extensive QA test suite, features will take longer to reach the customers and make an impact on the end users. How can we focus an area like Quality Assurance to ensure the optimal test scenarios based on the features to deliver features to the end users more quickly and safely?
As you can see from the dissection of the two phrases above, the goal is to improve accuracy and quality of software release. I want to be clear that the traditional model of SDLC does work with teams all working efficiently together. You can have phenomenal outcomes when it is working well. However, this is much more dependent on the organization and composition of the teams to achieve this nirvana of software development. Making one small change of building the testing in before development, or before the code is even written, can more normalize this optimization and make it sustainable. This is the power of Test-Driven Development, or TDD.
What is TDD?
To understand Test Driven Development, we should understand the process upon which it is built. At the very core, the test is the conversation between the Product Manager [PM] and the Software Developer about the feature in the actual code. How it gets there first starts at the story. A normal story in our application looks like the below example, broken up between a Value Statement and Acceptance Criteria:
As a [Persona]
I want to [do an action]
So I can [get some value]
GIVEN [some context in an Application]
WHEN [some interactive process takes place]
THEN [some outcome is observed]
Let’s put this in a tangible example. Say I am building an app to show an item from a search list for a car buying application, the value statement may read: “As Connie, I want to select a car from my search list so I can know more specifics about the car I want”. While this is a more abstract statement, the Acceptance Criteria gets more at how the feature will work. In our example, the Acceptance Criteria would read: “GIVEN I am on the search results page, WHEN I select one of the search results, THEN I should see a new page with a picture of the car, price, mileage and features.” This is the more concrete description that describes how the feature works. This is an important sidebar to describe how the intention of the feature is passed on to engineering.
At this juncture, TDD starts. It flows from this first test of the feature without writing any code. In the software testing pyramid, this would be an integration test. This means that once the test is written it fails, and any builds are red. While writing the code, the engineers work to complete the test or make it green. This is a process we call Red, Green, Refactor. In more abstract terms, this process boils down to Understand, Execute, Reflect. Throughout this process, engineers are constantly thinking about context in the application, to the user and to the code.
What’s in it for my application?
The outcome of this process is a feature that accurately tests and reflects the features implemented. It assures that the process that the user takes gives the intended outcome. In this model, you can start to see how TDD can influence the story writing process. Stories become thinner and the collection of tests build to organically create a robust test suite centered around intended user behavior and protecting the value delivered to the end user. This means smaller, certified features, and more stable and understandable code base. It creates a more direct communication path from user features and business to implementation. It also creates a naturally comprehensive test suite of features to certify intended functionality.
More maintainability, please!
One other benefit to this process of developing software is how it relates to maintainability. When it comes to reworking code, usually the application team will be very apprehensive. Habitually, this comes in the form of the following question: “If I change this part of the code, how will it affect the rest of the application?” Normal reaction to this statement is to again pressure the testers and product managers to certify the app’s most critical workflows. This fear begins the long cycle of regression testing, all based on an uncertainty of the codebase. TDD enables and empowers you to fearlessly make changes to the code when parts need to be updated or changed, because good test coverage will protect you from inadvertently introducing breaking changes. If the code you are changing is used somewhere unexpectedly, the test around that invocation should fail. Now you know more about where that change is used and can fix that part of the code before deploying. Think of it as all of your regression testing built in from day one.
Working at CoreLogic, using TDD in our software development process has decreased overall unexpected bugs in our applications. We practice Agile XP or Extreme Programming, where TDD is an important part of all aspects of our software development; from planning to design to implementation. It has helped to create an environment where we can deliver software quickly with high quality. Starting with TDD does involve some commitment and has some challenges to overcome; it involves the entire application team working together.
- Better test coverage leads to better overall code quality
- Tests are embedded in the build process, reducing the time it takes to promote a new feature to production
- Definition of done for a feature means tests cover intended functionality
- Functionality is validated based on user need, and not on implementation
- Further refactoring is safe because a changed line of code leads to broken test
- Test Driving is a significant paradigm shift from the traditional Software Development Lifecycle
- It takes more time to complete features
By Jeffrey Taylor, Senior Leader, Software Engineering, IDC Program Funding