An open API service indexing awesome lists of open source software.

https://github.com/coderarjob/yukti

Single header Parameterised testing and mocking library C/C++
https://github.com/coderarjob/yukti

c gcc mocking single-header-library testing unittest unittesting

Last synced: 4 months ago
JSON representation

Single header Parameterised testing and mocking library C/C++

Awesome Lists containing this project

README

          




## About

Yukti is a single header testing library for C/C++ projects. It has no 3rd party dependencies and is
fully contained within a single header file.

It provides most of the features available in other popular testing library but in a small single
header library.

* [Screenshots](#screenshots)
* [Examples](#examples)
* [Documentation](#documentation)
* [Test macros](#test-macros)
* [Assertion macros](#assertion-macros)
* [Interaction/behaviour validation macros](#interaction-or-behaviour-validation-macros)
* [Mock or Fake function creation macros](#mock-or-fake-function-declaration)
* [Breaking changes - Upgrade guide](#upgrade-guide)
* [Testing yukti.h](#running-tests)
* [Versioning](#versioning)
* [Feedback](#feedback)

## Goals

- [X] **State testing**
- [X] State/scalar value assertion macros
- [X] Continuous data like array, string assertion macros
- [X] YT_EQ_DOUBLE macro for approx matching of values
- [X] **Parameterised testing macros**
- [X] **Faking/Mocking external functions**
- [X] Macros to fake external functions
- [X] Behaviour modification of faked functions using custom Handler functions
- [X] **Interaction testing**
- [X] Assert if an external function was called with expected arguments
- [X] Assert if an external function was called with optional arguments
- [X] Assert if an external function was never called
- [ ] Assert if an external function was called with floating point or structures passed by-value in parameters.
- [X] **Reporting**
- [X] Report line numbers and source file of failed expectations
- [X] Report list of all the tests which failed
- [X] Tests executables exit with non-zero code if any of its tests fails
- [X] Time taken to run each test

## Screenshots

| Case: Some test are failing. Shows source file location and summary lists the failing tests | Case: All test are passing. Shows elapsed time for each test. |
|---------------------------------------------------------------------------------------------|--------------------------------------------------------------------------|
| [![Failure](./docs/images/failure_thumb.png)](./docs/images/failure.png) | [![Passing](./docs/images/passing_thumb.png)](./docs/images/passing.png) |

## Examples

Different examples are placed in the [example](./example) folder.

## Documentation

### Test macros

`YT_TEST` & `YT_TESTP` macros are used to create a non-parameterised test and a parameterised
test respectively. Tests functions are identified by their name, that is the 2nd argument in these
macros. These tests functions need to be called in the `main()` function explicitly. Arguments for
parameterised tests are given when calling them in the `main()`, non-parameterised tests do not take
any argument.

Each test function must end with `YT_END()` macro. If omitted will result in compilation errors.

| Macro name | Purpose |
|--------------------------------|-------------------------------------------------------------------------------------------------------------------------|
| `YT_TEST(g, tn)` | Creates a new non-parameterised test `tn` under group `g`. Group name is not yet used for any purpose. |
| `YT_TESTP(g, tn, t1, t2, ...)` | Creates a new parameterised test `tn` under group `g`. `t1`, `t2` etc are types of parameters to be passed to the test. |
| `YT_END()` | Ends a test function. It must exist at the very end of each test function. |
| `YT_INIT()` | Initializes yukti test internals. Must be called from `main` before test functions are run. |
| `YT_RETURN_WITH_REPORT()` | Returns from `main` after printing a summary. |

See [Parameterised test](./example/add_parameterised_test.c) example

### Assertion macros

Assertions macros check state expectations from an SUT (System Under Test). These are several of
these macros.

| Macro name | Validates |
|------------------------------|-----------------------------------------------------------|
| `YT_EQ_SCALAR(a, b)` | a == b |
| `YT_NEQ_SCALAR(a, b)` | a != b |
| `YT_GEQ_SCALAR(a, b)` | a >= b |
| `YT_LEQ_SCALAR(a, b)` | a <= b |
| `YT_GRT_SCALAR(a, b)` | a > b |
| `YT_LES_SCALAR(a, b)` | a < b |
| `YT_EQ_MEM(a, b, sz)` | First `sz` bytes in buffers `a` & `b` are equal |
| `YT_NEQ_MEM(a, b, sz)` | First `sz` bytes in buffers `a` & `b` are not equal |
| `YT_EQ_STRING(a, b)` | String `a` and `b` are equal |
| `YT_NEQ_STRING(a, b)` | String `a` and `b` are not equal |
| `YT_EQ_DOUBLE_ABS(a, b, e)` | Approx match a == b. Passes if `mod(a - b) <= e` |
| `YT_NEQ_DOUBLE_ABS(a, b, e)` | Approx match a != b. Passes if `mod(a - b) > e` |
| `YT_EQ_DOUBLE_REL(a, b, e)` | Approx match a == b. Passes if `mod(a - b) <= max(a,b)*e` |
| `YT_NEQ_DOUBLE_REL(a, b, e)` | Approx match a != b. Passes if `mod(a - b) > max(a,b)*e` |

`YT_EQ_DOUBLE_REL(a, b, e)` passes if `abs(a - b)` is `<=` than `e`% of the larger floating point
number. For example, `YT_EQ_DOUBLE_REL(1.1234, 1.12, 0.01)` passes because their difference of
0.0034 is < 1% of 1.1234.

See [Basic tests](./example/basic_tests.c) example

### Interaction or behaviour validation macros

More complex files work in conjunction with mocked functions. They help in validating interaction
between SUT and external functions. They help in determining if these external functions were called
in what order and which what parameters.

| Macro name | Validates |
|---------------------------------------------------|-------------------------------------------------------------------------------------------|
| `YT_MUST_CALL_IN_ORDER(f, ...)` | Function `f` is called with the given arguments at least once in an particular order |
| `YT_MUST_CALL_IN_ORDER_ATLEAST_TIMES(n, f, ...)` | Function `f` is called with the given arguments at least `n` times in an particular order |
| `YT_MUST_CALL_ANY_ORDER(f, ...)` | Function `f` is called with the given arguments at least once in no particular order |
| `YT_MUST_CALL_ANY_ORDER_ATLEAST_TIMES(n, f, ...)` | Function `f` is called with the given arguments at least `n` times in no particular order |
| `YT_MUST_NEVER_CALL(f, ...)` | Function `f` with the given arguments is never called |
| `YT_IN_SEQUENCE(n)` | Repeats expectations `n` number of times. Used to put expectations for a loop. |

See these examples
* [printer_fail](./example/sensor_test.c) example
* [printer_success](./example/sensor_test.c) example

### Mock or Fake function declaration

When unittesting it might be required to provide a fake definitions of external functions. This is
where these macros come in. These fake functions also enable the above mentioned interaction
validations and one can modify the behaviour of these fake functions in various ways.

| Macro name | Validates |
|----------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------|
| `YT_DECLARE_FUNC(rt, f, ...)` | Declaration for fake function `f` which takes any number of arguments returns some non void type `rt` |
| `YT_DECLARE_FUNC_VOID(f, ...)` | Declaration for fake function `f` which takes any number of arguments returns void |
| `YT_DEFINE_FUNC(rt, f, ...)` | Definition for fake function `f` previously declared using `YT_DECLARE_FUNC`. |
| `YT_DEFINE_FUNC_VOID(f, ...)` | Definition for fake function `f` previously declared using `YT_DECLARE_FUNC_VOID`. |
| `YT_DEFINE_FUNC_FALLBACK(rt, f, ...)` | Definition for fake function `f` previously declared using `YT_DECLARE_FUNC`. `MUST_CALL*`/`NEVER_CALL*` macros cannot be used on them. |
| `YT_DEFINE_FUNC_VOID_FALLBACK(f, ...)` | Definition for fake function `f` previously declared using `YT_DECLARE_FUNC_VOID`. `MUST_CALL*`/`NEVER_CALL*` macros cannot be used on them. |
| `YT_RESET_MOCK(f)` | Resets internal state of a mock/fake function previously defined using `YT_DEFINE_FUNC*`. |

`YT_DEFINE_FUNC_FALLBACK` and `YT_DEFINE_FUNC_VOID_FALLBACK` should be used to define functions with
one or more non integer arguments (floating point or `structs` passed by-value etc) as such
arguments do not work with `MUST_CALL*`/`NEVER_CALL*` macros at this time. Expectations set on them
using `MUST_CALL*` can never be met and those with `NEVER_CALL*` will always pass - thus giving
wrong impression of what's actually happening. But if used correctly, there will be no way to form
an expectation because `YT_V` construct will fail.

See [Mocking and faking](./example/sensor_test.c) example

## Upgrade guide

* Old `YT_EQ_DOUBLE` was equivalent to `YT_EQ_DOUBLE_ABS`, so the former can be replaced with the
later.
* When comparing large floating point numbers, the newer `YT_EQ_DOUBLE_REL` works better.

## Running tests

In order to test yukti.h run `tests/run_all_tests.sht`. This runs integration tests & examples.

## Versioning

It uses semantic versioning. See [https://semver.org/](https://semver.org).

## Feedback

Open a GitHub issue or drop a email at arjobmukherjee@gmail.com. I would love to hear your
suggestions and feedbacks.