Comment commencer à travailler avec GTest et CMake

125

J'ai récemment été convaincu d'utiliser CMake pour compiler mes projets C ++, et j'aimerais maintenant commencer à écrire des tests unitaires pour mon code. J'ai décidé d'utiliser l'utilitaire Google Test pour vous aider, mais j'ai besoin d'aide pour démarrer.

Toute la journée, j'ai lu divers guides et exemples dont le Primer , une introduction chez IBM et quelques questions sur SO ( ici et ici ) ainsi que d'autres sources dont j'ai perdu la trace. Je me rends compte qu'il y en a beaucoup là-bas, mais d'une manière ou d'une autre, j'ai encore des difficultés.

J'essaie actuellement d'implémenter le test le plus basique, pour confirmer que j'ai compilé / installé gtest correctement et que cela ne fonctionne pas. Le seul fichier source (testgtest.cpp) est tiré presque exactement de cette réponse précédente:

#include <iostream>

#include "gtest/gtest.h"

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

et mon CMakeLists.txt associé est le suivant:

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
)

Notez que j'ai choisi de créer un lien avec gtest_main au lieu de fournir le principal à la fin du fichier cpp car je pense que cela me permettra de faire évoluer les tests plus facilement vers plusieurs fichiers.

Lors de la construction du fichier .sln généré (dans Visual C ++ 2010 Express), j'obtiens malheureusement une longue liste d'erreurs du formulaire

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)

ce qui, je pense, signifie que je ne parviens pas à établir une liaison avec les bibliothèques gtest. Je me suis assuré que lors de la liaison avec les bibliothèques de débogage, j'ai ensuite essayé de construire en mode débogage.

ÉDITER

Après avoir creusé davantage, je pense que mon problème est lié au type de bibliothèque dans laquelle je construis gtest. Lors de la construction de gtest avec CMake, si elle BUILD_SHARED_LIBSn'est pas cochée, et que je lie mon programme à ces fichiers .lib, j'obtiens les erreurs mentionnées ci-dessus. Cependant, si BUILD_SHARED_LIBSest coché, je produis un ensemble de fichiers .lib et .dll. Lors de la liaison avec ces fichiers .lib, le programme se compile, mais lors de l'exécution se plaint qu'il ne peut pas trouver gtest.dll.

Quelles sont les différences entre une bibliothèque SHAREDet une SHAREDbibliothèque not , et si je choisis non partagé, pourquoi cela ne fonctionne-t-il pas? Y a-t-il une option dans le CMakeLists.txt pour mon projet qui me manque?

Chris
la source
4
Vous pouvez éviter d'inclure des sources GTest dans les vôtres en utilisant ExternalProject_Addplutôt que add_subdirectory. Voir cette réponse pour plus de détails.
Fraser
Pourquoi avons-nous accès à $ {gtest_SOURCE_DIR} dans l'exemple de solution ci-dessus? Comment / où cette variable est-elle déclarée?
dmonopoly
Oh, c'est déclaré dans gtest-1.6.0 / CMakeLists.txt: "project (gtest CXX C)" ce qui rend les variables gtest_SOURCE_DIR et gtest_BINARY_DIR disponibles.
dmonopoly
1
Que fait enable_testing()-on?
updogliu
1
@updogliu: Il active ctest et la cible 'test' (ou 'RUN_TESTS'). Il joue avec la commande cmake add_test ().
Ela782

Réponses:

76

La solution consistait à placer le répertoire source gtest comme un sous-répertoire de votre projet. J'ai inclus le CMakeLists.txt de travail ci-dessous s'il est utile à quiconque.

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 )
Chris
la source
3
Je ne suis pas sûr de ce que fait add_test (), mais cela ne semble pas entraîner l'exécution du test binaire ... Est-ce que je manque quelque chose?
weberc2
4
Ne pas battre un cheval mort mais j'ai pensé que cela valait la peine de le mentionner à nouveau. Le commentaire de Fraser ci-dessus fait un point très important: "Vous pouvez éviter d'inclure vos propres sources GTest en utilisant ExternalProject_Add plutôt que add_subdirectory." Voir la réponse et les commentaires de Fraser pour plus de détails ici: stackoverflow.com/a/9695234/1735836
Patricia
1
Dans mon cas, j'avais également besoin d'ajouter pthreadaux bibliothèques liées, en changeant l'avant-dernière ligne entarget_link_libraries(runUnitTests gtest gtest_main pthread)
panmari
3
@ weberc2 Vous devez exécuter make testpour exécuter les tests, ou exécuter à ctestpartir du répertoire de construction. Exécutez ctest -Vpour voir la sortie du test google ainsi que la ctestsortie.
Patrick
38

Voici un exemple de travail complet que je viens de tester. Il télécharge directement à partir du Web, soit une archive tar fixe, soit le dernier répertoire de 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)
user1427799
la source
7
Je ne sais pas pourquoi vous avez voté contre cela. Votre solution évite à quelqu'un d'avoir à enregistrer Google Test sur le contrôle de version. Félicitations pour votre solution.
Sal
4
L'URL que vous utilisez est maintenant cassée. Une URL à jour esthttps://github.com/google/googletest/archive/release-1.8.0.zip
oscfri
Très bonne réponse. Devrait être le numéro 1.
Mr00Anderson
1
bonne réponse! nous pouvons également utiliser à la GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG release-1.8.1place de l'URL
TingQian LI
L'URL de la dernière version de gtest est:https://github.com/google/googletest/archive/release-1.10.0.zip
vahancho le
16

Vous pouvez obtenir le meilleur des deux mondes. Il est possible d'utiliser ExternalProjectpour télécharger la source gtest puis de l'utiliser add_subdirectory()pour l'ajouter à votre build. Cela présente les avantages suivants:

  • gtest est construit dans le cadre de votre build principal, il utilise donc les mêmes indicateurs de compilateur, etc. et évite ainsi des problèmes comme ceux décrits dans la question.
  • Il n'est pas nécessaire d'ajouter les sources gtest à votre propre arborescence des sources.

Utilisé de manière normale, ExternalProject ne fera pas le téléchargement et le déballage au moment de la configuration (c'est-à-dire lorsque CMake est exécuté), mais vous pouvez le faire avec juste un peu de travail. J'ai écrit un article de blog sur la façon de faire cela, qui comprend également une implémentation généralisée qui fonctionne pour tout projet externe utilisant CMake comme système de construction, pas seulement gtest. Vous pouvez les trouver ici:

Mise à jour: cette approche fait désormais également partie de la documentation googletest .

Craig Scott
la source
2
OMI, c'est peut-être le moyen le plus propre d'implémenter le test Google avec un projet CMake. Je souhaite que les modérateurs accordent plus d'attention au contenu et à la qualité des réponses.
NameRakes
Le module généralisé DownloadProject.cmake lié est génial. Cela ressemble à la base pour que cmake ait un système de gestion de paquets où tout ce dont j'ai besoin est une liste de liens vers des URL github compatibles CMake.
Josh Peak
13

Très probablement, la différence dans les options du compilateur entre votre binaire de test et la bibliothèque de test Google est à blâmer sur de telles erreurs. C'est pourquoi il est recommandé d'intégrer Google Test sous la forme source et de le construire avec vos tests. C'est très facile à faire dans CMake. Vous ADD_SUBDIRECTORYappelez simplement avec le chemin d'accès à la racine gtest et vous pouvez ensuite utiliser les cibles de bibliothèque publique ( gtestet gtest_main) définies ici. Il y a plus d'informations générales dans ce fil CMake dans le groupe googletestframework.

[modifier] L' BUILD_SHARED_LIBSoption n'est efficace que sur Windows pour le moment. Il spécifie le type de bibliothèques que vous souhaitez que CMake crée. Si vous le définissez sur ON, CMake les construira en tant que DLL par opposition à des bibliothèques statiques. Dans ce cas, vous devez construire vos tests avec -DGTEST_LINKED_AS_SHARED_LIBRARY = 1 et copier les fichiers DLL produits par CMake dans le répertoire avec votre binaire de test (CMake les place par défaut dans un répertoire de sortie séparé). À moins que gtest dans la bibliothèque statique ne fonctionne pour vous, il est plus facile de ne pas définir cette option.

VladLosev
la source
1
Un grand merci, je ne savais pas que vous pouviez créer des projets complètement séparés dans les mêmes CMakeLists comme ça. Je peux maintenant dire en toute sécurité que EXPECT_EQ (1.0 == 1.0) réussit et EXPECT_EQ (0.0 == 1.0) échoue. Il est maintenant temps pour plus de vrais tests ...
Chris
2

Après avoir creusé davantage, je pense que mon problème est lié au type de bibliothèque dans laquelle je construis gtest. Lors de la construction de gtest avec CMake, si BUILD_SHARED_LIBS n'est pas coché et que je lie mon programme à ces fichiers .lib, j'obtiens les erreurs mentionnées ci-dessus. Cependant, si BUILD_SHARED_LIBS est coché, je produis un ensemble de fichiers .lib et .dll. Lors de la liaison avec ces fichiers .lib, le programme compile, mais lorsqu'il est exécuté, il se plaint qu'il ne peut pas trouver gtest.dll.

En effet, vous devez ajouter -DGTEST_LINKED_AS_SHARED_LIBRARY = 1 aux définitions du compilateur dans votre projet si vous souhaitez utiliser gtest comme bibliothèque partagée.

Vous pouvez également utiliser les bibliothèques statiques, à condition de les compiler avec l'option gtest_force_shared_crt activée pour éliminer les erreurs que vous avez vues.

J'aime la bibliothèque mais l'ajouter au projet est une vraie douleur. Et vous n'avez aucune chance de le faire correctement à moins de creuser (et de pirater) les fichiers cmake gtest. La honte. En particulier, je n'aime pas l'idée d'ajouter gtest comme source. :)

Slava
la source
1

L'OP utilise Windows, et un moyen beaucoup plus simple d'utiliser GTest aujourd'hui est avec vcpkg + cmake.


Installez vcpkg selon https://github.com/microsoft/vcpkg et assurez-vous que vous pouvez exécuter à vcpkgpartir de la ligne cmd. Prenez note du dossier d'installation de vcpkg, par exemple. C:\bin\programs\vcpkg.

Installez gtest en utilisant vcpkg install gtest: cela téléchargera, compilera et installera GTest.

Utilisez un CmakeLists.txt comme ci-dessous: notez que nous pouvons utiliser des cibles au lieu d'inclure des dossiers.

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)

Exécutez cmake avec: (modifiez le dossier vcpkg si nécessaire et assurez-vous que le chemin d'accès au fichier de chaîne d'outils vcpkg.cmake est correct)

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

et construisez en utilisant cmake --build buildcomme d'habitude. Notez que vcpkg copiera également le fichier gtest (d) .dll / gtest (d) _main.dll requis du dossier d'installation vers les dossiers Debug / Release.

Testez avec cd build & ctest.

Daniele
la source
0

Vos solutions et celles de VladLosevs sont probablement meilleures que les miennes. Si vous voulez une solution de force brute, cependant, essayez ceci:

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)
Torleif
la source
0

Le CMakeLists.txt le plus simple que j'ai distillé à partir des réponses dans ce fil et des essais et erreurs est:

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 devrait déjà être installé sur votre système.

AlexBriskin
la source
Ce n'est vraiment pas une bonne pratique d'ajouter une bibliothèque comme celle-ci dans CMake. L'un des principaux objectifs de cmake est de ne jamais avoir à faire d'hypothèses comme "Cette bibliothèque devrait déjà être installée ...". CMake vérifie que la bibliothèque est là, et sinon, une erreur est générée.
Adrien BARRAL
0

Tout comme une mise à jour du commentaire de @ Patricia dans la réponse acceptée et du commentaire de @ Fraser pour la question d'origine, si vous avez accès à CMake 3.11+, vous pouvez utiliser la fonction FetchContent de CMake .

La page FetchContent de CMake utilise googletest comme exemple!

J'ai fourni une petite modification de la réponse acceptée:

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)

Vous pouvez utiliser la INTERFACE_SYSTEM_INCLUDE_DIRECTORIESpropriété target des cibles gtest et gtest_main telles qu'elles sont définies dans le script google test CMakeLists.txt .

M. Splat
la source
Dans CMake> = v3.14, vous pouvez renoncer à l'explicite target_include_directorieset utiliser à la FetchContent_MakeAvailable(googletest)place. Cela remplira le contenu et l'ajoutera à la construction principale. CMake FetchContent - plus d'infos
67hz
0

J'ai décidé de jeter quelque chose de générique ensemble très rapidement, démontrant une façon différente de le faire que les réponses précédemment publiées, dans l'espoir que cela puisse aider quelqu'un. Ce qui suit a fonctionné pour moi sur mon mac. Tout d'abord, j'ai exécuté des commandes de configuration pour gtests. Je viens d'utiliser un script que j'ai trouvé pour tout configurer.

#!/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 

Ensuite, j'ai créé une structure de dossiers simple et écrit quelques classes rapides

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

J'ai créé un CMakeLists.txt de niveau supérieur pour le dossier utils et un CMakeLists.txt pour le dossier tests

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)

Il s'agit du CMakeLists.txt dans le dossier tests

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})

Ensuite, il ne reste plus qu'à écrire un exemple de gtest et gtest main

échantillon 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

échantillon gtest main

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

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

Je peux ensuite compiler et exécuter gtests avec les commandes suivantes à partir du dossier utils

cmake .
make 
./tests/gtestProject
Montre de l'amour
la source