C++ unit testing and mocking made easy
C++ unit testing and mocking made easy
unittest
is a proof of concept C++ unit testing framework inspired by
Python's unittest
package.
unitest
is designed with the following goals in mind:
Zero plumbing code: No setup functions, no main, no test registration. The library uses static reflection to figure out where are the tests declared, what is and what's not test case code, etc.
Zero macros: Every line of user code is just perfectly normal C++ code.
Non intrusive arbitrary function and class mocking: No need to use
virtual function interfaces, mock classes, and dependency injection,
which couple your library design with the way the mocking framework
works. unittest
uses monkey-patching through
elfspy
so mocking is as
transparent as possible.
Expressive test failure output: unittest
not only mimics Python's
unittest
API but also its console output, including descriptive
assertion error details, call arguments, location of the failed
assertion in the code, etc.
Easy integration: Just pull the library from conan
and use the provided add_unittest()
CMake function to add a unittest
executable to your project:
add_unittest(
NAME
test_example
TESTS
test_example.hpp
DEPENDENCIES
libexample
)
Here is a full example of a unit test case with unittest
:
#include <unittest/unittest.hpp>
#include <libexample/example.hpp>
#include <libexample/example.hpp.tinyrefl>
namespace test_example
{
struct ExampleTestCase : public unittest::TestCase
{
[[unittest::patch("mynamespace::ExampleClass::identity(int) const", return_value=42)]]
void test_another_one_bites_the_dust(unittest::MethodSpy<int(int)>& identity)
{
mynamespace::ExampleClass object;
self.assertEqual(object.methodThatCallsIdentity(), 42);
identity.assert_called_once_with(43);
}
};
}
test_another_one_bites_the_dust (test_example::ExampleTestCase) ... FAIL
=======================================================================
FAIL: test_another_one_bites_the_dust (test_example::ExampleTestCase)
-----------------------------------------------------------------------
Stack trace (most recent call last):
#0 Source "/home/manu343726/Documentos/unittest/examples/test_example.hpp", line 16, in test_another_one_b
ites_the_dust
13: mynamespace::ExampleClass object;
14:
15: self.assertEqual(object.methodThatCallsIdentity(), 42);
> 16: identity.assert_called_once_with(43);
17: }
18: };
AssertionError: Expected call: mynamespace::ExampleClass::identity(43)
Actual call: mynamespace::ExampleClass::identity(42)
-----------------------------------------------------------------------
Ran 1 tests in 0.002s
FAILED (failures=1)
Who said C++ could not be as expressive as Python?
import unittest, unittest.mock
import mynamespace
class ExampleTestCase(unittest.TestCase):
@unittest.mock.patch('mynamespace.ExampleClass.identity', return_value=42)
def test_another_one_bites_the_dust(self, identity):
object = mynamespace.ExampleClass()
self.assertEqual(object.methodThatCallsIdentity(), 42)
identity.assert_called_once_with(43)
test_another_one_bites_the_dust (test_example.ExampleTestCase) ... FAIL
======================================================================
FAIL: test_another_one_bites_the_dust (test_example.ExampleTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/lib/python3.7/unittest/mock.py", line 1195, in patched
return func(*args, **keywargs)
File "/home/manu343726/Documentos/unittest/examples/python_equivalent/test_example.py", line 11, in test_another_one_bites_
the_dust
identity.assert_called_once_with(43)
File "/usr/lib/python3.7/unittest/mock.py", line 831, in assert_called_once_with
return self.assert_called_with(*args, **kwargs)
File "/usr/lib/python3.7/unittest/mock.py", line 820, in assert_called_with
raise AssertionError(_error_message()) from cause
AssertionError: Expected call: identity(43)
Actual call: identity(42)
----------------------------------------------------------------------
Ran 1 test in 0.002s
FAILED (failures=1)
-O0 -g3
).$ git clone https://github.com/Manu343726/unittest && cd unittest
$ mkdir build && cd build
$ conan install .. --build=missing
$ cmake .. -DCMAKE_BUILD_TYPE=Debug
$ make
$ ctest . -V
patch("method", return_value=foo)
syntaxYeah, sorry, this is a work in progress PoC. The code is given as ugly as it looks without any comment or docstring that could make things clear.
Everything is released under MIT license.
Yeah, sure.