DEV Community

Cover image for fcfTest - Unit Test Library - All in one, yet lightweight
VladimirM
VladimirM

Posted on

fcfTest - Unit Test Library - All in one, yet lightweight

Hello. I finally finished writing the fcfTest unit testing library: https://github.com/fcf-framework/fcfTest

Until now, the library consisted of just a single macro; however, it now fully implements all the necessary functionality.

Its primary distinguishing feature lies in the use of a single assertion macro for all tests - a capability made possible by the fact that the library is written in C++. Furthermore, integrating it requires nothing more than a single header file.

The library supports independent command-line processing, allows for specifying the test execution order, and most importantly - supports a hierarchical test structure organized into three levels: Section -> Group -> Test. It can also be compiled as a standalone DLL.

Additionally, the library includes a simple logger (fcf::NTest::Duration::err() … fcf::NTest::log() … fcf::NTest::trc()) and a class for measuring execution duration (fcf::NTest::Duration).

The main FCF_TEST macro - suitable for all scenarios:

It allows for writing complex checks that include variable state monitoring.

 FCF_TEST(a==15, a);
Enter fullscreen mode Exit fullscreen mode

And in the terminal, you will see:

Test error: a == 15  [FILE: DIR_PATH/main.cpp:LINE]
 Values:
   a: 1
Enter fullscreen mode Exit fullscreen mode

The first parameter is a computed verification expression, while all other parameters are observed variables.

Example

Next follows the main example from this library; the accompanying comment explains the core mechanics of its operation.

#include <vector>
#include <cmath>

// It is necessary to define the `FCF_TEST_IMPLEMENTATION` macro so that the 
// implementations are exposed when the header file is included. 
// If the `fcfTest/test.hpp` file is included multiple times within a project, 
// this macro should be defined in only one `.cpp` file.
//
// When working with DLLs, you must define both the `FCF_TEST_IMPLEMENTATION` 
// and `FCF_TEST_EXPORT` macros within the main library that exports 
// the functions; conversely, in libraries that import these functions, 
// you need to define only the `FCF_TEST_IMPORT` macro.
#define FCF_TEST_IMPLEMENTATION
#include <fcfTest/test.hpp>


// --- Test Declarations ---
FCF_TEST_DECLARE("Math" /*PART NAME*/, 
                 "BasicArithmetic" /*GROUP NAME*/,
                 "Addition" /*TEST NAME*/) {
  // We create an object to measure execution duration
  // over 10,000 iterations.
  fcf::NTest::Duration bench(10000);

  // Set the starting time point for measuring execution time.
  bench.begin();
  for(size_t i = 0; i < bench.iterations(); ++i) {
    int a = 2;
    int b = 3;
    // Performing a check of the unit test execution.
    FCF_TEST(a + b == 5, a, b);
  }
  // We set the final time point for measuring execution time.
  bench.end();

  // Outputting the execution time measurement result at the 'info' logging level.
  fcf::NTest::inf() << "  Itertion count: " << bench.iterations() << std::endl;
  fcf::NTest::inf() << "  Total: " << bench.totalDuration().count() << " ns" << std::endl;
  fcf::NTest::inf() << "  Avg: " << bench.duration().count() << " ns" << std::endl;
}

FCF_TEST_DECLARE("Math" /*PART NAME*/, 
                 "BasicArithmetic" /*GROUP NAME*/, 
                 "Subtraction" /*TEST NAME*/) {
  // We create an object to measure execution duration
  // over 10,000 iterations.
  fcf::NTest::Duration bench(10000);

  // We perform the task 10,000 times.
  bench([](){
    int a = 10;
    int b = 4;
    // Performing a check of the unit test execution.
    FCF_TEST(a - b == 6, a, b);
  });

  // Outputting the execution time measurement result at the 'info' logging level.
  fcf::NTest::inf() << "  Itertion count: " << bench.iterations() << std::endl;
  fcf::NTest::inf() << "  Total: " << bench.totalDuration().count() << " ns" << std::endl;
  fcf::NTest::inf() << "  Avg: " << bench.duration().count() << " ns" << std::endl;
}

FCF_TEST_DECLARE("Vector" /*PART NAME*/, 
                 "SizeCheck" /*GROUP NAME*/, 
                 "EmptyVector" /*TEST NAME*/) {
    std::vector<int> v;
    FCF_TEST(v.size() == 0, v.size());
}

// --- Order Registration ---
// Run Math tests before Vector tests
FCF_TEST_PART_ORDER("Math", 1);
FCF_TEST_PART_ORDER("Vector", 2);

// Run "BasicArithmetic" group first within Math part
FCF_TEST_GROUP_ORDER("BasicArithmetic", 1);

// Run Addition test first
FCF_TEST_TEST_ORDER("Addition", 1);

int main(int a_argc, char* a_argv[]) {
  // Use CRM_RUN for standard execution
  bool error;
  fcf::NTest::cmdRun(a_argc, (const char**)a_argv, fcf::NTest::CRM_RUN, &error);
  return error ? 1 : 0;
}
Enter fullscreen mode Exit fullscreen mode

If you launch this application, you will see the following report:

Performing the test: "Math" -> "BasicArithmetic" -> "Subtraction" ...
Performing the test: "Math" -> "BasicArithmetic" -> "Addition" ...
Performing the test: "Vector" -> "SizeCheck" -> "EmptyVector" ...

All tests were completed. Number of tests: 3
Enter fullscreen mode Exit fullscreen mode

However, if you specify the logging level parameter beforehand:

$ ./full-example --test-log-level inf
Enter fullscreen mode Exit fullscreen mode

Then the result will be supplemented with information regarding execution speed.

Performing the test: "Math" -> "BasicArithmetic" -> "Subtraction" ...
 Itertion count: 10000
 Total: 65652 ns
 Avg: 6 ns
Performing the test: "Math" -> "BasicArithmetic" -> "Addition" ...
 Itertion count: 10000
 Total: 77750 ns
 Avg: 7 ns
Performing the test: "Vector" -> "SizeCheck" -> "EmptyVector" ...

All tests were completed. Number of tests: 3
Enter fullscreen mode Exit fullscreen mode

Help on the full set of commands can be obtained by passing the command-line parameter --test-help

$ ./full-example --test-help
Enter fullscreen mode Exit fullscreen mode
Test options:
 --test-run  - Run tests
 --test-list - Displays a list of all tests
 --test-part  PART_NAME - Run only tests from the part. The parameter can be used multiple times
 --test-group GROUP_NAME - Run only tests from the group. The parameter can be used multiple times
 --test-test  TEST_NAME - Run only this test. The parameter can be used multiple times
 --test-log-level LEVEL - Logging level (VALUES: off, ftl, err, wrn, att, log, inf, dbg, trc)
 --test-help  - Help message
Enter fullscreen mode Exit fullscreen mode

Top comments (0)