ALEMIL

Welcome to a blog about application and game development

Test-driven development: Introduction

Test-driven development: Introduction 2018-05-20 07:59:19
Est. read time: 3 minutes, 51 seconds

Test-driven development is a concept of writing application code after writing tests. It requires from a developer to first define what is the expected outcome before starting the implementation.

To test, or not to test


In the good old days, you could just write your code and manually check if it works (or not). Maybe give it for some manual testers, who would try to break it. Then at some point you release it and pray it doesn't blow in your clients faces. This concept might still be the way to go if you are a solo developer, or you are not sure if the feature you are implementing will serve your needs. Testing takes time and the gain is seen in the long term.

Applications became more complex over the years, teams working on them grew. A lot more bugs are being introduced into the code base, quality of the code varies, knowledge and experience are spread among peers. Writing automatic tests for your application isn't just a way to ensure the feature you are delivering will work, it ensures that it will work until concept for this functionality changes.

Test first


Test-driven development (TDD) is a technique that introduces a new workflow:
1. Write test
2. Run test
3. Write application code
4. Run test, go back to step 3 until the test passes
5. Repeat step 1

For some, it might be easier to understand this concept in code, so let's write this short loop to help visualize the concept.
while (coding) {
this->writeTest();
bool testResult = this->runTest();
while (testResult == false) {
this->writeCode();
this->runTest();
}
}
Let's consider test-driven development step by step.

Step 1: Write test


First, we want to write a test. The test should fail because we didn't start implementing the application code yet. Let's try this in a real-world example, a simple function from Entity Component System implementation that will just add a new entity to the manager.
function testAddEntityToManager() {
try {
Manager manager = Manager();
Entity lastEntity = manager.getLastEntity();
Entity entity = manager.createEntity();

if (lastEntity + 1 != entity) throw 1;
}
catch (int e) {
if (e == 1) {
std::cout << "Manager@testAddEntityToManager: Entity unique id counter has not increased";
}
}
}
First, we initialize a Manager instance and take the unique identifier for our last inserted Entity. Next step is to try creating a new entity. We expect that this will bump the unique identifier to the next value. If this will not be the case we just display a message that will help us quickly find where our application doesn't meet our expectations.

Let me quickly mention here, that this exception throwing is just for visualization. Usually, you would want to use some existing library specifically designed for testing that would simplify this greatly and just allow for assertions, like so:
this->assertEquals(entity, lastEntity +1);
And the line above would cover displaying the proper message and stopping code execution on failure. Popular languages have an existing libraries that will help with that.

Ok, let's move on to the next step.

Step 2: Run test


We run the test and it fails horribly. We don't have Manager class implemented, we didn't define Entity, the assertion we have made about our code can't even be checked at this point. This is the expected result at this point. Now that we have defined what are our needs for this application we can start making them a reality.

Step 3: Write code


Let's get to writing the code and create what we need for our application to work correctly.
// Entity.h
typedef unsigned int Entity;
// Manager.h
#include "Entity.h"

class Manager {
public:
Entity getLastEntity() {
return this->lastEntity;
}

Entity createEntity() {
this->lastEntity++;
return this->lastEntity;
}
protected:
Entity lastEntity = 0;
}

Step 4: Run test


When we include these classes in our test it should pass with green colors. If the test didn't pass, we go back to step 3, fix all issues that came up and run the test again. We repeat this process until everything works as per our assumptions that we made in our tests.

Step 5: Repeat


We implemented only a small part of what we actually need here. This was not a fully fledged feature that we have to do to accomplish our task. When we create Entities in our Manager we want the Manager to store Components associated with entities, we want it to register the Entity in every System that should operate on this Entity and maybe some other, more complex things.

All of this, according to test-driven development workflow should be done as an iterative process. We built a part of what we need and we made sure that it works. As we add more and more, we can quickly run the test to make sure nothing was broken along the way. After everything in our task is done, we can refactor our code, cleaning it up a bit and tests will ensure the code is still working as expected.

Conclusion


Test-driven development is a great iterative process of writing applications that can help you break your needs into smaller, more manageable chunks that you can test every step of the way. It will also make your colleagues grateful as you reduce the chance for them to accidentally break your code in the future. You will gain confidence in your code, greatly reducing the stress associated with deployment.

Although it does take additional time, it is an investment into the future. This approach is not the best choice when the application doesn't have potential to grow. If you plan to make a short game or a website in 2 weeks and just move on to another project, you will not see a return in that investment. On the other hand, if project is a big one, with multiple collaborators and might take years to develop and potentially expand, testing can save a lot of time and money.

There is a lot more to testing than making a simple assertion about our data. We will cover this in depth in another article soon. For now, I hope this article explained the concept behind the test-driven development and got you interested to give it a try.

Check out a complete example of feature implemented in Laravel the TDD way.



Comments +