Test-driven development (TDD) is a software development approach where tests are written before the actual code. It’s like a game plan where instead of jumping directly into code, you first decide what you want to achieve and how you will achieve it.
Steps in TDD
Write a Test: You start by writing a test that defines what you want your code to do.
Make It Fail: Since you haven't written any code yet, the test naturally fails.
Write the Code: Now you write the code that will make the test pass.
Test Passes: If your code does what the test says, congratulations! Your test passes.
Refactor (if needed): Finally, if your code works, but it's not very clean or efficient, you can clean it up without changing its behavior.
Let's walk through an example of TDD in JavaScript using a simple function to calculate the factorial of a number.
Writing a Test (Red phase): First, we write a test using a testing framework like Jest to define the behavior of our factorial function. We expect the function to correctly calculate the factorial of a given number.
// factorial.test.js
import { factorial } from './factorial'
test('Factorial of 5 should be 120', () => {
expect(factorial(5)).toBe(120);
});
When we run this test initially, it will fail because we haven't written the factorial function yet.
Writing the Code (Green phase): Now, we will write the minimal code required to make the test pass.
export default function factorial(n) {
if (n === 0 || n === 1) {
return 1;
}
return n * factorial(n - 1);
}
After implementing the factorial function, we run the test again. If everything is correct, the test should pass.
Refactoring (Refactor phase): Once the test passes, we can refactor the code to make further improvements without changing the behavior. For example, we might optimize the factorial function to handle large number or optimize runtime.
This process continues with each new requirement or change, ensuring that the code remains bug-free and maintainable with new changes.
Challenges of TDD
There is a research paper where TDD and non-TDD projects were compared in Microsoft and IBM. Each pair of teams worked on different features in the same project. It was noted that the teams practicing TDD witnessed a reduction of around 60% in defect density. However, they required approximately 25% more time to complete their tasks. Beside time investment, TDD also presents other challenges like-
Mindset Shift - Developers need to get accustomed to writing tests before writing code and this may pose challenge for some. Some team members may resist adopting TDD due to a preference for traditional development.
Uncertainty in Requirements - TDD works best when you have defined input and output. But in real life, data are complex and requirements are always evolving. In this case writing test upfront can be tough. Also, as the codebase evolves, tests need to be updated.
Mocking and Stubbing - Testing external dependencies such as databases, APIs, or libraries often requires mocking or stubbing. Testing these features require setup of additional tools and sometimes take up a lot of time even to write a simple test.
Initial Learning Curve - Adopting TDD often involves learning new testing frameworks, techniques, and best practices. This initial learning curve can slow down development initially.
Ultimately, the decision to adopt TDD should be made based on a careful assessment of project-specific factors, team expertise, and development timeline.
Links Around The Web
That’s it folks. Thanks for reading!
Nice read!