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


125

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

Весь день я читал различные руководства и примеры, включая Primer , введение в 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)" (??1?$basic_iostream@DU?$char_traits@D@std@@@std@@UAE@XZ) already defined in gtestd.lib(gtest-all.obj)

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

РЕДАКТИРОВАТЬ

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

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


4
Вы можете избежать включения исходных текстов GTest в свои собственные, используя ExternalProject_Addвместо add_subdirectory. См. Этот ответ для подробностей.
Fraser

Почему у нас есть доступ к $ {gtest_SOURCE_DIR} в примере решения выше? Как / где объявлена ​​эта переменная?
dmonopoly 03

О, он объявлен в gtest-1.6.0 / CMakeLists.txt: "project (gtest CXX C)", что делает доступными переменные gtest_SOURCE_DIR и gtest_BINARY_DIR.
dmonopoly 03

1
Что enable_testing()делать?
апдоглю

1
@updogliu: включает ctest и цель «test» (или «RUN_TESTS»). Он работает вместе с командой cmake add_test ().
Ela782

Ответы:


76

Решение заключалось в том, чтобы поместить исходный каталог 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 )

3
Я не уверен, что делает add_test (), но похоже, что это не приводит к запуску тестового двоичного файла ... Я что-то упускаю?
weberc2

4
Не бить мертвую лошадь, но я подумал, что об этом стоит упомянуть еще раз. Приведенный выше комментарий Фрейзера содержит очень важный момент: «Вы можете избежать включения источников GTest в свои собственные, используя ExternalProject_Add, а не add_subdirectory». Подробнее см. Ответ и комментарии Фрейзера здесь: stackoverflow.com/a/9695234/1735836
Патрисия

1
В моем случае мне также нужно было добавить pthreadв связанные библиотеки, изменив вторую последнюю строку наtarget_link_libraries(runUnitTests gtest gtest_main pthread)
panmari

3
@ weberc2 Вы должны запустить make testтесты или запустить их ctestиз каталога сборки. Запустите, ctest -Vчтобы увидеть как результат теста Google, так и ctestрезультат.
Патрик

38

Вот полный рабочий пример, который я только что протестировал. Он загружается непосредственно из Интернета, либо фиксированный архив, либо каталог последней версии 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)

7
Я не знаю, почему вам отказали за это. Ваше решение избавляет кого-то от необходимости проверять Google Test в системе контроля версий. Престижность за ваше решение.
Sal

4
Используемый вами URL теперь не работает. Актуальный URL-адресhttps://github.com/google/googletest/archive/release-1.8.0.zip
oscfri

Отличный ответ. Должен быть номер 1.
Mr00Anderson

1
отличный ответ! также мы можем использовать GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG release-1.8.1вместо URL
TingQian LI,

URL-адрес последней версии https://github.com/google/googletest/archive/release-1.10.0.zip
gtest

16

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

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

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

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


2
ИМО, это, пожалуй, самый чистый способ реализовать тест Google с проектом CMake. Желаю, чтобы модераторы уделяли больше внимания содержанию и качеству ответов.
NameRakes

Связанный обобщенный модуль DownloadProject.cmake великолепен. Похоже, что в основе cmake есть система управления пакетами, где все, что мне нужно, это список ссылок на URL-адреса github, совместимые с CMake.
Джош Пик

13

Скорее всего, в таких ошибках виновата разница в параметрах компилятора между вашим тестовым двоичным файлом и библиотекой Google Test. Вот почему рекомендуется использовать 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 по умолчанию помещает их в отдельный выходной каталог). Если gtest в статической библиотеке не работает для вас, проще не устанавливать эту опцию.


1
Большое спасибо, я не понимал, что вы можете создавать полностью отдельные проекты в одних и тех же CMakeLists, подобных этому. Теперь я могу с уверенностью сказать, что EXPECT_EQ (1.0 == 1.0) проходит, а EXPECT_EQ (0.0 == 1.0) не работает. Пришло время для более реальных испытаний ...
Крис

2

Покопавшись еще раз, я думаю, что моя проблема связана с типом библиотеки, в которую я встраиваю 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 в качестве источника. :)


1

OP использует Windows, и сегодня гораздо более простой способ использовать GTest - это vcpkg + cmake.


Установите vcpkg в соответствии с https://github.com/microsoft/vcpkg и убедитесь, что вы можете запускаться vcpkgиз строки cmd. Обратите внимание на папку установки vcpkg, например. C:\bin\programs\vcpkg,

Установите gtest с помощью vcpkg install gtest: это загрузит, скомпилирует и установит GTest.

Используйте CmakeLists.txt, как показано ниже: обратите внимание, что мы можем использовать цели вместо включения папок.

cmake_minimum_required(VERSION 3.15)
project(sample CXX)
enable_testing()
find_package(GTest REQUIRED)
add_executable(test1 test.cpp source.cpp)
target_link_libraries(test1 GTest::GTest GTest::Main)
add_test(test-1 test1)

Запустите cmake с помощью: (при необходимости отредактируйте папку vcpkg и убедитесь, что путь к файлу цепочки инструментов vcpkg.cmake правильный)

cmake -B build -DCMAKE_TOOLCHAIN_FILE=C:\bin\programs\vcpkg\scripts\buildsystems\vcpkg.cmake

и строить cmake --build buildкак обычно. Обратите внимание, что vcpkg также скопирует необходимый файл gtest (d) .dll / gtest (d) _main.dll из папки установки в папки Debug / Release.

Протестируйте с помощью cd build & ctest.


0

Ваши решения и решения Влада Лосева, наверное, лучше моих. Однако, если вам нужно решение методом перебора, попробуйте следующее:

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)

0

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

project(test CXX C)
cmake_minimum_required(VERSION 2.6.2)

#include folder contains current project's 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 уже должен быть установлен в вашей системе.


Добавлять подобную библиотеку в CMake - не лучшая практика. Одна из основных целей cmake - никогда не делать предположений типа «Эта библиотека уже должна быть установлена ​​...». CMake проверяет наличие библиотеки, и если нет, выдается ошибка.
Adrien BARRAL

0

Так же, как обновление комментария @ 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)

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


В CMake> = v3.14 вы можете отказаться от явного target_include_directoriesи использовать FetchContent_MakeAvailable(googletest)вместо него. Это одновременно заполнит контент и добавит его в основную сборку. CMake FetchContent - подробнее
67hz

0

Я решил быстро собрать что-то общее, демонстрируя способ, отличный от ранее опубликованных, в надежде, что это может кому-то помочь. Следующее сработало для меня на моем Mac. Сначала я запустил команды настройки для gtests. Я просто использовал найденный сценарий, чтобы все настроить.

#!/usr/bin/env bash

# install gtests script on mac
# https://gist.github.com/butuzov/e7df782c31171f9563057871d0ae444a

#usage
# chmod +x ./gtest_installer.sh
# sudo ./gtest_installer.sh

# Current directory
__THIS_DIR=$(pwd)


# Downloads the 1.8.0 to disc
function dl {
    printf "\n  Downloading Google Test Archive\n\n"
    curl -LO https://github.com/google/googletest/archive/release-1.8.0.tar.gz
    tar xf release-1.8.0.tar.gz
}

# Unpack and Build
function build {
    printf "\n  Building GTest and Gmock\n\n"
    cd googletest-release-1.8.0
    mkdir build 
    cd $_
    cmake -Dgtest_build_samples=OFF -Dgtest_build_tests=OFF ../
    make
}

# Install header files and library
function install {
    printf "\n  Installing GTest and Gmock\n\n"

    USR_LOCAL_INC="/usr/local/include"
    GTEST_DIR="/usr/local/Cellar/gtest/"
    GMOCK_DIR="/usr/local/Cellar/gmock/"

    mkdir $GTEST_DIR

    cp googlemock/gtest/*.a $GTEST_DIR
    cp -r ../googletest/include/gtest/  $GTEST_DIR
    ln -snf $GTEST_DIR $USR_LOCAL_INC/gtest
    ln -snf $USR_LOCAL_INC/gtest/libgtest.a /usr/local/lib/libgtest.a
    ln -snf $USR_LOCAL_INC/gtest/libgtest_main.a /usr/local/lib/libgtest_main.a

    mkdir $GMOCK_DIR
    cp googlemock/*.a   $GMOCK_DIR
    cp -r ../googlemock/include/gmock/  $GMOCK_DIR
    ln -snf $GMOCK_DIR $USR_LOCAL_INC/gmock
    ln -snf $USR_LOCAL_INC/gmock/libgmock.a /usr/local/lib/libgmock.a
    ln -snf $USR_LOCAL_INC/gmock/libgmock_main.a /usr/local/lib/libgmock_main.a
}

# Final Clean up.
function cleanup {
    printf "\n  Running Cleanup\n\n"

    cd $__THIS_DIR
    rm -rf $(pwd)/googletest-release-1.8.0
    unlink $(pwd)/release-1.8.0.tar.gz
}

dl && build && install && cleanup 

Затем я сделал простую структуру папок и написал несколько быстрых классов

utils/
  cStringUtils.cpp
  cStringUtils.h
  CMakeLists.txt
utils/tests/
    gtestsMain.cpp
    cStringUtilsTest.cpp
    CMakeLists.txt

Я сделал CMakeLists.txt верхнего уровня для папки utils и CMakeLists.txt для папки тестов.

cmake_minimum_required(VERSION 2.6)

project(${GTEST_PROJECT} C CXX)

set(CMAKE_C_STANDARD 98)
set(CMAKE_CXX_STANDARD 98)

#include .h and .cpp files in util folder
include_directories("${CMAKE_CURRENT_SOURCE_DIR}")

##########
# GTests
#########
add_subdirectory(tests)

Это CMakeLists.txt в папке с тестами.

cmake_minimum_required(VERSION 2.6)

set(GTEST_PROJECT gtestProject)

enable_testing()

message("Gtest Cmake")

find_package(GTest REQUIRED)

# The utils, test, and gtests directories
include_directories("${CMAKE_CURRENT_SOURCE_DIR}")
include_directories("/usr/local/Cellar/gtest/include")
include_directories("/usr/local/Cellar/gtest/lib")

set(SOURCES
  gtestsMain.cpp
  ../cStringUtils.cpp
  cStringUtilsTest.cpp
)

set(HEADERS
  ../cStringUtils.h
)

add_executable(${GTEST_PROJECT} ${SOURCES})
target_link_libraries(${GTEST_PROJECT} PUBLIC
  gtest
  gtest_main
)

add_test(${GTEST_PROJECT} ${GTEST_PROJECT})

Тогда все, что осталось, это написать образец gtest и gtest main

образец gtest

#include "gtest/gtest.h"
#include "cStringUtils.h"

namespace utils
{

class cStringUtilsTest : public ::testing::Test {

 public:

  cStringUtilsTest() : m_function_param(10) {}
  ~cStringUtilsTest(){}

 protected:
  virtual void SetUp() 
  {
    // declare pointer 
    pFooObject = new StringUtilsC();    
  }

  virtual void TearDown() 
  {
    // Code here will be called immediately after each test
    // (right before the destructor).
    if (pFooObject != NULL)
    {
      delete pFooObject;
      pFooObject = NULL;
    }
  }


  StringUtilsC fooObject;              // declare object
  StringUtilsC *pFooObject;
  int m_function_param;                // this value is used to test constructor
};

TEST_F(cStringUtilsTest, testConstructors){
    EXPECT_TRUE(1);

  StringUtilsC fooObject2 = fooObject; // use copy constructor


  fooObject.fooFunction(m_function_param);
  pFooObject->fooFunction(m_function_param);
  fooObject2.fooFunction(m_function_param);
}

} // utils end

образец gtest main

#include "gtest/gtest.h"
#include "cStringUtils.h"

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

Затем я могу скомпилировать и запустить gtests с помощью следующих команд из папки utils

cmake .
make 
./tests/gtestProject
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.