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