What is Test-Driven Development? How to use it? A simple JavaScript example.

What is Test-Driven Development? How to use it? A simple JavaScript example.

What is Test-Driven Development?

Test-Driven Development (TDD) is a software development process that relies on the repetition of a very short development cycle: first, the developer writes an (initially failing) automated test case that defines a desired improvement or new function, then produces the minimum amount of code to pass that test, and finally refactors the new code to acceptable standards.

Some of the benefits of Test-Driven Development

  1. It forces the developer to think about the desired functionality before writing any code. This can help to avoid code duplication and aim for a more elegant design.

  2. It provides a set of regression tests that can be run after any code changes to ensure that the functionality still works as expected.

  3. It can act as documentation for the code since the tests can serve as a clear and concise description of the functionality.

  4. It can help catch errors early on in the development process before the code is released to users.

  5. It can help improve the quality of code by encouraging developers to refactor their code regularly. By breaking down the development process into small, incremental steps, developers can more easily identify areas where their code can be improved, and make the necessary changes. This can lead to cleaner, more maintainable code in the long run.

When to use Test-Driven Development?

TDD can be used for any size project, from small scripts to large applications. It is especially well suited for projects with frequent or complex changes, as it helps to ensure that code changes do not break existing functionality.

How to Write Good Tests

Writing good tests is crucial for successful TDD. A good test should be clear and concise and should describe the expected behavior of the code under test. It should also be isolated from other code so that changes in other parts of the codebase do not break the test.

Another important aspect of writing good tests is choosing the right level of abstraction. Unit tests, for example, should test the behavior of individual classes and methods, while integration tests should test the behavior of the system as a whole.

Finally, it is important to remember that tests are not a replacement for good design. A well-designed system should be easy to test, but a poorly designed system can be difficult to test no matter how many tests are written.

The TDD Cycle - the best way to start working in TDD

There are four steps in the TDD cycle:

  1. Write a test
  2. Run the tests and see if they fail
  3. Write the code to make the tests pass
  4. Refactor the code

We will use JavaScript and Jest library example to showcase how to follow the TDD Cycle.

Step One: Write a Test

TDD begins with a test. This test should define some desired behavior or functionality that does not currently exist in the code. The test should be brief and to the point, it should only test one thing/behavior. For example, if you are working on a TODO app, you might write a test that checks if marking TODO as complete will delete it from the current tasks list. Once you have written the test, run it to see if it fails. It should fail because the functionality tested does not yet exist in the code.

Suppose we want to write a code which will return the biggest number from array. Let's start with test:

test('returns the largest number in an array', () => {
  const numbers = [1, 2, 3, 4, 5];
  const largest = findLargest(numbers);
  expect(largest).toEqual(5);
});

Step two: Run the test and see if they fail

After writing the test, run it to see if it fails. The test should fail because the functionality it tests does not currently exist in the code.

Step Three: Write the Code

Next, write the code necessary to make the test pass. This code should be as simple as possible; it is not the time to worry about making the code pretty or perfect. The goal is simply to make the test pass. Once the code is written, run the tests again. If they pass, go on to step three. If they fail, go back and check your code to make sure you wrote it correctly.

Here's the implementation that will make our previous test pass:

function findLargest(numbers) {
  let largest = numbers[0];
  for (let i = 1; i < numbers.length; i++) {
    if (numbers[i] > largest) {
      largest = numbers[i];
    }
  }
  return largest;
}

Step Four: Refactor

After the code passes the tests, it is time to refactor. This is the step where you can improve the code, making it more readable or efficient. However, you should not add any new functionality during this step. The goal is simply to clean up the code. Once you have finished refactoring, run the tests again to make sure they still pass.

Our example is quite simple and it does not require refactoring but if working with anything more complex your test will be a safety net on which you can rely.

Back to Step One

Repeat as many times as needed, adding new test cases and behaviors. By following the four steps outlined above, you can ensure that your code is well-tested and of high quality.

Wrapping up

Test-Driven Development is a process that can help improve the quality of your code and make your development process more efficient. By writing tests before you write code, you can ensure that your code meets your requirements and that it does not break existing functionality. Following the TDD cycle of writing a test, running the tests, writing the code, and refactoring can help you produce high-quality code that is well-tested and easy to maintain.