Como começar a trabalhar com o GTest e o CMake

125

Recentemente, vendi o CMake para compilar meus projetos C ++ e agora gostaria de começar a escrever alguns testes de unidade para o meu código. Decidi usar o utilitário de teste do Google para ajudar com isso, mas preciso de ajuda para começar.

Durante todo o dia, li vários guias e exemplos incluem o Primer , uma introdução na IBM e algumas perguntas sobre SO ( aqui e aqui ), além de outras fontes que perdi de vista. Sei que há muito por aí, mas de alguma forma ainda estou tendo dificuldades.

Atualmente, estou tentando implementar o teste mais básico, para confirmar se compilei / instalei o gtest corretamente e não está funcionando. O único arquivo de origem (testgtest.cpp) é obtido quase exatamente nesta resposta anterior:

#include <iostream>

#include "gtest/gtest.h"

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

e meu CMakeLists.txt associado é o seguinte:

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
)

Observe que eu escolhi vincular o gtest_main em vez de fornecer o principal no final do arquivo cpp, pois acredito que isso me permitirá escalar os testes com mais facilidade para vários arquivos.

Ao criar o arquivo .sln gerado (no Visual C ++ 2010 Express), infelizmente, recebo uma longa lista de erros do formulário

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)

o que acho que significa que não estou vinculando com êxito às bibliotecas gtest. Eu verifiquei que, ao vincular as bibliotecas de depuração, tentei criar no modo de depuração.

EDITAR

Depois de pesquisar um pouco mais, acho que meu problema está relacionado ao tipo de biblioteca na qual estou construindo o gtest. Ao criar o gtest com o CMake, seBUILD_SHARED_LIBS estiver desmarcado, e vinculo meu programa a esses arquivos .lib, obtenho os erros mencionados acima. No entanto, se BUILD_SHARED_LIBSestiver marcado, produzo um conjunto de arquivos .lib e .dll. Quando agora vincula esses arquivos .lib, o programa é compilado, mas quando executado reclama que não foi possível encontrar o gtest.dll.

Quais são as diferenças entre uma biblioteca SHAREDe uma não SHAREDe, se eu optar por não compartilhar, por que não funciona? Existe uma opção no CMakeLists.txt para o meu projeto que está faltando?

Chris
fonte
4
Você pode evitar a inclusão de fontes GTest por conta própria usando em ExternalProject_Addvez de add_subdirectory. Veja esta resposta para detalhes.
Fraser
Por que temos acesso a $ {gtest_SOURCE_DIR} no exemplo de solução acima? Como / onde essa variável é declarada?
N
Ah, é declarado em gtest-1.6.0 / CMakeLists.txt: "projeto (gtest CXX C)", que disponibiliza as variáveis ​​gtest_SOURCE_DIR e gtest_BINARY_DIR.
Dmonopoly
1
O que enable_testing()faz?
updogliu
1
@updogliu: habilita o ctest e o destino 'test' (ou 'RUN_TESTS'). Ele toca junto com o comando add_test () cmake.
precisa saber é o seguinte

Respostas:

76

A solução envolveu colocar o diretório de origem gtest como um subdiretório do seu projeto. Eu incluí o CMakeLists.txt que funciona abaixo, se for útil para qualquer pessoa.

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
fonte
3
Não tenho certeza do que o add_test () faz, mas parece não resultar na execução binária do teste ... Estou perdendo alguma coisa?
Weberc2 29/03
4
Para não bater em um cavalo morto, mas achei que valia a pena mencionar novamente. O comentário de Fraser acima menciona um ponto muito importante: "Você pode evitar a inclusão de fontes GTest por conta própria usando ExternalProject_Add em vez de add_subdirectory." Veja a resposta e os comentários de Fraser para obter detalhes aqui: stackoverflow.com/a/9695234/1735836
Patricia
1
No meu caso, eu também precisava para adicionar pthreadàs bibliotecas vinculadas, alterando o segundo última linha paratarget_link_libraries(runUnitTests gtest gtest_main pthread)
panmari
3
@ weberc2 É necessário executar make testpara executar os testes ou executar a ctestpartir do diretório build. Corra ctest -Vpara ver a saída do teste do google e também a ctestsaída.
21416 Patrick
38

Aqui está um exemplo completo de trabalho que acabei de testar. Ele baixa diretamente da web, um tarball fixo ou o diretório mais recente do 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
fonte
7
Não sei por que você foi votado para isso. Sua solução impede que alguém precise fazer o check-in do Google Test no controle de versão. Parabéns pela sua solução.
Sal
4
O URL que você usa agora está quebrado. Uma URL atualizada éhttps://github.com/google/googletest/archive/release-1.8.0.zip
oscfri 26/06
Ótima resposta. Deve ser o número 1. #
Mr00Anderson 14/11
1
greate resposta! Também podemos usar GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG release-1.8.1, em vez de URL
TingQian LI
O URL da última versão do gtest é:https://github.com/google/googletest/archive/release-1.10.0.zip
vahancho 02/07
16

Você pode obter o melhor dos dois mundos. É possível usar ExternalProjectpara baixar a fonte gtest e depois add_subdirectory()adicioná-la à sua compilação. Isso tem as seguintes vantagens:

  • O gtest é construído como parte da sua compilação principal, portanto, ele usa os mesmos sinalizadores do compilador etc. e, portanto, evita problemas como os descritos na pergunta.
  • Não há necessidade de adicionar as fontes gtest à sua própria árvore de fontes.

Utilizado da maneira normal, o ExternalProject não faz o download e a descompactação no momento da configuração (ou seja, quando o CMake é executado), mas você pode fazê-lo com apenas um pouco de trabalho. Eu escrevi um post sobre como fazer isso, que também inclui uma implementação generalizada que funciona para qualquer projeto externo que use o CMake como seu sistema de construção, e não apenas o gtest. Você pode encontra-los aqui:

Atualização: agora, essa abordagem também faz parte da documentação do googletest .

Craig Scott
fonte
2
Na IMO, essa talvez seja a maneira mais limpa de implementar o teste do Google com um projeto do CMake. Desejo que os moderadores prestem mais atenção ao conteúdo e à qualidade das respostas.
NameRakes
O módulo generalizado DownloadProject.cmake vinculado é ótimo. Parece a base para o cmake ter um sistema de gerenciamento de pacotes, onde tudo o que preciso é de uma lista de links para os URLs do github compatíveis com o CMake.
Josh Peak
13

Provavelmente, a diferença nas opções do compilador entre o binário de teste e a biblioteca de testes do Google é a responsável por esses erros. É por isso que é recomendável trazer o Google Test no formulário de origem e construí-lo junto com seus testes. É muito fácil de fazer no CMake. Você acabou de chamar ADD_SUBDIRECTORYcom o caminho para a raiz gtest e, em seguida, pode usar os destinos ( gteste gtest_main) da biblioteca pública definidos lá. Há mais informações básicas neste tópico do CMake no grupo googletestframework.

[editar] A BUILD_SHARED_LIBSopção só é eficaz no Windows por enquanto. Ele especifica o tipo de bibliotecas que você deseja que o CMake crie. Se você configurá-lo como ON, o CMake os criará como DLLs em vez de bibliotecas estáticas. Nesse caso, você deve criar seus testes com -DGTEST_LINKED_AS_SHARED_LIBRARY = 1 e copiar os arquivos DLL produzidos pelo CMake para o diretório com o binário de teste (o CMake os coloca em um diretório de saída separado por padrão). A menos que o gtest na biblioteca estática não funcione para você, é mais fácil não definir essa opção.

VladLosev
fonte
1
Muito obrigado, não sabia que era possível criar projetos completamente separados dentro dos mesmos CMakeLists assim. Agora posso dizer com segurança que EXPECT_EQ (1.0 == 1.0) passa e EXPECT_EQ (0.0 == 1.0) falha. Agora tempo para testes mais reais ...
Chris
2

Depois de pesquisar um pouco mais, acho que meu problema está relacionado ao tipo de biblioteca na qual estou construindo o gtest. Ao criar o gtest com o CMake, se a opção BUILD_SHARED_LIBS estiver desmarcada e eu vincular meu programa a esses arquivos .lib, obtenho os erros mencionados acima. No entanto, se BUILD_SHARED_LIBS estiver marcado, eu produzo um conjunto de arquivos .lib e .dll. Quando agora vincula esses arquivos .lib, o programa é compilado, mas quando executado reclama que não foi possível encontrar o gtest.dll.

Isso ocorre porque você precisa adicionar -DGTEST_LINKED_AS_SHARED_LIBRARY = 1 às definições do compilador em seu projeto, se desejar usar o gtest como uma biblioteca compartilhada.

Você também pode usar as bibliotecas estáticas, desde que a tenha compilado com a opção gtest_force_shared_crt para eliminar os erros que você viu.

Gosto da biblioteca, mas adicioná-la ao projeto é uma verdadeira dor. E você não tem chance de fazer o que é certo, a menos que cavar (e invadir) os arquivos gtest cmake. Vergonha. Em particular, não gosto da ideia de adicionar gtest como fonte. :)

Slava
fonte
1

O OP está usando o Windows, e uma maneira muito mais fácil de usar o GTest hoje é com o vcpkg + cmake.


Instale o vcpkg conforme https://github.com/microsoft/vcpkg e verifique se você pode executar a vcpkgpartir da linha cmd. Anote a pasta de instalação do vcpkg, por exemplo. C:\bin\programs\vcpkg.

Instale o gtest usando vcpkg install gtest: isso fará o download, compilará e instalará o GTest.

Use um CmakeLists.txt como abaixo: observe que podemos usar destinos vez de incluir pastas.

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)

Execute cmake com: (edite a pasta vcpkg, se necessário, e verifique se o caminho para o arquivo de cadeia de ferramentas vcpkg.cmake está correto)

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

e construa usando cmake --build buildcomo de costume. Observe que o vcpkg também copiará o arquivo gtest (d) .dll / gtest (d) _main.dll necessário da pasta de instalação para as pastas Debug / Release.

Teste com cd build & ctest.

Daniele
fonte
0

As suas e as soluções de VladLosevs são provavelmente melhores que as minhas. Se você deseja uma solução de força bruta, no entanto, tente o seguinte:

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

O CMakeLists.txt mais simples que eu destilou das respostas neste tópico e algumas tentativas e erros são:

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)

O Gtest já deve estar instalado no seu sistema.

AlexBriskin
fonte
Realmente não é uma boa prática adicionar uma biblioteca como esta no CMake. Um dos principais objetivos do cmake é nunca precisar assumir como "Essas bibliotecas já devem estar instaladas ...". CMake verifique se a biblioteca está aqui e, se não estiver, um erro será gerado.
Adrien BARRAL
0

Assim como uma atualização do comentário de @ Patricia na resposta aceita e do comentário de Fraser para a pergunta original, se você tiver acesso ao CMake 3.11+, poderá usar a função FetchContent do CMake .

A página FetchContent do CMake usa googletest como exemplo!

Forneci uma pequena modificação da resposta aceita:

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)

Você pode usar a INTERFACE_SYSTEM_INCLUDE_DIRECTORIESpropriedade target dos destinos gtest e gtest_main, pois eles são definidos no script CMakeLists.txt do teste do google .

Mr. Splat
fonte
No CMake> = v3.14, você pode abrir mão do explícito target_include_directoriese usá-lo FetchContent_MakeAvailable(googletest). Isso preencherá o conteúdo e o adicionará à compilação principal. CMake FetchContent - mais informações
67hz 19/02/02
0

Decidi juntar algo genérico rapidamente, demonstrando uma maneira diferente de fazê-lo do que as respostas postadas anteriormente, na esperança de que isso possa ajudar alguém. O seguinte funcionou para mim no meu mac. Primeiramente, executei os comandos de configuração para gtests. Acabei de usar um script que encontrei para configurar tudo.

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

Em seguida, criei uma estrutura de pastas simples e escrevi algumas aulas rápidas

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

Criei um CMakeLists.txt de nível superior para a pasta utils e um CMakeLists.txt para a pasta testing

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 é o CMakeLists.txt na pasta testes

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

Tudo o que resta é escrever uma amostra gtest e gtest main

amostra 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

amostra gtest principal

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

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

Posso então compilar e executar gtests com os seguintes comandos da pasta utils

cmake .
make 
./tests/gtestProject
Mostre amor
fonte