Test Driven Development (TDD)

Yudhistiro Agung N
5 min readJun 17, 2019

--

All the code is guilty, until proven innocent

Test-driven development is a development concept when the tests are written before the code. In practical, Developer start the development process by writing unit-test before writing actual code implementation.

TDD lifecycle

After each test, refactoring is done and then the same or a similar test is performed again. The process is iterated as many times as necessary until each unit is functionally working as expected

Example

In this example i will use Typescript as the language and jest for testing framework

Assumes that we want to create a calculator app. And we define some functionality like below:

  1. add
  2. subtract
  3. multiply

We can create the calculator interface like this:

interface Calculator {    
add(a: number, b: number): number;
substract(a: number, b: number): number; multiply(a: number, b: number): number;}

interface implementation:

class Karce implements Calculator {    public add(a: number, b: number): number {        throw new Exception();     }    public substract(a: number, b: number): number {        throw new Exception();    }    public multiply(a: number, b: number): number {        throw new Exception();    }}

So instead writing the implementation for each function, we will create the unit-test for all of them. But in this example i will focus with add function, hehe

  1. Write some tests
// initialized the calculator instanceconst karce: Karce = new Karce();describe('Calculator test', () => {    describe('Add', () => {        it('given a=1 and b=2, should return 3', () => {           const a = 1;           const b = 2;           const expectedResult = 3;           const actualResult = karce.add(a, b);           expect(actualResult).toBe(expectedResult);        });        it('given a=5 and b=5, should return 10', () => {          const a = 5;          const b = 5;          const expectedResult = 10;          const actualResult = karce.add(a, b);          expect(actualResult).toBe(expectedResult);        });        it('given a=3 and b=17, should return 20', () => {          const a = 3;          const b = 17;          const expectedResult = 20;          const actualResult = karce.add(a, b);          expect(actualResult).toBe(expectedResult);        });
it('given a=-1 and b=2, should return 1', () => {
const a = -1; const b = 2; const expectedResult = 1; const actualResult = karce.add(a, b); expect(actualResult).toBe(expectedResult); }); });});

2. Run the test (failed)

All the tests will be failed because initially we don’t have any implementation inside the add function

3. Write the implementation, lets make the first iteration failed, because we put the wrong logic

class Karce implements Calculator {    public add(a: number, b: number): number {       const result = a - b;       return result;    }   ...}

4. Run the test (failed)

All the test is failed because we put wrong logic inside the function

5. Lets iterate the implementation again, and fix the logic

class Karce implements Calculator {    public add(a: number, b: number): number {        const result = a + 1 + b - 1; // example of bad code        return result;    }    ...}

6. Run the test

All the test is passed, all is green

7. But wait, i think we need refactor our code, because is not efficient

class Karce implements Calculator {    public add(a: number, b: number): number {        return a + b;    }   ...}

8. Run the test

All the test is passed, all is green

After we know that all out test is green, we still need to iterate the process and add more tests, just to make sure we add more test coverage.

The benefit of using TDD approach :

  • Safer Refactoring

Once you’ve got a test passing, it’s then safe to refactor it, secure in the knowledge that the test cases will have your back. If you’re having to work with legacy code, or code that someone else has written, and no tests have been written, you can still practice TDD. You needn’t have authored the original code in order for you to TDD. Rather than thinking you can only TDD code that you have written, think of it more as you can only TDD any code you are about to write. So if you inherit someone else’s untested code, before you start work, write a test that covers as much as you can. That puts you in a better position to refactor, or even to add new functionality to that code, whilst being confident that you won’t break anything.

  • Fewer Bugs

TDD results in more tests, which can often result in longer test run times. However, with better code coverage, you save time down the line that would be spent fixing bugs that have popped up and need time to figure out. This is not to say that you might be able to think of every test case, but if a bug does come up, you can still write a test first before attempting to fix the problem, to ensure that the bug won’t come up again. This also helps define what the bug actually is, as you always need reproducible steps.

  • Increasing Returns

The cost to TDD is higher at first, when compared to not writing any tests, though projects that don’t have tests written first usually end up costing more. This stems from them not having decent test code coverage, or any at all, making them more susceptible to bugs and issues, which means more time is spent in the long run fixing those. More time equals more money, which makes the project more expensive overall. Not only does TDD save time on fixing bugs, it also means that the cost to change functionality is less, because the tests act as a safety net that ensure your changes won’t break existing functionality.

References:

--

--