Lukas Püttmann    About    Research    Blog

Simple example of how to write unit tests in Matlab

A unit test is a little program that checks if some part (or unit) of your code works as expected. What arguments are there for bothering to write such tests?

  • You find bugs more quickly
  • It’s reassuring to first run your tests when you haven’t touched your codes
  • Check if anything brakes if you change your code
  • Writing test also nudges you to keep functions small, as it’s more difficult to test functions when they have many input arguments.

Matlab ships with a unit testing framework since version R2013a. (See here if you’re using an earlier version).

I didn’t find the existing examples of how to use it easy to follow, so I’m putting here an explanation of how to test one individual function.

You can find all codes here.

Say we have a function add_one.m we want to test:

function y = add_one(x)
    y = x + 1;
end

For our unit test, we then write an additional script which we have to name with either test_ at the beginning or _test at the end. So here’s the new script test_add_one.m:

function tests = test_add_one
    tests = functiontests(localfunctions);
end

function test_normal1(testCase)
    x = 1;
    actSol = add_one(x);
    expSol = 2;
    verifyEqual(testCase, actSol, expSol)
end

The first three lines are always required and we only need to change the function name to match the name of the file.

The following function test_normal1 is our first test case. We will pass in the value x = 1 and check that the result is indeed 2.

So now go to the Matlab command line and run:

>>run(test_add_one);

Which returns:

Running test_add_one
.
Done test_add_one

There’ll be a dot for every test case for this function. In this case everything worked fine, but there would be an extensive message if an error had occured.

So let’s add some more tests:

function test_normal_pi(testCase)
    x = pi;
    actSol = add_one(x);
    expSol = pi + 1;
    verifyEqual(testCase, actSol, expSol)
end

function test_irrationalNr(testCase)
    x = 3 + i;
    actSol = add_one(x);
    expSol = 4 + i;
    verifyEqual(testCase, actSol, expSol)
end

function test_negative(testCase)
    x = -5;
    actSol = add_one(x);
    expSol = -4;
    verifyEqual(testCase, actSol, expSol)
end

function test_matrix(testCase)
    x = [1, 2; 3, 4];
    actSol = add_one(x);
    expSol = [2, 3; 4, 5];
    verifyEqual(testCase, actSol, expSol)
end

It’s a good idea to give the functions meaningful names so that when there’s an error, we know where things went wrong. Don’t worry if the names get really long, they’ll only live in this script anyway.

The tricky thing is to think of the irregular ways the function might be used. For example, the following tests check that we get the right output even if we pass in an empty matrix or an NaN value:

function test_empty(testCase)
    x = [];
    y = add_one(x);
    actSol = +isempty(y); % plus converts logical to double
    expSol = 1;
    verifyEqual(testCase, actSol, expSol)
end

function test_nan(testCase)
    x = nan;
    y = add_one(x);
    actSol = +isnan(y); % plus converts logical to double
    expSol = 1;
    verifyEqual(testCase, actSol, expSol)
end

Now let’s give the function something where would expect an error. If we pass the function a string 'Hello world' it returns a numerical vector. That’s not what we want, so let’s add

assert(isnumeric(x), 'Input must be numeric.')

to our add_one.m function. So now it fails if the input is not a number.

The following test case then checks if indeed an error is returned:

function test_stringError(testCase)
    x = 'Hello world';
    
    try
        y = add_one(x);
        actSol = 0;
    catch
        actSol = 1;
    end        
    
    expSol = 1;
    verifyEqual(testCase, actSol, expSol)
end

I use try-catch here to check if the function returns an error. There might be better ways to do this, but this works for me.

But we don’t always just have to check that results are equal, as sometimes we want to make sure that the difference is below some numerical threshold. In this case, calculate the absolute or relative error as actDiff and check that it’s less than some acceptable error like this:

verifyLessThan(testCase, actDiff, 1e-10)

One thing I lack so far is a way to test local functions, so functions that you define within some other function and which only that function can use.

So that’s it. If somebody has ideas for improvements, please let me know!