Skip to content

Manu343726/unittest

Repository files navigation

unittest

C++ unit testing and mocking made easy

What?

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:

  1. 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.

  2. Zero macros: Every line of user code is just perfectly normal C++ code.

  3. 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.

  4. 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.

  5. 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)

Requirements

Running the examples

$ 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

Roadmap

  • Mocking full classes with one patch call
  • Multiple patch calls in one test
  • Namespaces with free functions as test cases
  • Mocking of private methods
  • patch("method", return_value=foo) syntax

Documentation

Yeah, 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.

License

Everything is released under MIT license.

Production ready

Yeah, sure.

About

C++ unit testing and mocking made easy

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 3

  •  
  •  
  •