TDD
First of all TDD can’t be properly applied if you have no idea what the problem is and you don’t know how to solve it.
So an overview should be done before applying TDD.
- Analyze the problem.
- Go through the related existing code if present.
- Identify where the new functionality should be called from and how.
- Define a list of test cases.
TDD process can be depicted by the below diagram
Let's consider each stage separately.
Red
Write a test (with expectation, ie assertions).
Make sure to write necessary production code just to make it compiler friendly.
So the project should compile successfully and fail the newly introduced test.
Green
Now it’s time to modify the production code to make the test pass.
No new functionality other than the covered by the written test should be introduced.
Refactor
Here we can improve the relevant production code as long as we don’t introduce new functionality which are not covered by tests.
We can improve production code(removing code duplications, renaming) as well as test code (improving readability of tests) in this stage.
This is the opportunity to improve the code.
Above process happens repetitively until the requirement is satisfied by the production code.
Test Last
Problem
There is a requirement to develop a broadband bill calculation component for a leading telecommunication company. There are 2 packages with respective monthly rentals and data limits. 250 LKR should be charged on each exceeding GB.
Lite downloader ( 500 LKR, 1 GB)
Mega downloader (750 LKR, 3 GB)
10% of telecommunication levy should be added to each package.
Now let's discuss the solution for this problem using TDD approach first. As we have to follow the above diagram iteratively I’ll number each iteration. Also I’ll use junit to write unit tests.
Solution
Write a test (stage 1).
Make sure that the test should cover just 1 path of the production code.
@Test
public void calculate_bill_for_lite_package_when_the_limit_is_not_exceeded() {
String pkg = "lite";
float excessAmount = 0;
BillCalc billCalc = new BillCalc();
float actualBill = billCalc.calc(pkg, excessAmount);
float expectedAmount = 550.0f ; // 500 + 500 * (10/100.0f) ;
float delta = 0;
assertEquals(expectedAmount , actualBill , delta);
}
public class BillCalc {
public float calc(String dataPackage, float excessAmount) {
return 0; //minimal code to satisfy the compiler
}
}
Write production code to support the written test (stage 2)
Refactor the code (stage 3)
Refactored production code.
public class BillCalc { public final static String LITE_PACKAGE = "lite"; public final static float LITE_PACKAGE_RENTAL = 500.0f; public final static float EXCESS_GB_CHARGE = 250.0f; public float calc(String dataPackage, float excessAmount) { float finalBill = 0; switch (dataPackage) { case LITE_PACKAGE: float excess = excessAmount * EXCESS_GB_CHARGE; float total = LITE_PACKAGE_RENTAL + excess; finalBill = total + total * (10 / 100.0f); break; } return finalBill; } }
Refactored test code.
@Test public void calculate_bill_for_lite_package_when_the_limit_is_not_exceeded() { float excessAmount = 0; BillCalc billCalc = new BillCalc(); float actualBill = billCalc.calc(BillCalc.LITE_PACKAGE, excessAmount); float expectedAmount = 550.0f; // 500 + 500 * (10/100.0f) ; float delta = 0; assertEquals(expectedAmount, actualBill, delta); }
Let’s execute the test again to check whether we have broken the tests by refactoring the code.
I am going to follow the same process and write more test with production code to fulfill the requirement.
Completed code can be found on below gists.
Tests
Production code
Maintainability
It will be much harder to do this change if the codebase is maintained using the traditional approach. Lots of regression tests should be conducted to ensure the old logic is not broken.
Discussion
As we know enhancements become harder or much more costly when the lifetime of the codebase increases.
Initially TDD seems to be a burden because we have to write tests even for the tiniest thing we introduce to the code. But in the long term the codebase becomes more maintainable and testable.
By using TDD we have reduced this impact in Pagero code bases. Test suite acts as a live documentation. New features and refactorings to the codebase can be done with confidence. Also lots of bugs can be identified at the developer level.
0 comments:
Post a Comment