Skip to main content

Command Palette

Search for a command to run...

What is Test Driven Development?

Published
5 min read
What is Test Driven Development?
S

Stephan is a Flutter Developer from The Netherlands.

By day he is working on serious apps for Pinch, by night he likes to play around by making games and writing about what he has learned.

Test Driven Development, or TDD, is a software development method where one writes the tests before the implementation. You could say that the test drives the implementation.

This article is the first in the Introduction to TDD series in which we will build the logic for the game Tic Tac Toe using TDD.

Reasons to use TDD

What TDD did for me was making me look at the software that I write from another perspective.

Before I used TDD I would only focus on the happy path, meaning I would only program towards the expected usage of the software. The problem with only looking at the happy path is that other paths are missed or forgotten, possibly resulting in buggy software. TDD stimulates you to cover all paths of the code, making it more stable, robust.

When applying TDD, I sometimes end up with solutions I never would have thought of otherwise. That's because you only focus on small goals which prevent you from getting overwhelmed or side-tracked.

Another reason to at least try TDD once, is the way it makes you think about how to structure your code. By having to write testable code, you have to think about modularity and dependencies.

Contrary to what you might think, TDD is also very fast. You don't have to run your whole application to test your code, you only have to run tests which is often a matter of (milli)seconds.

Last but not least, TDD makes you write code that can be refactored with confidence. Since you're testing every bit of code along the way, you can clean-up and improve your code without being afraid of introducing bugs.

The steps to Test Driven Development

The steps to TDD are very simple yet powerful. I recommend trying to follow them as much as possible as they can sometimes guide you towards surprising solutions.

1. Write a test

The test should cover a single requirement. If we for example write a validator for passwords, we could start with a test that checks if the password is at least 8 characters long.

Try starting with the low-hanging fruit first. Constructors and getters are often easier to test than functions or class-methods. Parameter-validation tests are usually pretty easy also.

The reason why I recommend to start with the easier tests first is that you will have a lot of cases already covered for when you get to the more complicated stuff.

The more you practice TDD, the easier it becomes to identify the easier cases.

2. Run the test

In most cases the test will fail, as you haven't implemented anything yet. However, sometimes your test will pass immediately. Don't worry, this doesn't make this test anymore useless.

3. Make it pass

If the test passed in the previous step, this step can be skipped. If not, we will have to make it pass now.

We will do this by writing the minimal implementation to make the test pass, even if it means to return a hardcoded value. It might be very tempting to think ahead and write code that might be useful in an upcoming requirement. Don't. By not thinking ahead you will really be guided by TDD.

Thinking ahead too much might lock you into a specific solution, while you want it to come naturally while solving a single problem at a time.

4. Run all tests

Once you've implemented the code for the current requirement and confirmed that the test has passed, you should also run all the other test. This is just a check to be sure you've not broken anything along the way.

5. Repeat!

Repeat these steps until all your requirements have been covered.

Testing in Dart

Writing tests in Dart could not have been easier. Dart has everything you need build-in and whenever you create a new project it will contain a test directory with an example to get you started.

The anatomy of a Dart test-file

A test-file in dart is just like any other Dart-file. You use a collection of functions to create your tests. Below you will see a basic structure of a test-file:

// This is the entry-point for your test.
main() {

  // Tests are just functions that you call like any other.
  // You simply pass in a code-block that should be executed.
  test('My first test', () {
    // Here you will write your testing code.

    // You perform your action
    final result = isValidPassword('I<3TDD');

    // You use the expect function to test for the desired outcome.
    expect(result, isTrue);
  });

  group('My first test group', () {
    // You can create groups to group tests together.
    // This is useful when tests relate to the same function or
    // property for example.

    test('A grouped test', () {
      // Another test goes here
    });
  });
}

While you could just put all your test functions in the main function, I recommend you to group your tests. Grouping also allows you to scope the set-up and tear-down of your tests and it also gives you the option to run only a selective set of tests while you're implementing a requirement.

What's next?

You now know the basics of TDD. You know what it is, and why it can be useful, even how to apply it in Dart. But nothing will convince you more than some hands-on experience.

So in the upcoming parts of this series I will show you how you can apply TDD to create the logic for the game; Tic Tac Toe!

I will show you how easy TDD can be by using the skills you already possess as a developer.

Next article: Preparing for development

D

Thanks for article Stephan E.G. Veenstra, informative as always 👍

1
M

Already loving this series

1
S

Thank you! That's very kind of you to say.

A

i think their should be one more step called Refactor??

1
S

True, refactoring is often mentioned as a separate step (after the test passes).

Personally I don't see the point of refactoring your code while you're still implementing.

You could refactor your code after you pass the test and then when you do the next requirement realize it's not going to work, meaning you wasted time.

As I mentioned in the 'reasons', you will write code that can be refactored with confidence, but I would suggest to do this after you're done.

This is personal preference of course.

2
A

Yup i also means to do refactor after successfully passing the test: Test -> Code -> Refactor

1
O

Thank you.

1
F
Fueler.io3y ago

Thank you, Stephan for sharing this step by step guide to understand TDD

1
S

It's my pleasure!

J
JAKE3y ago

great article Stephan! really helpful.

1
S

Thank you Jonathan!

More from this blog

S

Stephan E.G. Veenstra

31 posts

Learning and teaching serious Flutter and Dart skills by building not-so-serious apps and games!