Skip to content
Permalink
master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time

Test-Driven Development

In this lab you will focus on implementing the test-driven development technique, popularised by Kent Beck in 2003. It is related to the test-first programming concepts that form part of the Extreme Programming methodology. We will be applying this process in two stages:

  1. Writing unit tests for legacy code.
  2. Implementing a full TDD process for our new code.

New to Unit testing?

If you are new to unit testing it is recommended that you start the lab by working through a tutorial that covers the platform you are using:

Writing Tests for Legacy Code

It is likely you already have some code that you will be using as a foundation to add additional functionality. Before you add more features you should take time to write test suites that cover the existing code. Although this can take some time it is something you should always take time to do if you are to create a robust piece of software.

Modularising The Code

If you have not been using the TDD technique it is likely that your code is not split into different files but is probably all in a few large chunks with few files and large functions. Since unit testing is used primarily to test code modules we need to organise the code in this way. Note, this is an iterative process and may take a few attempts to get right.

  1. Carry out manual testing of the 'system' to make sure it works as expected.
  2. Study the code and identify parts that could be pulled into separate functions. Generally these are blocks of code that take a set of input data and calculate a value. Ideally these should not each have any dependencies.
  3. Encapsulate these chunks of code in functions and ensure, as you pull out each block of code this does not introduce bugs.
  4. Keep doing this until you have a series of small functions.
  5. Study the functions you have created and group them together based on purpose or dependencies. Does you system still work?
  6. For each group of functions create a code module. The form this takes varies depending on the language you are using but might typically be a class file (for C++, Java, Python, etc.) or a module (for JavaScript, etc.). Move the functiomality into the modules and import these where needed.

By the end of this process you should have a series of classes/modules that have no (or very few) dependencies and are imported and used in your system.

Writing Unit Tests

Next we need to write tests for each function in our modules. This process can take some time and should be repeated for each class/module you created with one test suite per module.

  1. Create a test suite file for the module.
  2. Choose a function and write a single test for one small part of the functionality using the AAA approach.
  3. Run this test and make sure it passes.
  4. Write more tests to check the function and make sure these run and pass.
  5. Once you have written a comprehensive set of tests for the first function repeat for the others. Run the code coverage tool if available to ensure you have 100% coverage.
  6. Repeat for all the modules in the system.

By the time you have completed this task you should have a comprehensive suite of unit tests that cover all the logic in your system. With this strong foundation in place you can start to build additional functionality,

Implementing Test-Driven Development

The key principle in applying TDD is to develop the code in very small iterations, using the tests to define the required functionality. You should also get into the habit of modularising your code. There is a useful article on Wikipedia that explains the process in more detail.

  1. Start by thinking through the requirements of the next feature and deciding on the function you will need to write in order to achieve this.
    1. What data will be needed by this function (parameters)?
    2. What will this function return (data type)?
  2. Create a stub function that takes the required parameters and returns an arbitrary value of the correct type and format.
  3. As you add more functionality to the function:
    1. Decide on the expected behavior for a given set of inputs (return a specific value or thow a specific exception).
    2. Write a test to describe this behaviour.
    3. Run the test suite (all tests should pass except the one you have just written).
    4. Implement the functionality. Run the test suite in watch mode if available so the tests run on every save.
    5. Once the new test (and the old ones) passes, commit the changes to git.
    6. Now examine the function, rewrite the code to make it easier to understand and add comments. If you break the test you can use git to reset to the last working commit.
    7. Now commit the clean version of the code and start the same process adding the next bit of functionality.