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.
The framework supports several levels of test organization:
The framework provides a rich set of assertion macros for testing various conditions:
TFEL_TESTS_ASSERT(X): Assert that a condition is
trueTFEL_TESTS_STATIC_ASSERT(X): Static assertion that is
evaluated at compile timeTFEL_TESTS_CHECK_THROW(X, Y): Check that code X throws
exception of type YTFEL_TESTS_CALLMETHOD(X): Call a method and register
its resultTFEL_TESTS_CHECK_EQUAL(X, Y): Check that X equals
YTFEL_TESTS_CHECK_GT(X, Y): Check that X is greater than
YTFEL_TESTS_CHECK_GE(X, Y): Check that X is greater than
or equal to YTFEL_TESTS_CHECK_LT(X, Y): Check that X is less than
YTFEL_TESTS_CHECK_LE(X, Y): Check that X is less than or
equal to YTest results can be output to:
The framework provides mechanisms for automatic test registration using proxy classes, reducing boilerplate code.
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");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;
}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;
}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;
}The framework supports various output configurations:
#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);#include "TFEL/Tests/XMLTestOutput.hxx"
auto xml_output = std::make_shared<tfel::tests::XMLTestOutput>("results.xml");#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"));Base class for all unit tests. Provides the interface that all tests must implement.
virtual std::string name() const = 0: Return the name
of the testvirtual std::string classname() const = 0: Return the
group/class of the testvirtual TestResult execute() = 0: Execute the test and
return the resultA concrete test class that can contain multiple assertions.
TestCase(const std::string&): Constructor with test
nameTestCase(const std::string&, const std::string&):
Constructor with group and test namevirtual void registerResult(const std::string_view, const bool, const std::string_view = {}):
Register a test resultTestResult result: The result of the testA collection of tests that can be executed together.
TestSuite(): Default constructorTestSuite(const std::string&): Constructor with
suite namevoid add(const TestPtr&): Add a test to the
suiteTestResult execute(TestOutput&): Execute the suite
with a specific outputTestResult execute(): Execute the suite with default
outputSingleton class for managing tests and outputs.
static TestManager& getTestManager(): Get the
singleton instancevoid addTestOutput(TestOutputPtr): Add a test
outputvoid addTestOutput(std::ostream&, const bool = true):
Add a stream outputvoid addTestOutput(const std::string&): Add a file
outputvoid addXMLTestOutput(const std::string&): Add an
XML outputvoid addTestOutput(const std::string&, TestOutputPtr):
Add output for a specific suitevoid addTest(const std::string&, TestPtr): Add a
test to a suiteTestResult execute(): Execute all registered testsTemplate class for wrapping simple test functions.
TestFunctionWrapper(const std::string&, const std::string&):
Constructor with group and function nameTestFunctionWrapper(const char* const): Constructor
with function nameTemplate class for automatic test registration.
template <typename... Arguments> TestProxy(const std::string&, Arguments&&...):
ConstructorBase class for test outputs.
virtual void beginTestSuite(const std::string&) = 0:
Called when a test suite beginsvirtual void addTest(const std::string&, const std::string&, const TestResult&) = 0:
Add a test resultvirtual void endTestSuite(const TestResult&) = 0:
Called when a test suite endsOutput to standard streams.
StdStreamTestOutput(const std::string&):
Constructor with file nameStdStreamTestOutput(std::ostream&, const bool = true):
Constructor with stream and color optionStdStreamTestOutput(const std::shared_ptr<std::ostream>&, const bool = false):
Constructor with shared streamOutput to XML file in JUnit format.
XMLTestOutput(const std::string&): Constructor with
file nameAggregate multiple outputs.
void addTestOutput(const TestOutputPtr&): Add an
outputStructure describing the result of a test or test suite.
TestResult(): Default constructorTestResult(const bool): Constructor with success
statusTestResult(const bool, const std::string_view, const std::string_view = {}):
Constructor with detailsbool success() const: Check if the test was
successfulconst std::string& getDescription() const: Get the
test descriptionconst std::string& getFailureDescription() const:
Get the failure descriptionvoid append(const TestResult&): Append a
resultvoid setTestDuration(const double): Set the test
durationdouble duration() const: Get the test durationTFEL_TESTS_GENERATE_PROXY(X, Y): Generate a proxy for
class X in suite YTFEL_TESTS_GENERATE_PROXY2(X, X2, Y): Generate a proxy
with custom nameTFEL_TESTS_GENERATE_PROXY3(X, X2, Y, Y2): Generate a
proxy with constructor argumentTFEL_TESTS_FUNCTION(X, G, Y): Generate a test function
with group G in suite YTFEL_TESTS_ASSERT(X): Assert condition XTFEL_TESTS_STATIC_ASSERT(X): Static assertionTFEL_TESTS_CHECK_THROW(X, Y): Check exceptionTFEL_TESTS_CALLMETHOD(X): Call method and register
resultTFEL_TESTS_CHECK_EQUAL(X, Y): Check equalityTFEL_TESTS_CHECK_GT(X, Y): Check greater thanTFEL_TESTS_CHECK_GE(X, Y): Check greater or equalTFEL_TESTS_CHECK_LT(X, Y): Check less thanTFEL_TESTS_CHECK_LE(X, Y): Check less or equalThe 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.
The TFEL source code includes several examples of unit tests in the
tests/Tests/ directory:
testcase.cxx: Example of using the TestCase classtestsuite.cxx: Example of using the TestSuite
classtestmanager.cxx: Example of using the TestManager
classtestproxy.cxx: Example of using test proxiesmultipletestoutputs.cxx: Example of using multiple
outputsstdstreamtestoutput.cxx: Example of using standard
stream outputTest Isolation: Each test should be independent and not rely on the state of other tests.
Descriptive Names: Use descriptive names for tests and test suites to make it clear what is being tested.
Single Responsibility: Each test should verify a single behavior or property.
Use Assertions Wisely: Use the appropriate assertion macro for the condition being tested.
Test Coverage: Aim for comprehensive test coverage of your code, including edge cases and error conditions.
Performance: For performance-critical code, consider the overhead of test execution and use appropriate test strategies.
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.