Skip to content

UnitTests

ZahlGraf edited this page Oct 20, 2015 · 11 revisions

IrrIMGUI Unit Test Environment

Index

Introduction

With Version 0.3 the IrrIMGUI library introduced Unit-Test support. On the one hand this should help to keep the IrrIMGUI library stable, also when further features are released and on the other hand it should help developers to test projects using IrrIMGUI.

Basically for almost every public function and class there is now a Unit Test in IrrIMGUI available. All these tests are implemented in a way, that they just call public functions and check the expected behaviour. This has the advantage, that a developer can change and refactor the implementation behind this public interface without breaking the unit test, as long as the behaviour, visible from outside, is the same. Nevertheless for very complex classes this comes along with the disadvantage, that the Unit Tests cannot check every aspect of the internal functionality.

For example the Unit Tests of the IIMGUIHandle class are not checking, if it really passes GUI data for drawing to the graphic hardware, because this is something you cannot see easily from outside. However it checks, if the IMGUI Handle updates the GUI variables like time, mouse and keyboard states. It is somehow a trade-off between complexity, software security and rigidity.

Version 0.3 and above also helps developers to test their applications using IrrIMGUI as library. You can use dependency injection to inject your own Handle class as Stub- or Mock-Implementation for test purposes. This should work with every Mocking- and Unit-Test framework. But when you are also using CppUTest, IrrIMGUI delivers a IMGUI-Handle Mock for you, that can be used out of the box.

Furthermore the IrrIMGUI library has some tools available to help you testing: For example a very simple and easy to use memory leak detection (Visual Studio Debug Builds only - for other compilers the class has no functionality, but it will not break compiling).

Setup of CppUTest

IrrIMGUI uses the CppUTest framework for Unit Testing and Mocking. Maybe this is not the most complex and fancy Unit Test and Mocking framework, but it is quite stable and it works on many systems. When you want to compile the IrrIMGUI Unit Tests, please follow these steps:

  1. Download CppUTest from GitHub: http://cpputest.github.io/

  2. Build it as static library with CMake

  3. Attention: Do not enable the Memory Leak Detection of CppUTest, when you want to use IrrIMGUI as library, since the Memory Leak Detection of CppUTest will show you false negatives since it cannot track correctly the memory allocation and deallocation across library boundaries. For Memory Leak Detection, please use the build in IrrIMGUI::Tools::CBasicMemoryLeakDetection class on Win32 or the tool Valgrind on Linux (available on many distributions).

  4. Enable the option IRRIMGUI_BUILD_UNITTESTS inside the IrrIMGUI CMake system and press "Configure". You will see three error messages, since the path to CppUTests is not set correctly.

  5. Add the paths to the CppUTest includes and libraries inside the IrrIMGUI CMake system

  6. After building IrrIMGUI, run the unit tests from <install-directory>/tests/UnitTests/

Attention: When you build your own unit tests with CppUTests using this configuration, please take care to define the symbol CPPUTEST_USE_MEM_LEAK_DETECTION=0 to disable the Memory Leak Detection on the CppUTest headers. Otherwise you will get link-errors since the Memory Leak Detection functions are not contained inside the CppUTest libraries.

An Unit Test Example

Setting up a CppUTest Unit Test is very simple. First create a source file, that contains the Unit Test Main-Function:

// library includes
#include <CppUTest/CommandLineTestRunner.h>
#include <CppUTest/TestRegistry.h>
#include <CppUTestExt/MockSupportPlugin.h>
#include <CppUTestExt/MockSupport.h>

int main(int Arguments, char const ** ppCommandLineList)
{
  // Add mock support to CppUTest
  MockSupportPlugin MockPlugin;
  TestRegistry::getCurrentRegistry()->installPlugin(&MockPlugin);

  // Clean the memory inside the mock (otherwise you will see memory leaks in the first testcase)
  mock().clear();

  // Run all tests
  return CommandLineTestRunner::RunAllTests(Arguments, ppCommandLineList);
}

Then you can create other source files that contain the tests:

// library includes
#include <IrrIMGUI/UnitTest/UnitTest.h>

TEST_GROUP(TestGroup)
{
  TEST_SETUP()
  {
    // something that should be performed before every test
  }

  TEST_TEARDOWN()
  {
    // something that should be performed after every test
    // (also when the test was not successful) 
  }
};

TEST(TestGroup, FirstTest)
{
  // should always pass
  CHECK_EQUAL(false, false);
}

You can create as many groups and test-functions you want. In general it is better to just test a single and small feature within every test, to keep the tests as simple as possible.

For more information, please look at the CppUTest Manual.

Using the IMGUI Handle Mock

Introduction

A Mock is a function for class that looks from the outside like the original function or class. But in reality it is just a test-dummy that helps to reduce the complexity and dependencies of a test.

IrrIMGUI delivers a Mock for the IIMGUIHandle class. You can use this Mock, to check within your unit tests, if your application calls the IIMGUIHandle class in a right way. You can check the function calls that happened to this class and check the arguments that have been used. It is also possible to check the call-order and to define return values.

To use the IMGUIHandleMock, you need to include the header file IrrIMGUI/UnitTest/IIMGUIHandleMock.h.

Enable and Disable the Mock

Before the Mock can be used, it needs to be enabled, to tell IrrIMGUI to create a Mock object instead of a real IrrIMGUI object. This can be done with the function IrrIMGUI::UnitTest::IIMGUIHandleMock::enableMock();. When you want to use the real IMGUI-Handle object again, just disable it with IrrIMGUI::UnitTest::IIMGUIHandleMock::disableMock();.

Normally you want to use the Mock class inside a whole Unit Test Group, in this case it is a very good idea to enable the Mock inside the setup-function and to disable it inside the tear-down-function:

TEST_GROUP(IIMGUIHandleMock)
{
  TEST_SETUP()
  {
    IrrIMGUI::UnitTest::IIMGUIHandleMock::enableMock();
  }

  TEST_TEARDOWN()
  {
    IrrIMGUI::UnitTest::IIMGUIHandleMock::disableMock();
  }
};

Hint: It is always a good idea to disable the Mock inside the tear-down-function, even when not every unit test within this group enables it. The tear-down-function is performed after every test, even when the test fails. When a test fails, it will not perform the remaining code of the test-function, thus it could happen, that the test does not disable the Mock when it fails. And this could lead to fails on further tests. Thus putting it into the tear-down function will increase the stability also in case of failing tests.

Expect a call to a IMGUI-Handle function

This is a small example that shows, how to check if a certain call to a IMGUI-Handle function was done. In this case we create an IIMGUIHandle object and thus we expect a call to the constructor and destructor of the IMGUI-Handle Mock class.

// library includes
#include <IrrIMGUI/UnitTest/UnitTest.h>
#include <IrrIMGUI/UnitTest/IIMGUIHandleMock.h>
#include <IrrIMGUI/IrrIMGUI.h>

TEST_GROUP(TestGroup)
{
  TEST_SETUP()
  {
    IrrIMGUI::UnitTest::IIMGUIHandleMock::enableMock();
  }

  TEST_TEARDOWN()
  {
    IrrIMGUI::UnitTest::IIMGUIHandleMock::disableMock();
  }
};

TEST(TestGroup, FirstTest)
{

  irr::IrrlichtDevice * const pDevice = irr::createDevice(irr::video::EDT_NULL);
  IrrIMGUI::CIMGUIEventReceiver EventReceiver;

  // check call to constructor with the Irrlicht device and EventReciver as pointer
  // ignore the Settings pointer, since this is not important for this test
  mock().expectOneCall("IIMGUIHandleMock::IIMGUIHandleMock")
          .withParameter("pDevice",               pDevice)
          .withParameter("pEventStorage",         &EventReceiver)
          .ignoreOtherParameters();

  // check call to destructor
  mock().expectOneCall("IIMGUIHandleMock::~IIMGUIHandleMock");

  // ignore everything else
  mock().ignoreOtherCalls();

  IrrIMGUI::IIMGUIHandle * const pGUI = IrrIMGUI::createIMGUI(pDevice, &EventReceiver);

  pGUI->drop();
  pDevice->drop();

}

Enable or Disable IMGUI function calls

Normally the IMGUI System expects from a Application a setup and some functions calls in the right order before you can add GUI elements inside your main-loop. These functions calls and also the IMGUI setup is done for you inside the IMGUI-Handle class. But what about the IMGUI-Handle Mock? Does it work together with the IMGUI System?

The IMGUI-Handle Mock contains a very small amount of functionality to setup and call IMGUI functions, that are expected by the IMGUI system in a normal application. Thus it is possible to use the IMGUI-Handle Mock in a test that calls IMGUI functions directly.

However you can control this behaviour of the Mock by using the functions IrrIMGUI::UnitTest::IIMGUIHandleMock::enableIMGUICalls() and IrrIMGUI::UnitTest::IIMGUIHandleMock::disableIMGUICalls(). If this functionality is disabled, the Mock will not call any IMGUI function and it will not setup any value of the IMGUI System. But this also means your application cannot use the Mock together with direct IMGUI function calls. The IMGUI-calls inside the IMGUI-Handle-Mock are enabled by default.

Using Dependency Injection

Starting with Version 0.3 the IrrIMGUI library uses a factory function to create an object of the IMGUI-Handle class. In case you enable the IMGUI-Handle Mock, the same factory function will deliver an object of the IMGUI-Handle Mock instead of an object of the real IMGUI-Handle class. With this functionality you can use a Mock-class inside your application, without changing the code of your application. It helps to test the application and to reduce the dependency to the IrrIMGUI library.

This feature in called dependency injection and it could also be used to create your own Mocks and Instances of your own IMGUI-Handle class, as long as they fulfil the IIMGUIHandle interface. The optional header file IrrIMGUI/Inject/IrrIMGUIInject.h contains some functions for adapting the factory-function behaviour to your needs.

Setting a own factory function: You can set your own factory function by using the function IrrIMGUI::Inject::setIMGUIFactory(...). As an argument you must pass a function-name that fulfils the following prototype:

IrrIMGUI::IIMGUIHandle * dummyFactory(irr::IrrlichtDevice * pDevice, IrrIMGUI::CIMGUIEventStorage * pEventStorage, IrrIMGUI::SIMGUISettings const * pSettings)
{
  // create an object that is derived from IIMGUIHandle and return the pointer to it...
  return NULL;
}

After enabling your own factory function, your application will actually perform your factory function when calling the IrrIMGUI::createIMGUI(...) function.

Setting the default factory function: You can reset the injection to use the default factory function by calling IrrIMGUI::Inject::setIMGUIFactory() without arguments.

The injection-header contains also a getter function, that returns the pointer to the currently used factory-function: IrrIMGUI::Inject::getIMGUIFactory().

Using the Win32 Memory Leak Detection

The IrrIMGUI library includes a very basic but easy to use memory leak detection for Visual Studio that is based on the CRTDBG.h header. To activate the memory leak detection, you simply need to create an object of IrrIMGUI::Tools::CBasicMemoryLeakDetection from the optional header IrrIMGUI/Tools/CBasicMemoryLeakDetection.h.

When this object is created, it stores the current memory usage and when it is destroyed, it will compare the memory usage and raise an error in case it finds differences. You can create an object of this class as the very first object in your main-function. The expectation is, that your application deletes all memory objects before your application ends and thus the memory consumption is the same like the consumption at the beginning of the main-function. Otherwise you may have a memory leak inside your application.

This is a small example, how to use the Memory Leak Detection:

// library includes
#include <IrrIMGUI/Tools/CBasicMemoryLeakDetection.h>

int main(int Arguments, char const ** ppCommandLineList)
{
  IrrIMGUI::Tools::CBasicMemoryLeakDetection MemoryLeakDetection;
  
  {
    // here is your application code...
  }

  return 0;
}

IrrIMGUI also contains a Plugin for CppUTest that checks the memory consumption after every single testcase and if there is a difference, it will fail the test. For this, just load the Memory Detection Plugin inside your main-function of your tests:

// library includes
#include <CppUTest/CommandLineTestRunner.h>
#include <CppUTest/TestRegistry.h>
#include <CppUTestExt/MockSupportPlugin.h>
#include <CppUTestExt/MockSupport.h>
#include <IrrIMGUI/UnitTest/BasicMemoryLeakDetectionPlugin.h>

int main(int Arguments, char const ** ppCommandLineList)
{
  // Add mock support to CppUTest
  MockSupportPlugin MockPlugin;
  TestRegistry::getCurrentRegistry()->installPlugin(&MockPlugin);

  // Add memory leak detection plugin
  IrrIMGUI::UnitTest::BasicMemoryLeakDetectionPlugin MemoryPlugin;
  TestRegistry::getCurrentRegistry()->installPlugin(&MemoryPlugin);

  // Clean the memory inside the mock (otherwise you will see memory leaks in the first testcase)
  mock().clear();

  // Run all tests
  return CommandLineTestRunner::RunAllTests(Arguments, ppCommandLineList);
}

Attention: The Memory Leak Detection Plugin must be loaded after the Mocking-Plugin, otherwise the internal memory allocation of the mocking-feature may lead to false-negative memory leaks.

The Memory Leak Detection works only for Visual Studio Debug builds. If you compile a release build or if you use a different compiler, the memory leak detection class is just a empty dummy-class without any functionality. You can check the definition _ENABLE_MEMORY_LEAK_DETECTION_ inside the header IrrIMGUI/Tools/CBasicMemoryLeakDetection.h to verify if the memory leak detection is enabled or disabled.

For Unix you can use the tool Valgind, that shows you a lot of useful debug information in case of memory leaks.