A Very Simple Introduction to Unit Testing

The idea of unit testing is to write code that automatically tests other code, and that can be executed repeatedly. It has grown in popularity over the last few years, its profile having been raised by the test-driven development (TDD) practised within the agile development community. Note that unit testing is not, in itself, test driven development – the latter being a development which is more than unit testing, and that employs unit testing as part of it. This article offers an introduction to unit testing, aimed at less experienced programmers.

To start off with, here is a very simple example class:

    class bank_account
    {
    public:
        bank_account();  // Initially, balance() == 0

        long balance() const;

        void deposit(unsigned int amount);
        void withdraw(unsigned int amount);

        //...
    private:
        long balance_;
    };

This class is basically my C# BankAccount class from my Updating Object Properties While Respecting Encapsulation article, and before going any further there are a couple of points I would like to note about this class:

  • Here I’ve rewritten it in C++. I’m going to use C++ as my programming language for this article. This is because, as part of its C heritage, C++ has the assert macro – and although assert wasn’t originally intended for unit testing, it makes a simple but effective tool for doing so without the need to look outside the language’s standard library. In Java and C# there are unit testing frameworks – Junit and Nunit respectively – that the respective communities have adopted as their standard, but nevertheless one has the overhead of acquiring, setting up, and learning to use an external library. Not having to look beyond the programming language’s standard library helps to keep things simple. If you can read Java/C# you should have no difficulty following the code examples even if you’ve never seen C++ code before; note that the expression assert(x) halts the program with an error message if the expression x evaluates to false.
  • I’m not pretending this is a production standard class: the version shown is no more than an illustrative fragment, the use of long and unsigned int types to represent money is certainly not best, and there is no overflow or underflow checking.

Moving on, the next thing to do is construct the unit tests. I’m going to describe this by building up a set of tests a step at a time.

The first thing to do is write a skeleton C++ main() function (main() is the C++ function that is automatically called when the program is run – note that in C++ functions do not have to be members of classes). This skeleton function simply creates an instance of bank_account and checks that its initial state, with balance() returning zero, is correct:

    #include "bank_account.hpp"
    #include <cassert>

    int main()
    {
        bank_account test_account;
        assert(test_account.balance() == 0);
        return 0;
    }

We compile and run this, and find it runs without any problems, so it’s time to add another test. Let’s see what happens when we make a deposit and see if balance() returns what we would expect:

    #include "bank_account.hpp"
    #include <cassert>

    int main()
    {
        bank_account test_account;
        assert(test_account.balance() == 0);

        test_account.deposit(12);
        assert(test_account.balance() == 12);

        return 0;
    }

Again, when we compile and run this we find it runs without any problem, so we can move. The next step is to make a withdrawal, and check balance() still returns what we would expect:

    #include "bank_account.hpp"
    #include <cassert>

    int main()
    {
        bank_account test_account;
        assert(test_account.balance() == 0);
        test_account.deposit(12);
        assert(test_account.balance() == 12);

        test_account.withdraw(12);
        assert(test_account.balance() == 0); // balance() should now be back to zero

        return 0;
    }

This compiles without a problem, but when the program is run things do not go as smoothly as with the previous steps. Instead of running without error, the program produces an error message saying that assert() failed, and citing the line of the last assert() as being the problem. So far balance() has not let us down, so this suggests the implementation of withdraw() is the problem – and a look at the code reveals a simple mistake:

    void bank_account::withdraw(unsigned int amount)
    {
        balance_ += amount; // Oops! This should be a decrement.
    }

The data member balance_ is being incremented when it should be decremented, so this is fixed, and the unit test now runs smoothly once again.

That completes the step by step illustration of the development of a set of unit tests, and brings this article to a close. This example is contrived and very, very simple, but it does show the points I want it to show. In particular, several times I have encountered unit testing being objected to because of the time overhead it adds to development. However, in reality, it pays for itself with interest. The kind of error shown above is a good example of one that would have been far harder to track down had it not been tested in isolation. In real world development, one needs to think carefully about what needs to be tested, and the above example certainly does not show a complete set of tests. For example, typically, a production class would need unit tests to check it produces error conditions correctly. Even so, a reasonable set of unit tests can usually be designed fairly straightforwardly, and it’s surprising just how many mistakes can be detected just by ensuring code is executed in isolation!

Update

February 5th, 2009.

I’ve now writen a follow up article entitled Pros and Cons of Using the C assert Macro for Unit Testing in C/C++.

2 Comments

  1. Thomas Guest January 29, 2009 at 1:49 pm

    Hi Mark, I’m enjoying your blog, especially the code samples.
    Take care your system includes don’t get swallowed by the blog software though! (Probably an angle bracket issue.)

  2. MarkR January 29, 2009 at 2:12 pm

    Hi Thomas,

    I’m glad you’re enjoying the blog, and thanks for pointing out the mistake. Yes I did indeed allow the blogging software to swallow the cassert header owing to the angle brackets. Because the code is edited from compiled code I didn’t pay it sufficient attention when checking the article.

    Bug Fixed, and Lesson learned!

Leave a Comment

Your email address will not be published. Required fields are marked *