Come iniziare a lavorare con GTest e CMake


125

Recentemente sono stato convinto dell'utilizzo di CMake per la compilazione dei miei progetti C ++ e ora vorrei iniziare a scrivere alcuni unit test per il mio codice. Ho deciso di utilizzare l'utilità di test di Google per aiutare con questo, ma ho bisogno di aiuto per iniziare.

Per tutto il giorno ho letto varie guide ed esempi includono il Primer , un'introduzione all'IBM e alcune domande su SO ( qui e qui ) così come altre fonti di cui ho perso le tracce. Mi rendo conto che c'è molto là fuori, ma in qualche modo ho ancora difficoltà.

Attualmente sto cercando di implementare il test più semplice, per confermare che ho compilato / installato gtest correttamente e non funziona. L'unico file sorgente (testgtest.cpp) è preso quasi esattamente da questa risposta precedente:

#include <iostream>

#include "gtest/gtest.h"

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

e il mio CMakeLists.txt associato è il seguente:

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
)

Nota che ho scelto di collegarmi a gtest_main invece di fornire il main alla fine del file cpp poiché credo che questo mi permetterà di scalare il test più facilmente a più file.

Durante la creazione del file .sln generato (in Visual C ++ 2010 Express) purtroppo ottengo un lungo elenco di errori del modulo

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)

che penso significhi che non mi sto collegando con successo alle librerie gtest. Mi sono assicurato che quando mi collegavo alle librerie di debug, ho provato a compilare in modalità di debug.

MODIFICARE

Dopo aver approfondito ulteriormente, penso che il mio problema abbia a che fare con il tipo di libreria in cui sto costruendo gtest. Quando creo gtest con CMake, se BUILD_SHARED_LIBSnon è selezionato e collego il mio programma a questi file .lib ottengo gli errori sopra menzionati. Tuttavia, se BUILD_SHARED_LIBSè selezionato, produco un set di file .lib e .dll. Quando ora si collega a questi file .lib il programma compila, ma quando viene eseguito si lamenta di non riuscire a trovare gtest.dll.

Quali sono le differenze tra una SHAREDe una SHAREDlibreria non e se scelgo non condivisa, perché non funziona? C'è un'opzione nel CMakeLists.txt per il mio progetto che mi manca?


4
Puoi evitare di includere sorgenti GTest nel tuo usando ExternalProject_Addinvece di add_subdirectory. Vedi questa risposta per i dettagli.
Fraser

Perché abbiamo accesso a $ {gtest_SOURCE_DIR} nell'esempio di soluzione sopra? Come / dove viene dichiarata quella variabile?
dmonopoly

Oh, è dichiarato in gtest-1.6.0 / CMakeLists.txt: "project (gtest CXX C)" che rende disponibili le variabili gtest_SOURCE_DIR e gtest_BINARY_DIR.
dmonopoly

1
Cosa fa enable_testing()?
aggiornamento

1
@updogliu: Abilita ctest e il target 'test' (o 'RUN_TESTS'). Funziona insieme al comando add_test () cmake.
Ela782

Risposte:


76

La soluzione prevedeva l'inserimento della directory dei sorgenti gtest come sottodirectory del progetto. Ho incluso il CMakeLists.txt funzionante di seguito se è utile a chiunque.

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
Non sono sicuro di cosa faccia add_test (), ma non sembra che il binario di prova sia in esecuzione ... Mi manca qualcosa?
weberc2

4
Non per battere un cavallo morto, ma ho pensato che valesse la pena menzionarlo di nuovo. Il commento di Fraser sopra fa un punto molto importante: "Puoi evitare di includere sorgenti GTest nel tuo utilizzando ExternalProject_Add invece di add_subdirectory." Vedi la risposta ei commenti di Fraser per i dettagli qui: stackoverflow.com/a/9695234/1735836
Patricia

1
Nel mio caso, avevo anche bisogno di aggiungere pthreadalle librerie collegate, cambiando la penultima riga intarget_link_libraries(runUnitTests gtest gtest_main pthread)
panmari

3
@ weberc2 Devi eseguire make testper eseguire i test o eseguire ctestdalla directory di build. Corri ctest -Vper vedere l'output di test di Google e l' ctestoutput.
Patrick

38

Ecco un esempio funzionante completo che ho appena testato. Si scarica direttamente dal web, un tarball fisso o l'ultima directory di 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
Non so perché sei stato svalutato per questo. La tua soluzione impedisce a qualcuno di dover controllare in Google Test il controllo della versione. Complimenti per la tua soluzione.
Sal

4
L'URL che utilizzi ora è danneggiato. Un URL aggiornato èhttps://github.com/google/googletest/archive/release-1.8.0.zip
oscfri

Bella risposta. Dovrebbe essere il numero 1.
Mr00 Anderson il

1
ottima risposta! inoltre possiamo usare al GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG release-1.8.1posto dell'URL
TingQian LI

L'URL dell'ultima versione di gtest è:https://github.com/google/googletest/archive/release-1.10.0.zip
vahancho

16

Puoi ottenere il meglio da entrambi i mondi. È possibile utilizzare ExternalProjectper scaricare il sorgente gtest e quindi utilizzarlo add_subdirectory()per aggiungerlo alla build. Ciò presenta i seguenti vantaggi:

  • gtest è compilato come parte della build principale, quindi utilizza gli stessi flag del compilatore, ecc. e quindi evita problemi come quelli descritti nella domanda.
  • Non è necessario aggiungere i sorgenti gtest al proprio albero dei sorgenti.

Usato in modo normale, ExternalProject non eseguirà il download e lo spacchettamento al momento della configurazione (cioè quando CMake viene eseguito), ma puoi farlo con un po 'di lavoro. Ho scritto un post sul blog su come farlo che include anche un'implementazione generalizzata che funziona per qualsiasi progetto esterno che utilizza CMake come sistema di compilazione, non solo gtest. Li puoi trovare qui:

Aggiornamento: questo approccio ora fa anche parte della documentazione di googletest .


2
IMO, questo è forse il modo più pulito per implementare il test di Google con un progetto CMake. Vorrei che i moderatori prestassero maggiore attenzione al contenuto e alla qualità delle risposte.
NameRakes

Il modulo generalizzato DownloadProject.cmake collegato è fantastico. Sembra che la base per cmake abbia un sistema di gestione dei pacchetti in cui tutto ciò di cui ho bisogno è un elenco di collegamenti agli URL GitHub compatibili con CMake.
Josh Peak,

13

Molto probabilmente, la differenza nelle opzioni del compilatore tra il tuo binario di prova e la libreria di Google Test è da incolpare di tali errori. Ecco perché si consiglia di inserire Google Test nel modulo di origine e di crearlo insieme ai test. È molto facile da fare in CMake. Devi solo invocare ADD_SUBDIRECTORYcon il percorso della radice gtest e quindi puoi utilizzare le destinazioni della libreria pubblica ( gteste gtest_main) definite lì. Sono disponibili ulteriori informazioni di base in questo thread CMake nel gruppo googletestframework.

[modifica] L' BUILD_SHARED_LIBSopzione è valida solo su Windows per ora. Specifica il tipo di librerie che si desidera vengano create da CMake. Se lo imposti ON, CMake le creerà come DLL anziché come librerie statiche. In tal caso devi creare i tuoi test con -DGTEST_LINKED_AS_SHARED_LIBRARY = 1 e copiare i file DLL prodotti da CMake nella directory con il tuo binario di prova (CMake li inserisce in una directory di output separata per impostazione predefinita). A meno che gtest in static lib non funzioni per te, è più facile non impostare questa opzione.


1
Grazie mille, non sapevo che potevi costruire progetti completamente separati all'interno delle stesse CMakeList come quella. Ora posso tranquillamente affermare che EXPECT_EQ (1.0 == 1.0) ha esito positivo e EXPECT_EQ (0.0 == 1.0) non riesce. Ora è il momento per test più reali ...
Chris

2

Dopo aver approfondito ulteriormente, penso che il mio problema abbia a che fare con il tipo di libreria in cui sto costruendo gtest. Quando creo gtest con CMake, se BUILD_SHARED_LIBS è deselezionato e collego il mio programma a questi file .lib ottengo gli errori sopra menzionati. Tuttavia, se BUILD_SHARED_LIBS è selezionato, produco una serie di file .lib e .dll. Quando ora si collega a questi file .lib il programma compila, ma quando viene eseguito si lamenta di non riuscire a trovare gtest.dll.

Questo perché devi aggiungere -DGTEST_LINKED_AS_SHARED_LIBRARY = 1 alle definizioni del compilatore nel tuo progetto se vuoi usare gtest come libreria condivisa.

Puoi anche usare le librerie statiche, a patto di averle compilate con l'opzione gtest_force_shared_crt attiva per eliminare gli errori che hai visto.

Mi piace la libreria ma aggiungerla al progetto è una vera seccatura. E non hai alcuna possibilità di farlo bene a meno che non scavare (e hackerare) nei file gtest cmake. Vergogna. In particolare non mi piace l'idea di aggiungere gtest come sorgente. :)


1

L'OP utilizza Windows e un modo molto più semplice di usare GTest oggi è con vcpkg + cmake.


Installa vcpkg come da https://github.com/microsoft/vcpkg e assicurati di poter eseguire vcpkgdalla riga cmd. Prendi nota della cartella di installazione di vcpkg, ad es. C:\bin\programs\vcpkg.

Installa gtest usando vcpkg install gtest: questo scaricherà, compilerà e installerà GTest.

Usa un CmakeLists.txt come di seguito: nota che possiamo usare target invece di includere cartelle.

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)

Esegui cmake con: (modifica la cartella vcpkg se necessario e assicurati che il percorso del file toolchain vcpkg.cmake sia corretto)

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

e costruire usando cmake --build buildcome al solito. Notare che, vcpkg copierà anche il gtest (d) .dll / gtest (d) _main.dll richiesto dalla cartella di installazione alle cartelle Debug / Release.

Prova con cd build & ctest.


0

Le soluzioni tue e di VladLosev sono probabilmente migliori delle mie. Se vuoi una soluzione di forza bruta, tuttavia, prova questo:

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

Il CMakeLists.txt più semplice che ho distillato dalle risposte in questo thread e alcuni tentativi ed errori è:

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 dovrebbe essere già installato sul tuo sistema.


Non è davvero una buona pratica aggiungere una libreria come questa in CMake. Uno degli obiettivi principali di cmake è di non dover mai fare supposizioni come "Questa libreria dovrebbe essere già installata ...". CMake controlla che la libreria sia qui e, in caso contrario, viene generato un errore.
Adrien BARRAL

0

Proprio come un aggiornamento al commento di @ Patricia nella risposta accettata e al commento di @ Fraser per la domanda originale, se hai accesso a CMake 3.11+ puoi utilizzare la funzione FetchContent di CMake .

La pagina FetchContent di CMake utilizza googletest come esempio!

Ho fornito una piccola modifica della risposta accettata:

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)

Puoi utilizzare la INTERFACE_SYSTEM_INCLUDE_DIRECTORIESproprietà target dei target gtest e gtest_main così come sono impostati nello script CMakeLists.txt di Google test .


In CMake> = v3.14 puoi rinunciare all'esplicito target_include_directoriese usare FetchContent_MakeAvailable(googletest)invece. Questo popolerà il contenuto e lo aggiungerà alla build principale. CMake FetchContent - ulteriori informazioni
67 Hz

0

Ho deciso di mettere insieme qualcosa di generico molto velocemente dimostrando un modo diverso di farlo rispetto alle risposte precedentemente pubblicate, nella speranza che potesse aiutare qualcuno. Quanto segue ha funzionato per me sul mio Mac. Per prima cosa ho eseguito i comandi di installazione per gtests. Ho appena usato uno script che ho trovato per impostare tutto.

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

Successivamente, ho creato una semplice struttura di cartelle e ho scritto alcune lezioni veloci

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

Ho creato un CMakeLists.txt di primo livello per la cartella utils e un CMakeLists.txt per la cartella dei test

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)

Questo è il CMakeLists.txt nella cartella dei test

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

Quindi non resta che scrivere un esempio di gtest e gtest main

esempio 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

campione gtest main

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

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

Posso quindi compilare ed eseguire gtests con i seguenti comandi dalla cartella utils

cmake .
make 
./tests/gtestProject
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.