151 votes

Comment commencer à travailler avec GTest et CMake

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 m'aider dans cette tâche, mais j'ai besoin d'aide pour commencer.

Toute la journée, j'ai lu différents guides, parmi lesquels le L'abécédaire , un introduction chez IBM et quelques questions sur le SO ( ici y ici ) ainsi que d'autres sources dont j'ai perdu la trace. Je sais qu'il y a beaucoup de choses à faire, mais j'ai toujours des difficultés.

J'essaie actuellement de mettre en place le test le plus basique, pour confirmer que j'ai bien compilé/installé gtest et qu'il 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 fichier 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 faire un lien avec gtest_main au lieu de fournir le main à la fin du fichier cpp, car je pense que cela me permettra d'étendre plus facilement les tests à 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 de la forme suivante

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 signifie, je pense, que je ne réussis pas à lier les bibliothèques gtest. Je me suis assuré que lors de l'édition de liens avec les bibliothèques de débogage, j'ai essayé de construire en mode débogage.

EDIT

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

Quelles sont les différences entre un SHARED et un pas SHARED et si je choisis de ne pas partager, pourquoi cela ne fonctionne-t-il pas ? Est-ce qu'il y a une option dans le CMakeLists.txt pour mon projet que j'ai manqué ?

4 votes

Vous pouvez éviter d'inclure les sources GTest dans les vôtres en utilisant ExternalProject_Add plutôt que add_subdirectory . Voir cette réponse pour plus de détails.

0 votes

Pourquoi avons-nous accès à ${gtest_SOURCE_DIR} dans l'exemple de solution ci-dessus ? Comment/où cette variable est-elle déclarée ?

0 votes

Oh c'est déclaré dans gtest-1.6.0/CMakeLists.txt : "project(gtest CXX C)" qui rend les variables gtest_SOURCE_DIR et gtest_BINARY_DIR disponibles.

2voto

Slava Points 399

Après avoir creusé un peu plus, je pense que mon problème est lié au type de bibliothèque dans laquelle je compile gtest. Lorsque je compile 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 l'édition de liens avec ces fichiers .lib, le programme se compile, mais lorsqu'il est exécuté, il se plaint de ne 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 en tant que 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 bien la bibliothèque mais l'ajouter au projet est une véritable plaie. Et vous n'avez aucune chance de le faire correctement à moins de creuser (et de pirater) dans les fichiers cmake de gtest. C'est dommage. En particulier, je n'aime pas l'idée d'ajouter gtest comme source :)

1voto

Mr. Splat Points 1

Juste une mise à jour du commentaire de @Patricia dans la réponse acceptée et du commentaire de @Fraser pour la question originale, si vous avez accès à CMake 3.11+, vous pouvez utiliser la fonction de CMake Extraire le contenu fonction.

La page FetchContent de CMake utilise googletest comme exemple !

J'ai apporté une petite modification à 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 le INTERFACE_SYSTEM_INCLUDE_DIRECTORIES des cibles gtest et gtest_main telles qu'elles sont définies dans le test google CMakeLists.txt script.

0voto

AlexBriskin Points 42

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

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.

0voto

ShowLove Points 116

J'ai décidé d'élaborer rapidement un document générique démontrant une méthode différente des 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 lancé les commandes d'installation de gtests. J'ai simplement utilisé 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 j'ai écrit quelques classes rapides

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

J'ai créé un fichier CMakeLists.txt de haut niveau pour le dossier utils, et un fichier 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)

Voici le fichier 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})

Il ne reste plus qu'à écrire un exemple de gtest et de 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 principal

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

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

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

cmake .
make 
./tests/gtestProject

0voto

Torleif Points 1334

Vos solutions et celles de VladLosevs sont probablement meilleures que les miennes. Si vous voulez une solution brute, 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)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X