Как начать работу с GTest и CMake

Недавно меня продали за использование CMake для компиляции моих проектов C++, и теперь я хотел бы начать писать некоторые модульные тесты для моего кода. Я решил использовать утилиту Google Test, чтобы помочь с этим, но мне нужна помощь в начале работы.

Весь день я читал различные руководства и примеры, в том числе Учебник для начинающих, введение в IBM и некоторые вопросы по SO (здесь и здесь) а также другие источники, которые я потерял. Я понимаю, что есть много, но почему-то у меня все еще есть трудности.

В настоящее время я пытаюсь реализовать самый простой тест, чтобы подтвердить, что я правильно скомпилировал/установил gtest и он не работает. Единственный исходный файл (testgtest.cpp) почти точно взят из этого предыдущего ответа:

#include <iostream>

#include "gtest/gtest.h"

TEST(sample_test_case, sample_test)
{
    EXPECT_EQ(1, 1);
}

и связанный с ним файл CMakeLists.txt выглядит следующим образом:

cmake_minimum_required(VERSION 2.6)
project(basic_test)

# Setup testing
enable_testing()
find_package(GTest REQUIRED)
include_directories(${GTEST_INCLUDE_DIR})

# Add test cpp file
add_executable(runUnitTests
    testgtest.cpp
)

# Link test executable against gtest & gtest_main
target_link_libraries(runUnitTests ${GTEST_LIBRARY_DEBUG} ${GTEST_MAIN_LIBRARY_DEBUG})

add_test(
    NAME runUnitTests
    COMMAND runUnitTests
)

Обратите внимание, что я выбрал ссылку на gtest_main вместо предоставления main в конце файла cpp, так как считаю, что это позволит мне более легко масштабировать тестирование до нескольких файлов.

При создании сгенерированного файла .sln (в Visual C++ 2010 Express) я, к сожалению, получаю длинный список ошибок вида

2>msvcprtd.lib(MSVCP100D.dll) : error LNK2005: "public: virtual __thiscall std::basic_iostream<char,struct std::char_traits<char> >::~basic_iostream<char,struct std::char_traits<char> >(void)" ([email protected][email protected]@[email protected]@@[email protected]@[email protected]) already defined in gtestd.lib(gtest-all.obj)

что, я думаю, означает, что я не могу успешно связываться с библиотеками gtest. Я убедился, что при связывании с отладочными библиотеками я попытался встроить режим отладки.

EDIT

Сделав еще несколько копаний, я думаю, что моя проблема связана с типом библиотеки, в которую я встраиваю gtest. При сборке gtest с CMake, если BUILD_SHARED_LIBS не проверен, и я связываю свою программу с этими .lib файлами, я получаю ошибки, упомянутые выше. Однако, если флажок BUILD_SHARED_LIBS установлен, я создаю набор файлов .lib и .dll. При связывании этих .lib файлов программа компилируется, но при запуске жалуется, что не может найти gtest.dll.

В чем различия между библиотекой SHARED и не SHARED, и если я выбрал не общий доступ, почему он не работает? Есть ли в CMakeLists.txt вариант для моего проекта, который мне не хватает?

Ответ 1

Решение включало установку исходного каталога gtest в качестве подкаталога вашего проекта. Я включил рабочий CMakeLists.txt ниже, если он кому-то полезен.

cmake_minimum_required(VERSION 2.6)
project(basic_test)

################################
# GTest
################################
ADD_SUBDIRECTORY (gtest-1.6.0)
enable_testing()
include_directories(${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR})

################################
# Unit Tests
################################
# Add test cpp file
add_executable( runUnitTests testgtest.cpp )
# Link test executable against gtest & gtest_main
target_link_libraries(runUnitTests gtest gtest_main)
add_test( runUnitTests runUnitTests )

Ответ 2

Вот полный рабочий пример, который я только что проверил. Он загружается непосредственно из Интернета, будь то фиксированный архив или последний каталог subversion.

cmake_minimum_required (VERSION 3.1)

project (registerer)

##################################
# Download and install GoogleTest

include(ExternalProject)
ExternalProject_Add(gtest
  URL https://googletest.googlecode.com/files/gtest-1.7.0.zip
  # Comment above line, and uncomment line below to use subversion.
  # SVN_REPOSITORY http://googletest.googlecode.com/svn/trunk/ 
  # Uncomment line below to freeze a revision (here the one for 1.7.0)
  # SVN_REVISION -r700

  PREFIX ${CMAKE_CURRENT_BINARY_DIR}/gtest
  INSTALL_COMMAND ""
)
ExternalProject_Get_Property(gtest source_dir binary_dir)

################
# Define a test
add_executable(registerer_test registerer_test.cc)

######################################
# Configure the test to use GoogleTest
#
# If used often, could be made a macro.

add_dependencies(registerer_test gtest)
include_directories(${source_dir}/include)
target_link_libraries(registerer_test ${binary_dir}/libgtest.a)
target_link_libraries(registerer_test ${binary_dir}/libgtest_main.a)

##################################
# Just make the test runnable with
#   $ make test

enable_testing()
add_test(NAME    registerer_test 
         COMMAND registerer_test)

Ответ 3

Скорее всего, разница в параметрах компилятора между вашим тестовым двоичным файлом и тестовой библиотекой Google заключается в том, чтобы обвинять такие ошибки. Именно поэтому он рекомендовал принести Google Test в исходную форму и построить ее вместе с вашими тестами. Это очень легко сделать в CMake. Вы просто вызываете ADD_SUBDIRECTORY с помощью пути к корню gtest, а затем можете использовать цели публичной библиотеки (gtest и gtest_main), определенные там. В этой теме CMake есть дополнительная информация в группе googletestframework.

[править] Опция BUILD_SHARED_LIBS действует только в Windows. Он определяет тип библиотек, которые вы хотите создать CMake. Если вы установите значение ON, CMake будет создавать их как библиотеки DLL, а не статические библиотеки. В этом случае вам необходимо создать свои тесты с помощью -DGTEST_LINKED_AS_SHARED_LIBRARY = 1 и скопировать DLL файлы, созданные CMake, в каталог с вашим тестовым двоичным кодом (по умолчанию CMake помещает их в отдельный выходной каталог). Если только в static lib не работает для вас, проще не устанавливать эту опцию.

Ответ 4

Вы можете получить лучшее из обоих миров. Можно использовать ExternalProject для загрузки исходного кода gtest, а затем использовать add_subdirectory() для добавления его в свою сборку. Это имеет следующие преимущества:

  • gtest собран как часть вашей основной сборки, поэтому он использует те же флаги компилятора и т.д. и поэтому избегает проблем, подобных тем, которые описаны в вопросе.
  • Нет необходимости добавлять источники gtest в собственное дерево исходных текстов.

При обычном использовании ExternalProject не выполняет загрузку и распаковку во время настройки (т.е. при запуске CMake), но вы можете сделать это, выполнив немного работы. Я написал сообщение в блоге о том, как это сделать, которое также включает обобщенную реализацию, которая работает для любого внешнего проекта, который использует CMake в качестве своей системы сборки, а не только gtest. Вы можете найти их здесь:

Обновление: Этот подход теперь также является частью документации Googletest.

Ответ 5

Сделав еще несколько копаний, я думаю, что моя проблема связана с типом библиотеки, в которую я строю gtest. При установке gtest с помощью CMake, если BUILD_SHARED_LIBS не проверен, и я связываю свою программу с этими .lib файлами, я получаю ошибки, упомянутые выше. Однако, если отмечен BUILD_SHARED_LIBS, я создаю набор файлов .lib и .dll. Когда теперь ссылаются на эти .lib файлы, программа компилирует, но при запуске жалуется, что не может найти gtest.dll.

Это потому, что вы должны добавить -DGTEST_LINKED_AS_SHARED_LIBRARY = 1 в определения компилятора в своем проекте, если хотите использовать gtest как общую библиотеку.

Вы также можете использовать статические библиотеки, если вы скомпилировали его с параметром gtest_force_shared_crt, чтобы устранить ошибки, которые вы видели.

Мне нравится библиотека, но добавление ее в проект - настоящая боль. И у вас нет шансов сделать это правильно, если вы не будете копать (и взломать) в файлы gtest cmake. Позор. В частности, мне не нравится идея добавления gtest в качестве источника.:)

Ответ 6

Решения Yours и VladLosevs, вероятно, лучше моих. Однако, если вы хотите использовать решение грубой силы, попробуйте следующее:

SET(CMAKE_EXE_LINKER_FLAGS /NODEFAULTLIB:\"msvcprtd.lib;MSVCRTD.lib\")

FOREACH(flag_var
    CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
    CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
    if(${flag_var} MATCHES "/MD")
        string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
    endif(${flag_var} MATCHES "/MD")
ENDFOREACH(flag_var)

Ответ 7

Самый простой CMakeLists.txt, который я отстранился от ответов в этом потоке, и некоторые пробные версии и ошибки:

project(test CXX C)
cmake_minimum_required(VERSION 2.6.2)

#include folder contains current project header filed
include_directories("include")

#test folder contains test files
set (PROJECT_SOURCE_DIR test) 
add_executable(hex2base64 ${PROJECT_SOURCE_DIR}/hex2base64.cpp)

# Link test executable against gtest nothing else required
target_link_libraries(hex2base64 gtest pthread)

Gtest уже должен быть установлен в вашей системе.

Ответ 8

Так же, как обновление комментария @Patricia в принятом ответе и комментария @Fraser для исходного вопроса, если у вас есть доступ к CMake 3. 11+, вы можете использовать функцию CMake FetchContent.

Страница CMake FetchContent в качестве примера использует googletest!

Я предоставил небольшую модификацию принятого ответа:

cmake_minimum_required(VERSION 3.11)
project(basic_test)

set(GTEST_VERSION 1.6.0 CACHE STRING "Google test version")

################################
# GTest
################################
FetchContent_Declare(googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG release-${GTEST_VERSION})

FetchContent_GetProperties(googletest)
if(NOT googletest_POPULATED)
  FetchContent_Populate(googletest)
  add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR})
endif()

enable_testing()

################################
# Unit Tests
################################
# Add test cpp file
add_executable(runUnitTests testgtest.cpp)

# Include directories
target_include_directories(runUnitTests 
                      $<TARGET_PROPERTY:gtest,INTERFACE_SYSTEM_INCLUDE_DIRECTORIES>
                      $<TARGET_PROPERTY:gtest_main,INTERFACE_SYSTEM_INCLUDE_DIRECTORIES>)

# Link test executable against gtest & gtest_main
target_link_libraries(runUnitTests gtest
                                   gtest_main)

add_test(runUnitTests runUnitTests)

Вы можете использовать свойство target INTERFACE_SYSTEM_INCLUDE_DIRECTORIES целей gtest и gtest_main, поскольку они установлены в тестовом сценарии ga CMakeLists.txt.