Cómo comenzar a trabajar con GTest y CMake

125

Recientemente me han vendido el uso de CMake para compilar mis proyectos de C ++, y ahora me gustaría comenzar a escribir algunas pruebas unitarias para mi código. Decidí usar la utilidad Google Test para ayudar con esto, pero necesito ayuda para comenzar.

Todo el día he estado leyendo varias guías y ejemplos que incluyen el Manual , una introducción en IBM y algunas preguntas sobre SO ( aquí y aquí ), así como otras fuentes de las que he perdido la noción. Me doy cuenta de que hay mucho por ahí, pero de alguna manera todavía tengo dificultades.

Actualmente estoy tratando de implementar la prueba más básica, para confirmar que he compilado / instalado gtest correctamente y no funciona. El único archivo fuente (testgtest.cpp) se toma casi exactamente de esta respuesta anterior:

#include <iostream>

#include "gtest/gtest.h"

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

y mi CMakeLists.txt asociado es el siguiente:

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
)

Tenga en cuenta que he elegido vincular contra gtest_main en lugar de proporcionar el principal al final del archivo cpp, ya que creo que esto me permitirá escalar las pruebas más fácilmente a varios archivos.

Cuando construyo el archivo .sln generado (en Visual C ++ 2010 Express) desafortunadamente obtengo una larga lista de errores del formulario

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)

lo que creo que significa que no estoy enlazando con éxito a las bibliotecas gtest. Me he asegurado de que al vincular contra las bibliotecas de depuración, he intentado compilar en modo de depuración.

EDITAR

Después de investigar un poco más, creo que mi problema tiene algo que ver con el tipo de biblioteca en la que estoy construyendo gtest. Cuando construyo gtest con CMake, si no BUILD_SHARED_LIBSestá marcado, y vinculo mi programa con estos archivos .lib, recibo los errores mencionados anteriormente. Sin embargo, si BUILD_SHARED_LIBSestá marcado, entonces produzco un conjunto de archivos .lib y .dll. Cuando ahora se vincula con estos archivos .lib, el programa compila, pero cuando se ejecuta se queja de que no puede encontrar gtest.dll.

¿Cuáles son las diferencias entre una SHAREDy una SHAREDbiblioteca no , y si elijo no compartido, ¿por qué no funciona? ¿Hay una opción en CMakeLists.txt para mi proyecto que me falta?

Chris
fuente
44
Puede evitar incluir fuentes de GTest en su propio uso en ExternalProject_Addlugar de hacerlo add_subdirectory. Vea esta respuesta para más detalles.
Fraser
¿Por qué tenemos acceso a $ {gtest_SOURCE_DIR} en el ejemplo de solución anterior? ¿Cómo / dónde se declara esa variable?
dmonopolio
Oh, se declara en gtest-1.6.0 / CMakeLists.txt: "project (gtest CXX C)" que hace que las variables gtest_SOURCE_DIR y gtest_BINARY_DIR estén disponibles.
dmonopolio
1
¿Qué enable_testing()hacer?
updogliu
1
@updogliu: Habilita ctest y el objetivo 'test' (o 'RUN_TESTS'). Se juega junto con el comando add_test () cmake.
Ela782

Respuestas:

76

La solución consistía en colocar el directorio fuente de gtest como un subdirectorio de su proyecto. He incluido el CMakeLists.txt de trabajo a continuación si es útil para alguien.

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
fuente
3
No estoy seguro de lo que hace add_test (), pero no parece que se ejecute el binario de prueba ... ¿Me estoy perdiendo algo?
weberc2
44
No para vencer a un caballo muerto, pero pensé que valía la pena mencionarlo nuevamente. El comentario anterior de Fraser hace un punto muy importante: "Puede evitar incluir las fuentes de GTest en su cuenta utilizando ExternalProject_Add en lugar de add_subdirectory". Consulte la respuesta y los comentarios de Fraser para obtener más detalles aquí: stackoverflow.com/a/9695234/1735836
Patricia
1
En mi caso, también necesitaba agregar pthreada las bibliotecas vinculadas, cambiando la segunda última línea atarget_link_libraries(runUnitTests gtest gtest_main pthread)
panmari
3
@ weberc2 Debe ejecutar make testpara ejecutar las pruebas, o ejecutar ctestdesde el directorio de compilación. Ejecute ctest -Vpara ver la salida de prueba de google, así como la ctestsalida.
Patrick
38

Aquí hay un ejemplo de trabajo completo que acabo de probar. Se descarga directamente desde la web, ya sea un tarball fijo o el último directorio de subversión.

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)
usuario1427799
fuente
77
No sé por qué te votaron negativamente por esto. Su solución evita que alguien tenga que verificar en Google Test el control de versiones. Felicitaciones por su solución.
Sal
44
La URL que usa ahora está rota. Una URL actualizada eshttps://github.com/google/googletest/archive/release-1.8.0.zip
oscfri
Gran respuesta. Debería ser el número 1.
Mr00Anderson
1
gran respuesta! también podemos usar en GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG release-1.8.1lugar de URL
TingQian LI
La URL de la última versión más reciente es:https://github.com/google/googletest/archive/release-1.10.0.zip
vahancho
16

Puedes obtener lo mejor de ambos mundos. Es posible usarlo ExternalProjectpara descargar la fuente gtest y luego usarlo add_subdirectory()para agregarlo a su compilación. Esto tiene las siguientes ventajas:

  • gtest se crea como parte de su compilación principal, por lo que utiliza los mismos indicadores del compilador, etc. y, por lo tanto, evita problemas como los descritos en la pregunta.
  • No es necesario agregar las fuentes gtest a su propio árbol de fuentes.

Utilizado de la manera normal, ExternalProject no realizará la descarga y el desempaquetado en el momento de la configuración (es decir, cuando se ejecuta CMake), pero puede hacerlo con solo un poco de trabajo. He escrito una publicación de blog sobre cómo hacer esto, que también incluye una implementación generalizada que funciona para cualquier proyecto externo que use CMake como su sistema de compilación, no solo gtest. Usted puede encontrarlos aquí:

Actualización: este enfoque ahora también forma parte de la documentación de googletest .

Craig Scott
fuente
2
En mi opinión, esta es quizás la forma más limpia de implementar la prueba de Google con un proyecto CMake. Deseo que los moderadores presten más atención al contenido y la calidad de las respuestas.
NameRakes
El módulo generalizado DownloadProject.cmake vinculado es excelente. Se siente como la base para que cmake tenga un sistema de administración de paquetes donde todo lo que necesito es una lista de enlaces a las URL de github compatibles con CMake.
Josh Peak
13

Lo más probable es que la diferencia en las opciones del compilador entre el binario de prueba y la biblioteca de Google Test sea la culpa de dichos errores. Es por eso que se recomienda traer Google Test en el formulario fuente y compilarlo junto con sus pruebas. Es muy fácil de hacer en CMake. Simplemente invoque ADD_SUBDIRECTORYcon la ruta a la raíz gtest y luego puede usar los objetivos de la biblioteca pública ( gtesty gtest_main) definidos allí. Hay más información de fondo en este hilo de CMake en el grupo googletestframework.

[editar] La BUILD_SHARED_LIBSopción solo es efectiva en Windows por ahora. Especifica el tipo de bibliotecas que desea que CMake construya. Si lo configura ON, CMake los compilará como archivos DLL en lugar de bibliotecas estáticas. En ese caso, debe crear sus pruebas con -DGTEST_LINKED_AS_SHARED_LIBRARY = 1 y copiar los archivos DLL producidos por CMake en el directorio con su binario de prueba (CMake los coloca en un directorio de salida separado de forma predeterminada). A menos que gtest en lib estática no funcione para usted, es más fácil no configurar esa opción.

VladLosev
fuente
1
Muchas gracias, no me di cuenta de que podrías construir proyectos completamente separados dentro de las mismas CMakeLists como esa. Ahora puedo decir con seguridad que EXPECT_EQ (1.0 == 1.0) pasa y EXPECT_EQ (0.0 == 1.0) falla. Ahora es el momento para más pruebas reales ...
Chris
2

Después de investigar un poco más, creo que mi problema tiene algo que ver con el tipo de biblioteca en la que estoy construyendo gtest. Al compilar gtest con CMake, si BUILD_SHARED_LIBS no está marcado, y vinculo mi programa con estos archivos .lib, obtengo los errores mencionados anteriormente. Sin embargo, si BUILD_SHARED_LIBS está marcado, entonces produzco un conjunto de archivos .lib y .dll. Cuando ahora se vincula con estos archivos .lib, el programa compila, pero cuando se ejecuta se queja de que no puede encontrar gtest.dll.

Esto se debe a que debe agregar -DGTEST_LINKED_AS_SHARED_LIBRARY = 1 a las definiciones del compilador en su proyecto si desea utilizar gtest como biblioteca compartida.

También puede usar las bibliotecas estáticas, siempre que la haya compilado con la opción gtest_force_shared_crt para eliminar los errores que haya visto.

Me gusta la biblioteca, pero agregarla al proyecto es un verdadero dolor. Y no tiene la oportunidad de hacerlo correctamente a menos que profundice (y piratee) en los archivos cmake de gtest. Vergüenza. En particular, no me gusta la idea de agregar gtest como fuente. :)

Slava
fuente
1

El OP está usando Windows, y una forma mucho más fácil de usar GTest hoy es con vcpkg + cmake.


Instale vcpkg según https://github.com/microsoft/vcpkg , y asegúrese de que puede ejecutar vcpkgdesde la línea cmd. Tome nota de la carpeta de instalación de vcpkg, por ejemplo. C:\bin\programs\vcpkg.

Instale gtest usando vcpkg install gtest: esto descargará, compilará e instalará GTest.

Use CmakeLists.txt como se muestra a continuación: tenga en cuenta que podemos usar objetivos en lugar de incluir carpetas.

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)

Ejecute cmake con: (edite la carpeta vcpkg si es necesario y asegúrese de que la ruta al archivo vcpkg.cmake toolchain sea correcta)

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

y construir usando cmake --build buildcomo de costumbre. Tenga en cuenta que, vcpkg también copiará el gtest (d) .dll / gtest (d) _main.dll requerido de la carpeta de instalación a las carpetas Debug / Release.

Prueba con cd build & ctest.

Daniele
fuente
0

Las soluciones suyas y de VladLosevs son probablemente mejores que las mías. Sin embargo, si desea una solución de fuerza bruta, intente esto:

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
fuente
0

El CMakeLists.txt más simple que extraje de las respuestas en este hilo y algunas pruebas y errores son:

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 ya debería estar instalado en su sistema.

AlexBriskin
fuente
Realmente no es una buena práctica agregar una biblioteca como esta en CMake. Uno de los principales objetivos de cmake es nunca tener que hacer suposiciones como "Esta biblioteca ya debería estar instalada ...". CMake compruebe que la biblioteca está aquí, y si no, se produce un error.
Adrien BARRAL
0

Del mismo modo que una actualización del comentario de @ Patricia en la respuesta aceptada y el comentario de @ Fraser para la pregunta original, si tiene acceso a CMake 3.11+, puede utilizar la función FetchContent de CMake .

¡La página FetchContent de CMake utiliza googletest como ejemplo!

He proporcionado una pequeña modificación de la respuesta aceptada:

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)

Puede utilizar la INTERFACE_SYSTEM_INCLUDE_DIRECTORIESpropiedad de destino de los objetivos gtest y gtest_main tal como se establecen en la prueba de google CMakeLists.txt script de .

Señor splat
fuente
En CMake> = v3.14 puede renunciar a lo explícito target_include_directoriesy usar FetchContent_MakeAvailable(googletest)en su lugar. Esto completará el contenido y lo agregará a la compilación principal. CMake FetchContent - más información
67hz
0

Decidí lanzar algo genérico juntos rápidamente demostrando una forma diferente de hacerlo que las respuestas publicadas anteriormente, con la esperanza de que pueda ayudar a alguien. Lo siguiente funcionó para mí en mi mac. En primer lugar, ejecuté los comandos de configuración para gtests. Acabo de usar un script que encontré para configurar todo.

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

Luego, hice una estructura de carpetas simple y escribí algunas clases rápidas

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

Hice un CMakeLists.txt de nivel superior para la carpeta de utilidades, y un CMakeLists.txt para la carpeta de pruebas

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)

Este es el CMakeLists.txt en la carpeta de pruebas

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

Entonces todo lo que queda es escribir una muestra gtest y gtest main

muestra 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

muestra gtest principal

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

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

Luego puedo compilar y ejecutar gtests con los siguientes comandos desde la carpeta utils

cmake .
make 
./tests/gtestProject
Muestra amor
fuente