domingo, 8 de enero de 2017

Qtest + google mock

The problem we are trying to solve is using google mocks with qtest. Since we don't use google test we need to replace the default event listener so we can fail the test with qt macros. We overwrite the OnTestPartResult. Then we create a macro to initialize all our tests the same way. unittest.h:
/**
 * \file
 * \brief Generic definitions for unit tests.
 */

#ifndef __UNIT_TESTS_H__
#define __UNIT_TESTS_H__

#include 
#include "gmock/gmock.h"

namespace unittests {

/**
 * \brief A class to replace qtest default event listener. 
 * 
 * The relevant thing here is the OnTestPartResult method. We need to change it
 * to fail the tests, at qtest level, when the expectations fail.
 *
 * This function gets called when we call the destructor of the mocks or the
 * ::testing::Mock::VerifyAndClearExpectations function.
 */
class GoogleTestEventListener : public ::testing::EmptyTestEventListener {
   virtual void OnTestStart(const ::testing::TestInfo&) {
   }

   virtual void OnTestPartResult(const ::testing::TestPartResult& test_part_result) {
        if (test_part_result.failed()) {
            QFAIL(
                QString("mock objects failed with '%1' at %2:%3")
                    .arg(QString(test_part_result.summary()))
                    .arg(test_part_result.file_name())
                    .arg(test_part_result.line_number())
                    .toLatin1().constData()
            );
        }
   }

   // Called after a test ends.
   virtual void OnTestEnd(const ::testing::TestInfo&) {
   }
};

} // namespace unittests

/**
 * \def INIT_GOOGLE_MOCKS (argc, argv)
 * \brief A macro that defines the unit test initialization. 
 *
 * We define the initialization process in one place.
 * This only really needed when using google mocks expectations.i.e. if your
 * test uses EXPECT_CALL.
 */
#define INIT_GOOGLE_MOCKS(argc, argv) { \
      ::testing::InitGoogleTest (&(argc), (argv)); \
      ::testing::TestEventListeners& listeners = ::testing::UnitTest::GetInstance()->listeners();  \
      delete listeners.Release(listeners.default_result_printer());\
      listeners.Append(new unittests::GoogleTestEventListener); }

#endif // __UNIT_TESTS_H__
And now an example on how to use it. First we have a class called classToTest. classToTest.h:
#ifndef __CLASS_TO_TEST__
#define __CLASS_TO_TEST__

#include "dependency.h"

class ClassToTest 
{
public:
    ClassToTest (Dependency *dep);
    ~ClassToTest ();

    int function1 (int param);
private:
    Dependency * _dependency;
};

#endif //__CLASS_TO_TEST__
classToTest.cpp:
#include "classToTest.h"

ClassToTest::ClassToTest (Dependency *dep)
{
    _dependency = dep;
}

ClassToTest::~ClassToTest ()
{
}

int ClassToTest::function1 (int param)
{
    return (_dependency->getValue (param));
}
The class uses the following dependency. dependency.h:
#ifndef __DEPENDENCY_H__
#define __DEPENDENCY_H__

class Dependency 
{
public:
    virtual int getValue (int inValue) = 0;

};

#endif // __DEPENDENCY_H__
This is how the test looks like: MyTest.h:
#ifndef __SECOND_TEST_H__
#define __SECOND_TEST_H__

#include 

class MyTest: public QObject
{
    Q_OBJECT
private slots:
    void test1 ();
};

#endif //__SECOND_TEST_H__
MyTest.cpp:
#include "classToTest.h"
#include "unittests.h"
#include "MyTest.h"
#include "gmock/gmock.h"

class MockDependency: public Dependency 
{
public:
    MOCK_METHOD1 (getValue, int (int inValue));
};

void MyTest::test1 ()
{
    MockDependency dep;

    EXPECT_CALL (dep, getValue (13))
        .Times (1)
        .WillOnce(::testing::Return(7));

    ClassToTest C (&dep);

    QCOMPARE (C.function1 (13), 7);
}

int main (int argc, char **argv)
{
    INIT_GOOGLE_MOCKS (argc, argv);

    MyTest *theTest = new MyTest ();
    return (QTest::qExec (theTest, argc, argv));
    delete theTest;
}
Finally, I've included the cmake/ctest script in case you want to compile the example. CMakeLists.txt:
cmake_minimum_required (VERSION 2.8.11)

enable_testing ()
project (ppral CXX)

add_subdirectory (googletest-master/googlemock)

set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)

find_package (Qt5Core REQUIRED)
find_package (Qt5Test REQUIRED)

# Add the include directories for the Qt 5 Widgets module to
# the compile lines.
include_directories(${Qt5Core_INCLUDE_DIRS} )
include_directories(googletest-master/googlemock/include)
include_directories(googletest-master/googletest/include)
# Add compiler flags for building executables (-fPIE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Qt5Core_EXECUTABLE_COMPILE_FLAGS} ")

qt5_wrap_cpp (classToTest.cpp classToTest.h Dependecy.h unittests.h
 MyTest.h MyTest.cpp)

add_executable (MyTest classToTest.cpp classToTest.h MyTest.h
    MyTest.cpp unittests.h dependency.h)
target_link_libraries (MyTest ${Qt5Core_LIBRARIES} ${Qt5Test_LIBRARIES} 
    gmock gtest)
add_test (NAME MyTest COMMAND MyTest)