Drive Speed, Build Confidence & Increase Transparency with Test Driven Development
Test Driven Development (TDD) is a programming best practice that helps software engineering teams continuously deliver with confidence. However, other teams—like product design and product management—and the business as a whole also benefit from TDD. Read on to learn how practicing TDD at your organization will help your product development teams build high-quality software fast that everyone feels good about.
What is TDD?
By definition, Test Driven Development (TDD) is a programming practice that instructs software developers to write new code only if an automated test has failed. It is an iterative, three-step process that involves testing, coding, and refactoring. First, a software developer writes a failing test. Then, they must write the implementation to make the test pass. Finally, they must refactor the codebase when they have a suite of “green” (passing) tests.
At Crafted, we’re big believers in this “strict” TDD definition. That being said, we’re bigger believers in writing automated tests in general, whether they are written before or after the production code. What’s really important is writing the right tests at various levels of granularity.
Unit Testing Unit tests check small pieces of an application (called units) and ensure that isolated code units function as expected. They can help detect early flaws in code, which may be more difficult to find in later testing stages.
Integration Testing Integration tests (such as acceptance tests) verify the interaction of code units with other code units in the application, which comprise the overall software. They can help expose defects in the data communication between software modules, which are usually a result of differing programming logic.
End-to-End (E2E) Testing E2E tests check entire flows through an application using a test environment and data to simulate real-world functionality. The goal is to approach testing from the end-user’s perspective to evaluate whether the results comply with expected outcomes.
Fast feedback loops are a crucial part of TDD. A historical software development flow is to write the code and then launch the program/application and test that it is working properly. This oftentimes requires navigating the application in a particular way to exercise the feature that’s being implemented. With TDD, the software developer will constantly run tests that provide rapid feedback in a matter of seconds rather than minutes. The idea is to keep the feedback loops as tight as possible to enable more efficient development.
A common pitfall we’ve observed over the past decade of helping companies ship products is Quality Assurance (QA) teams writing tests instead of the software development and delivery teams. Engineers are better equipped to write high-quality E2E tests because writing E2E tests should be incorporated into the TDD workflow where they are written before the actual implementation. This allows developers to continuously refactor the E2E codebase as the product evolves. This also allows the QA team to stay focused on doing other value-add testing such as exploratory tests.
Why is TDD important?
TDD is critically important for driving speed, building confidence, and increasing transparency at your organization. The engineering team, other members of the Balanced Team (made up of product design and product management), and the rest of the business benefit from these practices.
Speed
Through TDD, software engineering teams will see an increase in velocity on new features, a decrease in time to fix bugs, and shorter ramp times for new teammates. The Balanced Team can test ideas and iterate product designs faster. And the organization as a whole can realize ROI faster. Sometimes businesses can be hyperfocused on short-term wins that hurt long-term velocity. Testing is a small investment that pays dividends over time. The below graphic from VMWare Tanzu Labs illustrates speed over time:
Bugs get more expensive to fix over time, which makes the curve nearly exponential. “Shift left” is a popular idea in software development right now, especially when it comes to security. The shift left testing movement is about pushing testing toward the early stages of software development to catch critical bugs as soon as possible.
Confidence
TDD helps engineering teams deploy code and continuously deliver value to users with confidence. Refactoring—the process of restructuring code without changing its original functionality—helps engineers meet business needs. These practices also give the Balanced Team and the rest of the business confidence that their product(s) can safely evolve over time to meet new market demands without breaking existing functionality and destroying existing user experiences.
Transparency
Implementing TDD increases transparency across the engineering team because these practices help developers recognize when things are broken. They allow for quick bug detection, no matter who wrote the code. TDD also protects against regressions, which are software bugs that cause features that worked correctly to stop working after a certain event (like system upgrades or other bug fixes). Needless to say, no one likes regressions, especially those in customer-facing roles. TDD also increases transparency across the business because they enable engineers to ship tiny increments of code to visible environments dozens of times a day (or more!). This helps get eyes on the software faster. Lastly, tools like Cucumber increase transparency because they help engineers write test cases in English. This ensures the tests are easily understood by the rest of the Balanced Team and the business but still verify the behavior of the system.
Tips and Tricks to Incrementally Implement TDD
Whether your organization is developing web software but currently not writing any tests, or your engineering team has some familiarity with testing, there’s no time like the present to start incrementally practicing TDD. But don’t be intimated! Below we’ve outlined some ways to get started as well as tools that will make implementing these engineering best practices much easier. All tests outlined below should be run on every commit into a continuous integration system such as Jenkins, CircleCI or Github Actions.
Beginner (No Testing Currently)
Start by writing happy-path tests for the most business-critical parts of an application (purchasing products, logging in, etc.).
E2E tests are a cheap and easy way to cover a lot of ground with few tests. Also, because they are written from the user’s perspective, it’s easy to explain to the rest of the business what the tests are doing. Start by writing a few of these!
Moving down the testing pyramid (see link above), an API can be covered with a few integration tests.
Finally, add some unit tests around the most complicated, fragile pieces of business logic.
Intermediate (Have Written Some Tests)
Audit the tests to see which are providing high value and which are not.
Ask yourself, “Are there any critical code paths that are not covered by the tests?” Code-coverage tools can be used to point these out (Jacoco is a good tool for Java).
Slowly start working on increasing the code coverage in an existing codebase.
All new code should be tested so that any lack-of-coverage problems are not exacerbated by new feature development.
Tools/Testing Frameworks
Cucumber - Integration and E2E testing with Gherkin-style syntax
CI Systems
CircleCI
Jenkins
Unit and Integration Testing
Junit
PyTest
Browser-Based E2E Testing
Selenium
Cypress
Conclusion
Here at Crafted, we believe the strongest software engineering teams practice Test Driven Development. However, it’s not too late for your organization to start implementing some of these best practices. And if you’d like our help, reach out and we’d love to connect! Stay tuned for follow-up blog posts on the value of Continuous Integration and Continuous Deployment, which cannot be achieved without TDD.