Test!

In our last post, we talked about error handling and ways to protect your code from those nefarious bugs. However, if you’re like me, there is a very high probability that a few stubborn ones still managed to evade your comprehensive checks. To get rid of them, you need to continuously inspect your code for possible infections. You need to test.
There are numerous manners in which you can test your code: from analyzing debug messages in the console to fully automated integration test systems. For now, let’s focus on those that should be an integral part of every developer’s day: unit tests.
Unit tests, as their name suggests, focus on exercising basic units of code, or more simply put, your modules. Why should we write them? Because they give us the possibility to put our modules under stress, to see how they perform under various circumstances. Essentialy, we gain insight about the usage of that module which in turn guides our design. And this leads to a very important aspect of testing: by writing (or just thinking about) tests we make better code. More decoupled code. Code that is easy to extend and maintain.
But how exactly should you write these tests?
In the last decade there is an ever increasing number of programmers that practice a new software development process: Test Driven Development (TDD). Adopting it transforms your way of programming by focusing on the tests, and then designing your code according to them. Essentially, it says:
- You may not write production code until you have written a failing unit test
- You may not write more of a unit test than is sufficient to fail, and not compiling is failing
- You may not write more production code than is sufficient to pass the current failing test
By following these rules you are encouraged to program in very short cycles that make you think about the usage and contract of each function in your module. And as a bonus, you get to catch bugs in an instant.
Unit tests can be written in C++ using various testing frameworks. Whatever your working environment might be, be sure to look up if it supports testing and familiarize yourself with the procedures. We at Connection Quest tend to use Qt Test framework, as you can see in this contrived example:
void CalculatorTest::testAddition() {
// build
Calculator calculator;
// operate
int result = calculator.add(2, 3);
// check
QCOMPARE(result, 5);
}
void CalculatorTest::testSubtraction() {
Calculator calculator;
int result = calculator.subtract(10, 6);
QCOMPARE(result, 4);
}
void CalculatorTest::testMultiplication() {
Calculator calculator;
int result = calculator.multiply(7, 0);
QCOMPARE(result, 0);
}
void CalculatorTest::testDivision() {
Calculator calculator;
int result = calculator.divide(60, 2);
QCOMPARE(result, 30);
}
What matters is that, similar to your production code, you keep your tests clean. As your code evolves, so do your tests. If not written cleanly, there is a high possibility that they’ll be a burden to change. Consequently, you might be tempted to break your testing discipline and leave that updated module uninspected. On the other hand, clean tests give you confidence to refactor and improve your code knowing that everything will continue to work as it did before.
Test functions should be written using a build-operate-check pattern:
- build the data
- operate on it
- check the results
Furthermore, try to minimize the number of asserts in a test. Each test function should assess just one concept.
Finally, remember that tests should be FIRST:
- Fast
- Independent
- Repeatable
- Self-Validating
- a boolean output, either they pass or fail
- Timely
- written just before production code
To conclude, your responsibility as a developer is not just to write code, but also thoroughly test it to see how it will behave in the production environment. Try to incorporate these ideas into your daily routine to make rock-solid solutions that will withstand the test of time and delight your users.
Stay tuned and happy coding.
