Paulund

Test Driven Development

In this post we will be investigating what is test driven development and how we can use it to improve our development process.

What Is Test Driven Development?

Test driven development is a development process that is exactly what it sounds like, we develop our applications driven by a number of different tests. This involves writing all your tests up front before any development has taken place. At the start this will seem strange as all the tests are going to fail but then we can develop our application to make sure all these tests will pass. Test Driven Development can lead to your code being simpler, more defensive and less feature creep. This is because you only need to write the code to make the tests pass and nothing else. You can also make sure that you aren't about to create functionality that isn't needed by writing your tests first and then running them, if some of your new tests pass then that functionality for the test already exists. These two principles of development are known as:

  • KISS - Keep it simple, stupid.
  • YAGNI - You ain't gonna need it.

Because of Test Driven Development you will have to write more code and the program will most likely take more time to develop because of the tests, but the code you are releasing it is safe to say that this has been well tested and the testing has been documented. With a well structure test case you can be more confident that the code released doesn't have any bugs, which cuts down on the development time spent manual testing your own code and fixing any defects found. Why sit there and manual go through a test script for your code then, if you find a problem you have to then fix it and re-run through the test script. With test driven development you are testing as you are developing and the feature is done when all the tests pass. This means that when a new feature is required on an existing program you can develop the new feature, run all the tests, make sure everything passes and you can be sure that your new feature has not broken anything else on the program. You do not even have to manual test any other areas of the system as your unit tests will make sure that the rest of the code is safe to be released. The way that test driven development is done will lead to developer to create more modular, flexible and extensible code. The nature of test driven development requires the developer to create small units of code and more focuses classes that are fully tested and can be joined together later in the development process.

New Feature Process With Test Driven Development

  • Write your tests.
  • Run all the tests and make sure they fail.
  • Write development code to make a test pass.
  • Run all tests again to make sure only this test passes.
  • Pick another test and write development code to make this test pass.
  • Keep going until all tests pass.
  • Refactor code.
  • Make sure all tests pass.
  • Feature is complete.

There is a process that needs to be followed in Test Driven Development each new feature starts by writing your test on how you expect the feature to function. To start with this test should fail as the code needed to process this function doesn't currently exist. The benefit of writing the test first is that it will make sure that you fully understand the requirements of the feature before any development has taken place. It will allow you to look at all the things that can make the function fail and what is needed for the function to pass. The second step is to run all the tests to make sure that your new tests are the only ones that have failed. It makes sure that if a new test passes then the functionality you are expecting to develop from this test isn't needed as it already exists. This also makes sure that the tests you have just written can actually fail, so now we can develop to make them pass. The next step is to write the least amount of code to make a test pass, then you run all the tests again and make sure that this test passes and the other new tests fail. Then you can pick another test and write the least amount of code to make this test pass but do not remove any code to get this to work. At this stage it doesn't matter if the code is messy and you have a long list of if statements you just want the tests to pass. Re-run all you tests and once they all pass now you can refactor the code you have just created. Do not remove any of the functionality but clean up the code, by removing multiple if statements with a combined if or statement, add comments to your code...etc. After refactoring re-run all your tests and make sure nothing has broken during your refactoring. When all tests have passed your feature is complete and you can repeat the process for the next feature.

Example Of Test Driven Development

An example I am going to use is developing a basic calculator, we know the functionality that it requires, we need it to add, subtract, multiple and divide different numbers. Therefore we can write tests for this different functionality, from writing the tests we will understand how this functionality is intended to behaviour then we can develop the application to get these to work. For our first test we are going to add two numbers together so we create a test where we pass two numbers into a function and the return of the function will be the value of the two numbers added together. From this test we know that we need to develop a function that has two parameters and the return will be the value of the two numbers.

function add( $number1, $number2 )
{
    return $number1 + $number2;
}

Now if we run our test it will pass and the return value will be what we expect. But were not only going to test successful behaviour we also need to test fail states, so what happens if the numbers we pass in are null? What we do expect to happen? What if the numbers we pass in is not a number but a string? What do we expect to happen? You can create tests to deal with these fail states, you can create a test that passes in the first parameters as null and expect a return of false. Run this test and it will fail as the code will still deal with this request so now we can change our code on the application to handle this null parameter.


function add( $number1, $number2 )
{
    if(is_null( $number1 ) || is_null( $number2 ))
    {
         return false;
    }

    return $number1 + $number2;
}

If you run this test again it will pass as we do a check on the first parameter to see if it's null and if it is we return false. From doing this test we will also know that we will have to do the same check for the second parameter. Now we can write a new test to pass a null value into the second and when we run this test it will now pass. The other test we want to perform is to make sure that the numbers can not be a null value and must be numbers, either integers or decimals. So we create a test and set the parameters to be null and set the return to be false, but when we run the test it will fail as the return value will not be false, we need to change the function to check if the value of the parameters are numbers before we can add the values together.


function add( $number1, $number2 )
{
    // If the value is null or not a number then return false
    if(is_null( $number1 ) || 
       is_null( $number2 ) || 
       !is_numeric( $number1 ) || 
       !is_numeric( $number2 ))
    {
         return false;
    }

    return $number1 + $number2;
}

It will now do a check to see that both numbers are null or if both the numbers are not numerics, if it finds a problem with any of them then it will return false and the test will pass. As you can see this function is now a lot more defensive than it originally was by doing a number of different checks to make sure that the values provided to this function are going to be correct. But we can improve this code more by refactoring it by removing the is_null() function and just have the !is_numeric() function as these will fail if the parameter is a null.


function add( $number1, $number2 )
{
    // If the value is null or not a number then return false
    if(!is_numeric( $number1 ) || !is_numeric( $number2 ))
    {
         return false;
    }

    return $number1 + $number2;
}

This is good programming you can not always trust the parameters the are received from a function, it is normally fine when you are the only one who will be calling this function as you know what you expect it to do. But what if you are on a team of developers and you create this function and now another developer wants to use this on their feature of the site. They might pass in null values, string values and will not get the expected behaviour of the function. This is when you can direct them at the tests for this function so they will understand how to use this function correctly. This is massive benefit to development teams as now the developer who wrote the function doesn't have to explain how to use this function the tests will explain exactly how to use it. This is called self documenting code, which is code that is documenting how to use other functions of code.

Conclusion

Of course this is a very simple example with very simple code but I hope you can see the benefit of writing a test that fails then refactoring the code to make this test pass. It makes sure that you can not create a new feature unless it will make a failing test pass. It makes sure that you have simple tests to make sure this functionality will fail. It makes sure that you only write code that is needed to make your feature pass.