Overview

TFELTests is a lightweight and flexible unit testing framework designed for C++ applications. It provides a comprehensive set of tools for writing, organizing, and executing unit tests, with support for test suites, test proxies, and multiple output formats including standard streams and XML (JUnit format).

The framework is particularly well-suited for scientific computing applications and is integrated with the TFEL ecosystem.

Key Features

Test Organization

The framework supports several levels of test organization:

Assertion Macros

The framework provides a rich set of assertion macros for testing various conditions:

Output Formats

Test results can be output to:

Automatic Test Registration

The framework provides mechanisms for automatic test registration using proxy classes, reducing boilerplate code.

Usage

Basic Test Case

The simplest way to create a test is to inherit from the TestCase class:

#include "TFEL/Tests/TestCase.hxx"
#include "TFEL/Tests/TestProxy.hxx"

struct MyTest final : public tfel::tests::TestCase {
  MyTest() : TestCase("MyGroup", "MyTest") {}
  tfel::tests::TestResult execute() override {
    TFEL_TESTS_ASSERT(true);
    TFEL_TESTS_ASSERT(1 != 2);
    TFEL_TESTS_CHECK_EQUAL(std::string("test"), "test");
    return this->result;
  }
};

TFEL_TESTS_GENERATE_PROXY(MyTest, "MyTestSuite");

Test Function Wrapper

For simple test functions, the TestFunctionWrapper can be used:

#include "TFEL/Tests/TestFunctionWrapper.hxx"

bool myTestFunction() {
  return true;
}

int main() {
  using namespace tfel::tests;
  using Wrapper = TestFunctionWrapper<myTestFunction>;
  auto test = std::make_shared<Wrapper>("MyGroup", "myTestFunction");
  TestSuite suite("MySuite");
  suite.add(test);
  return suite.execute().success() ? EXIT_SUCCESS : EXIT_FAILURE;
}

Test Suite

Tests can be grouped into suites for better organization:

#include "TFEL/Tests/TestSuite.hxx"
#include "TFEL/Tests/TestFunctionWrapper.hxx"

bool test1() { return true; }
bool test2() { return false; }

int main() {
  using namespace tfel::tests;
  using Wrapper1 = TestFunctionWrapper<test1>;
  using Wrapper2 = TestFunctionWrapper<test2>;
  
  auto a = std::make_shared<Wrapper1>("test1");
  auto b = std::make_shared<Wrapper2>("test2");
  
  TestSuite suite("MySuite");
  suite.add(a);
  suite.add(b);
  
  return suite.execute().success() ? EXIT_SUCCESS : EXIT_FAILURE;
}

Test Manager

The TestManager singleton provides centralized management of tests and outputs:

#include "TFEL/Tests/TestManager.hxx"
#include "TFEL/Tests/TestFunctionWrapper.hxx"

bool test1() { return true; }

int main() {
  using namespace tfel::tests;
  using Wrapper = TestFunctionWrapper<test1>;
  
  auto& m = TestManager::getTestManager();
  auto test = std::make_shared<Wrapper>("test1");
  
  m.addTest("MySuite", test);
  m.addTestOutput("test-output.txt");
  m.addXMLTestOutput("test-results.xml");
  
  return m.execute().success() ? EXIT_SUCCESS : EXIT_FAILURE;
}

Output Configuration

The framework supports various output configurations:

Standard Stream Output

#include "TFEL/Tests/StdStreamTestOutput.hxx"

// Redirect output to a file
auto output = std::make_shared<tfel::tests::StdStreamTestOutput>("output.txt");

// Redirect output to std::cout with colors
auto console = std::make_shared<tfel::tests::StdStreamTestOutput>(std::cout, true);

XML Output (JUnit Format)

#include "TFEL/Tests/XMLTestOutput.hxx"

auto xml_output = std::make_shared<tfel::tests::XMLTestOutput>("results.xml");

Multiple Outputs

#include "TFEL/Tests/MultipleTestOutputs.hxx"

tfel::tests::MultipleTestOutputs outputs;
outputs.addTestOutput(std::make_shared<tfel::tests::StdStreamTestOutput>("file1.txt"));
outputs.addTestOutput(std::make_shared<tfel::tests::XMLTestOutput>("results.xml"));

API Reference

Core Classes

Test

Base class for all unit tests. Provides the interface that all tests must implement.

TestCase

A concrete test class that can contain multiple assertions.

TestSuite

A collection of tests that can be executed together.

TestManager

Singleton class for managing tests and outputs.

TestFunctionWrapper

Template class for wrapping simple test functions.

TestProxy

Template class for automatic test registration.

Output Classes

TestOutput

Base class for test outputs.

StdStreamTestOutput

Output to standard streams.

XMLTestOutput

Output to XML file in JUnit format.

MultipleTestOutputs

Aggregate multiple outputs.

Result Classes

TestResult

Structure describing the result of a test or test suite.

Macros

Test Registration

Assertions

Integration with Build Systems

The TFELTests library is typically used in CMake-based projects. Tests can be added to the build system using the standard CMake testing infrastructure or custom test runners.

Examples

The TFEL source code includes several examples of unit tests in the tests/Tests/ directory:

Best Practices

  1. Test Isolation: Each test should be independent and not rely on the state of other tests.

  2. Descriptive Names: Use descriptive names for tests and test suites to make it clear what is being tested.

  3. Single Responsibility: Each test should verify a single behavior or property.

  4. Use Assertions Wisely: Use the appropriate assertion macro for the condition being tested.

  5. Test Coverage: Aim for comprehensive test coverage of your code, including edge cases and error conditions.

  6. Performance: For performance-critical code, consider the overhead of test execution and use appropriate test strategies.

Continuous Integration

The XML output format follows the JUnit standard, making it compatible with most continuous integration systems including Jenkins, GitLab CI, and GitHub Actions. This allows for easy integration of test results into your CI pipeline.

References