Skip to content

Latest commit

 

History

History
 
 

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 

README.md

Gtest example with gmock for C++

Install git and compile

$ git clone https://github.com/google/googletest.git -b release-1.12.0
$ cd googletest
$ mkdir build
$ cd build
$ cmake .. -DBUILD_GMOCK=OFF
$ make

Please check the current release version and update the git clone command accordingly.

After this you releated static binaries will be in the googletest/build/lib/ location.

To test it lets write simpe hello world gtest application. (you can find it as an example.cpp in the repo)

#include <iostream>
#include <gtest/gtest.h>

using namespace std;

TEST(TestName, test1)
{
	ASSERT_EQ(1, 1);
}

int main(int argc, char **argv)
{
	testing::InitGoogleTest(&argc, argv);
	return RUN_ALL_TESTS();
}

What we have here, one main method and one test method has name TestName.test1. To compile it you can use simply:

// g++ example.cpp googletest/build/lib/libgtest.a googletest/build/lib/libgtest_main.a -lpthread -I googletest/googletest/include/

After this it will give you a.out when you run it.

jnano@jnano:~$ ./a.out 
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from TestName
[ RUN      ] TestName.test1
[       OK ] TestName.test1 (0 ms)
[----------] 1 test from TestName (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[  PASSED  ] 1 test.

But we do not use commandline to compile our projects. Then it is a good idea to use cmake to compile all.

cmake_minimum_required(VERSION 3.10)
set(CMAKE_CXX_STANDARD 14)

project(ExampleGtest LANGUAGES CXX)

include_directories(googletest/googletest/include googletest/googlemock/include  include)
add_executable(${PROJECT_NAME} src/example.cpp)
target_link_libraries(${PROJECT_NAME} ${CMAKE_SOURCE_DIR}/googletest/build/lib/libgtest.a ${CMAKE_SOURCE_DIR}/googletest/build/lib/libgtest_main.a ${CMAKE_SOURCE_DIR}/googletest/build/lib/libgmock.a ${CMAKE_SOURCE_DIR}/googletest/build/lib/libgmock_main.a)

set(CMAKE_THREAD_LIBS_INIT "-lpthread")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
set(CMAKE_HAVE_THREADS_LIBRARY 1)
set(CMAKE_USE_WIN32_THREADS_INIT 0)
set(CMAKE_USE_PTHREADS_INIT 1)
set(THREADS_PREFER_PTHREAD_FLAG ON)

What do we apply here, simply link the static binaries and include headers with cmake. But this is a best practice ? Nope. Then what ?

If you check here https://cmake.org/cmake/help/latest/module/FetchContent.html then you are able to see that we can fetch the content form the repositroy directly. No need to build. Like :

cmake_minimum_required(VERSION 3.14)
set(CMAKE_CXX_STANDARD 14)

project(ExampleGtest LANGUAGES CXX)

include(FetchContent)
FetchContent_Declare(
        googletest
        GIT_REPOSITORY https://github.com/google/googletest.git
        GIT_TAG        bf66935e07825318ae519675d73d0f3e313b3ec6 
)
FetchContent_MakeAvailable(googletest)

add_executable(${PROJECT_NAME} src/example.cpp)

target_link_libraries(${PROJECT_NAME} gtest_main gmock_main)

include(GoogleTest)
gtest_discover_tests(${PROJECT_NAME})

#include headers
include_directories(include)

This will compile and fectch the gtest accordingly, so no need to compile by hand. And no need to care about the static files. You can give the desired commit number. Becareful about cmake version, it can be not available for old versions. And there is one more benefit here. gtest_discover_tests make us to remove main() function in the example.cpp. That means we do not have to init google test and run it. Just simply define the tests.

If you check the cmakelists.txt in the repository then you will see extra commands like gcov and lcov. These are used to calculate line coverage. What is linecoverage ? Simply it will give you the numbers of the lines and coverage what you test in the your code base.

# Create the gcov target. Run coverage tests with 'make gcov'
add_custom_target(gcov
    COMMAND mkdir -p gcoverage
    COMMAND ${CMAKE_MAKE_PROGRAM} test
    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
    )

add_custom_command(TARGET gcov
    COMMAND echo "=================== GCOV ===================="
    COMMAND ${GCOV_PATH} -b ${CMAKE_SOURCE_DIR}/src/*.cpp -o ${OBJECT_DIR} 
    COMMAND echo "-- Source diretorie: ${CMAKE_SOURCE_DIR}/src/"
    COMMAND echo "-- Coverage files have been output to ${CMAKE_BINARY_DIR}/gcoverage"
    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/gcoverage
    )

add_dependencies(gcov ${PROJECT_NAME})

add_custom_target(lcov
    COMMAND mkdir -p lcoverage
    )

add_custom_command(TARGET lcov
    COMMAND echo "=================== LCOV ===================="
    COMMAND echo "-- Passing lcov tool under code coverage"
    COMMAND lcov --capture --directory ${OBJECT_DIR} --output-file lcoverage/main_coverage.info -b .
    COMMAND echo "-- Remove undesired files from main_coverage"
    COMMAND lcov --remove lcoverage/main_coverage.info '*/usr/include/*' '*/gtest/*' -o lcoverage/filtered_coverage.info
    COMMAND echo "-- Generating HTML output files"
    COMMAND genhtml lcoverage/filtered_coverage.info --output-directory lcoverage
    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
    )

# Make sure to clean up the coverage folder
set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES gcoverage)

# Create the gcov-clean target. This cleans the build as well as generated 
# .gcda and .gcno files. ${CMAKE_MAKE_PROGRAM} is /usr/bin/make
add_custom_target(refresh
    COMMAND ${CMAKE_MAKE_PROGRAM} clean
    COMMAND rm CMakeCache.txt
    COMMAND rm -f ${OBJECT_DIR}/*.gcno
    COMMAND rm -f ${OBJECT_DIR}/*.gcda
    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
    )

I created here three differrent more targets in the cmakelists.txt. What is target ? when you run "make" command you will be able to "make -targetName-". For instance one target is "gcov". After compilation, when you run "make gcov" you will see that all files are processed in your application. This will produce .gcov files where you can find line coverage information. But it is not human readable and there are other files that we do not need. It will produce for all linked binaries for instance gtest and included libraries. We should remove them. We can remove them during lcov process. Lcov is a tool to create html readable format for line coverage. then run "make lcov"

Generating output.
Processing file src/foo.cpp
Processing file src/example.cpp
Processing file src/dummy.cpp
Writing directory view page.
Overall coverage rate:
  lines......: 52.6% (10 of 19 lines)
  functions..: 70.0% (7 of 10 functions)
Built target lcov

This will produce lcoverage directory go there, then you may be able to see index.html file. Open it using one of your favorite web browser. You should see sth like this:

alt text

You can check the line coverage visually file by file. Btw, I added all the stuff in one CMakeLists.txt file, I think it is not best practice, it is a good idea to split it :)

Gmock example

So what is gmock ? When you have dependecies for your object, during the unit testing these dependencies should be mocked. When you mocked them, your object does not call the real methods of dependencies but mocked methods. It will be good to have to use interfaces in your design. ( interface is good thing )

For instance in this example you can see that foo class is mocked, if you check under include directory, there is a mock_foo.hpp file. ( keeping this in include with others is not a good idea ofcourse).

#include "foo_if.hpp"
#include <gmock/gmock.h>
#include <string>

class mockFoo : public fooIf
{
	public:
	MOCK_METHOD(void, fooStr, (std::string& str), (override));
	MOCK_METHOD(void, fooThrow, (), (override));
	MOCK_METHOD(void, callbackMethod, (std::function<void(void)>),(override));

};

As you see, it implements the fooIf interface. The way of implementaion is different from syntax perspective. You need to use MOCK_METHOD gtest macro and first define return of function then name finally the arguments. To inject this, you will use constructor of your object.

testDummy = std::make_unique<dummy>(fooMock);

Because we can pass it, why ? we use same interface, so we are able to see here that one of the advantage of using interfaces.

Then in the test fixture, you can use it like :

EXPECT_CALL(fooMock, fooStr(::testing::_)).WillOnce(::testing::SetArgReferee<0>(returnStr));

What does it mean ? Simply fooStr method of the mock, must be called and return the string from argument for this test, otherwise gtest will fail.

If you want you can check this diagram to understand the relationship better for mock and test classess. ( design is for this simple example. Relationship can be different according to you requirements. )

Class Diagram

so as you see test class (text fixture) has composition dependecy to dummy class which will be tested, and to mockFoo class not foo class. In this way, during test, real methods of the foo object will not be called. This is because if the mock mechanism.

So the question is that why we do not use foo itself instead of mockFoo. Because we want to test only dummy class not foo or others. This is the point of unit test. Foo object can have some dependecny which can not be relevant for dummy object test. We need to remove them from the test but at the same time we should not change anything in the dummy implementation so we should mock the dependecies of the dummy object which we want to test.