Skip to content

Writing Unit Tests

Unit tests are written in C++ with the aid of macros from Catch2 2.x. These tests are suited for larger scale testing than sktest, as a large number of tests can be quickly run and the output easily checked. This makes them suited for automation, because any failing test will be flagged so that any issues can be resolved before merging the changes into the main repository of SplashKit.

When writing unit tests, you should aim to test the expected behaviour of a specific function or procedure. In addition to positive test cases (where the function or procedure takes valid input and should produce the expected output), consider negative cases (cases where the function or procedure has invalid input), as well as edge cases.

Test files are located at:

  • Directorycoresdk
    • Directorysrc
      • Directorytest
        • unit_test_main.cpp
        • unit_test_<name>.cpp

unit_test_main.cpp is the entry point for all unit tests. You do not need to modify this to write your own tests or update existing ones.

The unit_test_<name>.cpp files contain tests for related parts of SplashKit. For example, unit_test_utilities.cpp has tests for SplashKit’s utility functions. A test file must include the Catch2 header file along with any other includes required:

#include "catch.hpp"

At a minimum, a unit test consists of a TEST_CASE and an assertion (usually REQUIRE):

TEST_CASE("gets the number of milliseconds that have passed since the program was started", "[current_ticks]")
{
unsigned int result = current_ticks();
REQUIRE(result >= 0);
}

TEST_CASE(name, [,tags]) defines a test case with the given name and, optionally, one or more tags.

REQUIRE evaluates an expression and aborts the test as a failure if the result is false. REQUIRE_FALSE is similar but fails if the expression evaluates true. There are other assertion macros but these are the most common.

A test may contain multiple assertions:

TEST_CASE("random number float between 0 and 1 is generated", "[rnd]")
{
float result = rnd();
REQUIRE(result >= 0);
REQUIRE(result <= 1);
}

You may write tests that have some common steps, such as defining a variable. You can define one or more SECTION(name) inside a TEST_CASE. The TEST_CASE is run from the start for each SECTION.

TEST_CASE("return a SplashKit resource of resource_kind with name filename as a string", "[file_as_string]")
{
const resource_kind RESOURCE = resource_kind::BUNDLE_RESOURCE;
const string RESOURCE_PATH = "blah.txt";
SECTION("filename is a valid file")
{
string result = file_as_string(RESOURCE_PATH, RESOURCE);
string expected = "BITMAP,ufo,ufo.png\n";
REQUIRE(result == expected);
}
SECTION("filename is an empty string")
{
string result = file_as_string("", RESOURCE);
string expected = "";
REQUIRE(result == expected);
}
SECTION("filename is an invalid file")
{
string result = file_as_string("invalid.txt", RESOURCE);
string expected = "";
REQUIRE(result == expected);
}
}

This test has three SECTIONs, so the TEST_CASE will run three times. Each time, the RESOURCE and RESOURCE_PATH variables will be defined.