https://github.com/fcf-framework/fcftest
Provides the FCF_TEST macro to perform the check in unit tests
https://github.com/fcf-framework/fcftest
cpp unittest
Last synced: 26 days ago
JSON representation
Provides the FCF_TEST macro to perform the check in unit tests
- Host: GitHub
- URL: https://github.com/fcf-framework/fcftest
- Owner: fcf-framework
- License: mit
- Created: 2025-01-21T03:28:01.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2025-01-21T03:56:16.000Z (over 1 year ago)
- Last Synced: 2025-03-15T08:25:39.471Z (over 1 year ago)
- Topics: cpp, unittest
- Language: C++
- Homepage:
- Size: 5.86 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# fcfTest
**fcfTest** is a lightweight, header-only unit testing framework for C++. It provides a simple macro, FCF_TEST, which implements all possible checks and displays the current values โโof variables. The framework also includes test registration, command-line execution, a built-in simple logger, and tools for measuring execution time.
The library is distributed as a single header file: `fcfTest/test.hpp`.
To use the library, you must define the `FCF_TEST_IMPLEMENTATION` macro before including the header file in your application's main cpp file to avoid errors due to multiple definitions.
### Liniks:
(New section of the site dedicated to the description of this library.)
Official page with documentation: https://fcf-framework.is-a-fullstack.dev/cpp-libraries/fcfTest
Usage article on dev.to: https://dev.to/vladimirm/fcftest-unit-test-library-3cgg
### Friendly:
If something doesn't work for you, please report it quickly in Issues and we'll fix it!
## Quick Start Example
```c++
#define FCF_TEST_IMPLEMENTATION
#include
#include
// Declare a test case
FCF_TEST_DECLARE("MyLibraryPartName", "ExamplesGroupName", "VectorSizeTestName"){
std::vector vec;
vec.push_back("test");
// Perform an assertion. If false, throws std::runtime_error with details.
FCF_TEST(vec.size() == 2, vec.size());
}
int main(int a_argc, char* a_argv[]){
// Run the test suite via command line interface
bool error;
fcf::NTest::cmdRun(a_argc, a_argv, fcf::NTest::CRM_RUN, &error);
return error ? 1 : 0;
}
```
**Output:**
The program will execute the registered tests. If an assertion fails, it prints a descriptive error including file, line number and variable values. If successful, it prints a summary count of completed tests.
```stdout
Performing the test: "MyLibraryPartName" -> "ExamplesGroupName" -> "VectorSizeTestName" ...
Test error: vec.size() == 2 [FILE: DIR_PATH/main.cpp:9]
Values:
vec.size(): 1
```
## Core Macros
### `FCF_TEST_IMPLEMENTATION`
This macro is used to enable the implementation section within the header file `fcfTest/test.hpp`.
- **Usage**: Must be defined (`#define FCF_TEST_IMPLEMENTATION`) before including the header if you want to use the library's functionality (e.g., running tests, using the logger).
- **Purpose**: Prevents multiple definition errors by ensuring that global variables and function definitions are only generated once per compilation unit. When omitted, the header provides only declarations.
### `FCF_TEST_EXPORT`
This macro is used to export symbols from the test library when building it as a DLL or shared library.
- **Usage**: Defined by the main FCF library macros (`FCF_EXPORT`) if applicable, otherwise empty.
- **Purpose**: Ensures consistent symbol visibility for functions and variables defined in `fcfTest` when compiled as a DLL.
### `FCF_TEST_IMPORT`
This macro is used to import symbols from the test library when using it as a client application linking against a shared library.
- **Usage**: Defined by the main FCF library macros (`FCF_IMPORT`) if applicable, otherwise empty.
- **Purpose**: Ensures consistent symbol visibility for functions and variables defined in external shared libraries when they are imported into the client process without multiple definition errors.
### `FCF_TEST`
The primary macro for performing checks in unit tests.
- **Behavior**: Evaluates `(a_expression)`. If the result is false (non-zero), it throws a `std::runtime_error`.
- **Error Message**: The exception message includes:
- The failing expression (`#exp`).
- The file name and line number where the macro was called.
- Values of all additional arguments provided in `a_observedVariables`.
**Example:**
```c++
int x = 5;
FCF_TEST(x == 4, x);
// Throws error: Test error: x == 4 [FILE: main.cpp:10]
// Values:
// x: 5
```
## Testing Organization Macros
Tests are organized hierarchically into Parts, Groups, and Tests. This allows for filtering execution based on these levels.
### `FCF_TEST_DECLARE(am_part, am_group, am_test)`
Declares a new test case.
- **Parameters**:
- `am_part`: The name of the part (logical grouping level).
- `am_group`: The name of the group (sub-grouping level).
- `am_test`: The unique identifier of the test function.
- **Usage**: This macro generates a static anonymous class that registers the test with the global storage upon instantiation.
**Example:**
```c++
FCF_TEST_DECLARE("Network", "HTTP", "GetRequestTest") {
// Test implementation
}
FCF_TEST_DECLARE("Network", "HTTP", "PostRequestTest") {
// Test implementation
}
```
### `FCF_TEST_PART_ORDER(am_part, am_order)`
Registers the execution order for a specific part.
- **Parameters**:
- `am_part`: Name of the part.
- `am_order`: Integer priority (lower values run first).
**Example:**
```c++
FCF_TEST_PART_ORDER("CriticalTests", 1); // Run critical tests first
FCF_TEST_PART_ORDER("GeneralTests", 2);
```
### `FCF_TEST_GROUP_ORDER(am_group, am_order)`
Registers the execution order for a specific group within a part.
- **Parameters**:
- `am_group`: Name of the group.
- `am_order`: Integer priority.
**Example:**
```c++
FCF_TEST_GROUP_ORDER("Integration", 1);
FCF_TEST_GROUP_ORDER("Unit", 2);
```
### `FCF_TEST_TEST_ORDER(am_test, am_order)`
Registers the execution order for a specific test within a group.
- **Parameters**:
- `am_test`: Name of the test.
- `am_order`: Integer priority.
**Example:**
```c++
FCF_TEST_TEST_ORDER("SmokeTest", 1);
FCF_TEST_TEST_ORDER("LoadTest", 2);
```
## Global Logging Functions
The library provides global shortcuts to access streams based on log levels. These are defined in the `fcf::NTest` namespace and also available globally if included directly or via specific exports.
### `ftl()`, `err()`, `wrn()`, `att()`, `log()`, `inf()`, `dbg()`, `trc()`
These functions return references to output streams (`std::ostream&`) corresponding to specific log levels:
- `ftl()`: Fatal level.
- `err()`: Error level.
- `wrn()`: Warning level.
- `att()`: Attention level.
- `log()`: Log level (default).
- `inf()`: Information level.
- `dbg()`: Debug level.
- `trc()`: Trace level.
If the global logger's level is set lower than the requested level, these return a reference to an empty stream buffer (no output).
**Example:**
```c++
fcf::NTest::ftl() << "This will only appear if log level is FTL or higher" << std::endl;
fcf::NTest::dbg() << "This appears only in debug mode" << std::endl;
```
## Benchmarking: `Duration` Class
The `fcf::NTest::Duration` class provides a simple interface for measuring the execution time of code blocks. It uses `std::chrono::high_resolution_clock`.
### Members and Methods
- **Constructor**: `Duration(unsigned long long a_iterations)`
- **Parameters**:
- `a_iterations`: The number of times the enclosed functor will be executed.
- **Default Constructor**: `Duration()`
- Sets the number of iterations to 1.
- **Methods**:
- `unsigned long long iterationCount()`: Returns the number of iterations set for this duration.
- `void begin()`: Records the start time for timing.
- `void end()`: Records the end time for timing.
- `std::chrono::nanoseconds totalDuration()`: Returns the total duration of all iterations in nanoseconds.
- `std::chrono::nanoseconds duration()`: Returns the average duration of a single iteration in nanoseconds.
- `void operator()(TFunctor&& a_functor)`: Executes a functor multiple times and measures the total duration.
- **Parameters**:
- `a_functor`: The callable object to execute.
**Example:**
```c++
// Measure sorting 1000 times
fcf::NTest::Duration bench(1000);
bench([](){
std::vector v = {5, 2, 9};
std::sort(v.begin(), v.end());
});
std::cout << "Total: " << bench.totalDuration().count() << " ns\n";
std::cout << "Avg: " << bench.duration().count() << " ns\n";
```
## Command Line Interface
The test runner supports filtering tests by Part, Group, or specific Test name, and setting the global log level. It uses a command-line argument parser implemented in `cmdRun`.
### The `Options` Structure
This structure holds the configuration for running tests. It is populated automatically by the command line parser but can be used manually.
```c++
struct Options {
std::vector parts; ///< List of part names to run (empty means all).
std::vector groups; ///< List of group names to run (empty means all).
std::vector tests; ///< List of specific test names to run (empty means all).
std::vector ignoreParts; ///< List of ignore part names.
std::vector ignoreGroups; ///< List of ignore group names to run.
std::vector ignoreTests; ///< List of ignore specific test names to run.
ELogLevel logLevel; ///< Desired logging level.
};
```
### The `cmdRun` Function
The central function for executing the test suite. It parses command-line arguments and determines the action.
**`ECmdMode cmdRun(Options& a_dstOptions, int a_argc, const char* const* a_argv, ECmdRunMode a_runMode, bool* a_errorPtr = 0)`**
Parses command line arguments and executes the appropriate action.
- **Parameters**:
- `a_dstOptions`: Reference to the `Options` structure to populate with parsed arguments.
- `a_argc`: Number of command line arguments.
- `a_argv`: Array of argument strings.
- `a_runMode`: Current mode of execution (`CRM_PARSE`, `CRM_EXECUTE`, or `CRM_RUN`).
- `a_errorPtr`: A pointer to a variable that receives information about a test error. If an error occurs, the value is set to true. If a null pointer is passed, the function throws an exception.
- **Returns**: `ECmdMode` returns the selected mode based on the parameters of the command line a_argv.
#### `ECmdMode` Enum
Defines the mode of operation for the command line parser:
- **`CM_NONE`**: No specific mode was set.
- **`CM_RUN`**: The `--test-run` flag was detected.
- **`CM_LIST`**: The `--test-list` flag was detected.
- **`CM_HELP`**: The `--test-help` flag was detected.
#### `ECmdRunMode` Enum
This enum dictates how `cmdRun` behaves during parsing:
- **`CRM_PARSE`**: Parses arguments but **does not** execute tests or show help/list. Returns the determined mode (e.g., `CM_HELP`, `CM_LIST`) so the caller can decide what to do next.
- **`CRM_EXECUTE`**: Parses arguments. If the flag `--test-run` was provided, it executes tests. If `--test-help` or `--test-list` were provided, it displays that information immediately and returns.
- **`CRM_RUN`**: Parses arguments and **automatically executes** tests unless `--test-help` or `--test-list` was explicitly requested.
#### Usage Examples
```c++
int main(int a_argc, char* a_argv[]) {
// Standard execution: Parse and run.
bool error = false;
fcf::NTest::cmdRun(a_argc, a_argv, fcf::NTest::CRM_RUN, &error);
return error ? 1 : 0;
}
```
```c++
int main(int a_argc, char* a_argv[]) {
// Standard execution: Parse and run by request.
bool error = false;
int mode = fcf::NTest::cmdRun(a_argc, a_argv, fcf::NTest::CRM_EXECUTE, &error);
if (error) {
// An error occurred while running a test.
return 1;
}
if (mode != fcf::NTest::CM_NONE){
// The --test-run | --test-help | ---test-list flags were passed at startup.
// And the function performed all the actions
return 0;
}
.... your application code ...
return 0;
}
```
```c++
int main(int a_argc, char* a_argv[]) {
// Or custom menu mode: Just parse to see what was asked
fcf::NTest::Options options;
int mode = fcf::NTest::cmdRun(options, a_argc, a_argv, fcf::NTest::CRM_PARSE);
if (mode == fcf::NTest::CM_HELP) {
fcf::NTest::cmdHelp();
return 0;
} else if (mode == fcf::NTest::CM_LIST) {
fcf::NTest::cmdList();
return 0;
} else if (mode == fcf::NTest::CM_RUN) {
bool error = false;
fcf::NTest::run(options, &error);
return error ? 1 : 0;
}
// ... default execution
}
```
### Command Line Flags
- `--test-run`: Forces execution of selected tests.
- `--test-list`: Prints a list of all available tests in the hierarchy (Part -> Group -> Test).
- `--test-help`: Displays usage information and available flags.
- `--test-log-level LEVEL`: Sets the global logging verbosity (e.g., `dbg`, `log`, `err`).
- `--test-part PART_NAME`: Filters execution to only tests belonging to the specified part. Can be used multiple times.
- `--test-group GROUP_NAME`: Filters execution to only tests belonging to the specified group. Can be used multiple times.
- `--test-test TEST_NAME`: Filters execution to run only the specific test named. Can be used multiple times.
- `--test-ignore-part PART_NAME`: Exclude tests in the specified part(s). Can be used multiple times.
- `--test-ignore-group GROUP_NAME`: Exclude tests in the specified group(s). Can be used multiple times.
- `--test-ignore-test TEST_NAME`: Exclude the specified test(s). Can be used multiple times.
**Example Command:**
```bash
./my_tests --test-run --test-part Network --test-group HTTP --test-log-level dbg
```
### Helper Functions
- **`cmdHelp()`**: Displays help information and available command-line flags.
- **`cmdList()`**: Displays a list of all registered tests with their hierarchy structure.
- **`run(const Options& a_options, bool* a_errorPtr = 0)`**: Executes the selected tests based on an `Options` object.
- **Parameters**:
- `a_options`: Configuration options specifying which tests to run and logging level.
- `a_errorPtr`: A pointer to a variable receiving error information. If an error occurs, the value is set to true. If a null pointer is passed, the function throws an exception.
- **Returns**: void.
## Full Usage Example
```c++
#define FCF_TEST_IMPLEMENTATION
#include
#include
#include
// --- Test Declarations ---
FCF_TEST_DECLARE("Math", "BasicArithmetic", "Addition") {
int a = 2;
int b = 3;
FCF_TEST(a + b == 5, a, b);
}
FCF_TEST_DECLARE("Math", "BasicArithmetic", "Subtraction") {
int a = 10;
int b = 4;
FCF_TEST(a - b == 6, a, b);
}
FCF_TEST_DECLARE("Vector", "SizeCheck", "EmptyVector") {
std::vector 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, a_argv, fcf::NTest::CRM_RUN, &error);
return error ? 1 : 0;
}
```