Jak rozpocząć pracę z GTest i CMake

125

Niedawno sprzedano mi używanie CMake do kompilowania moich projektów C ++ i teraz chciałbym rozpocząć pisanie testów jednostkowych dla mojego kodu. Zdecydowałem się skorzystać z narzędzia Google Test, aby pomóc w tym, ale potrzebuję pomocy w rozpoczęciu.

Przez cały dzień czytałem różne przewodniki i przykłady, w tym Primer , wprowadzenie w IBM i kilka pytań na temat SO ( tu i tutaj ), a także inne źródła, które straciłem z oczu. Zdaję sobie sprawę, że jest tam wiele, ale jakoś nadal mam trudności.

Obecnie próbuję zaimplementować najbardziej podstawowy test, aby potwierdzić, że poprawnie skompilowałem / zainstalowałem gtest i nie działa. Jedyny plik źródłowy (testgtest.cpp) pochodzi prawie dokładnie z tej poprzedniej odpowiedzi:

#include <iostream>

#include "gtest/gtest.h"

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

a mój powiązany plik CMakeLists.txt wygląda następująco:

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
)

Zauważ, że zdecydowałem się połączyć z gtest_main zamiast podawać main na końcu pliku cpp, ponieważ uważam, że pozwoli mi to na łatwiejsze skalowanie testów do wielu plików.

Podczas budowania wygenerowanego pliku .sln (w Visual C ++ 2010 Express) otrzymuję niestety długą listę błędów formularza

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)

co, jak sądzę, oznacza, że ​​nie łączę się pomyślnie z bibliotekami gtest. Upewniłem się, że podczas łączenia się z bibliotekami debugowania próbowałem następnie budować w trybie debugowania.

EDYTOWAĆ

Po dokładniejszym zagłębieniu się, myślę, że mój problem jest związany z rodzajem biblioteki, w której buduję gtest. Podczas budowania gtest z CMake, jeśli BUILD_SHARED_LIBSjest odznaczone i łączę swój program z tymi plikami .lib, otrzymuję wymienione powyżej błędy. Jeśli jednak BUILD_SHARED_LIBSjest zaznaczone, tworzę zestaw plików .lib i .dll. Podczas łączenia z tymi plikami .lib program kompiluje, ale po uruchomieniu narzeka, że ​​nie może znaleźć pliku gtest.dll.

Jakie są różnice między biblioteką a SHAREDi nie SHARED, a jeśli wybiorę opcję nieudostępnianą, dlaczego to nie działa? Czy brakuje opcji w pliku CMakeLists.txt dla mojego projektu?

Chris
źródło
4
Możesz uniknąć włączania źródeł GTest do swoich własnych, używając ExternalProject_Addzamiast add_subdirectory. Zobacz tę odpowiedź, aby uzyskać szczegółowe informacje.
Fraser
Dlaczego mamy dostęp do $ {gtest_SOURCE_DIR} w powyższym przykładzie rozwiązania? Jak / gdzie jest deklarowana ta zmienna?
dmonopoly
Och, jest to zadeklarowane w gtest-1.6.0 / CMakeLists.txt: "project (gtest CXX C)", co powoduje, że dostępne są zmienne gtest_SOURCE_DIR i gtest_BINARY_DIR.
dmonopoly
1
Co robi enable_testing()?
updogliu
1
@updogliu: Włącza ctest i cel „test” (lub „RUN_TESTS”). Działa razem z poleceniem cmake add_test ().
Ela782

Odpowiedzi:

76

Rozwiązanie polegało na umieszczeniu katalogu źródłowego gtest jako podkatalogu twojego projektu. Załączam działający plik CMakeLists.txt poniżej, jeśli jest to pomocne dla kogoś.

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
źródło
3
Nie jestem pewien, co robi add_test (), ale wygląda na to, że test binarny nie działa ... Czy czegoś mi brakuje?
weberc2
4
Nie bić martwego konia, ale pomyślałem, że warto o tym jeszcze raz wspomnieć. Powyższy komentarz Frasera mówi o bardzo ważnym punkcie: „Możesz uniknąć włączania własnych źródeł GTest, używając ExternalProject_Add zamiast add_subdirectory”. Zobacz odpowiedź i komentarze Frasera, aby uzyskać szczegółowe informacje tutaj: stackoverflow.com/a/9695234/1735836
Patricia
1
W moim przypadku musiałem również dodać pthreaddo połączonych bibliotek, zmieniając przedostatnią linię natarget_link_libraries(runUnitTests gtest gtest_main pthread)
panmari
3
@ weberc2 Musisz uruchomić, make testaby uruchomić testy, lub uruchomić ctestz katalogu kompilacji. Uruchom, ctest -Vaby zobaczyć wyniki testu Google, a także dane ctestwyjściowe.
Patrick,
38

Oto kompletny przykład roboczy, który właśnie przetestowałem. Pobiera się bezpośrednio z sieci, albo ze stałego archiwum, albo z najnowszego katalogu 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
źródło
7
Nie wiem, dlaczego zostałeś za to odrzucony. Twoje rozwiązanie zapobiega konieczności sprawdzania w teście Google kontroli wersji. Brawa za twoje rozwiązanie.
Sal
4
Adres URL, którego używasz, jest teraz uszkodzony. https://github.com/google/googletest/archive/release-1.8.0.zip
Aktualny
Świetna odpowiedź. Powinien mieć numer 1.
Mr00Anderson
1
witaj odpowiedz! możemy również użyć GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG release-1.8.1zamiast adresu URL
TingQian LI
Adres URL najnowszego wydania gtest to:https://github.com/google/googletest/archive/release-1.10.0.zip
vahancho
16

Możesz uzyskać to, co najlepsze z obu światów. Można użyć ExternalProjectdo pobrania źródła gtest, a następnie użyć go, add_subdirectory()aby dodać je do kompilacji. Ma to następujące zalety:

  • gtest jest budowany jako część twojej głównej kompilacji, więc używa tych samych flag kompilatora itp., dzięki czemu unika problemów, jak te opisane w pytaniu.
  • Nie ma potrzeby dodawania źródeł gtest do własnego drzewa źródeł.

Używany w normalny sposób, ExternalProject nie wykona pobierania i rozpakowywania w czasie konfiguracji (tj. Gdy CMake jest uruchomiony), ale możesz to zrobić przy odrobinie pracy. Napisałem post na blogu o tym, jak to zrobić, który zawiera również uogólnioną implementację, która działa dla każdego zewnętrznego projektu, który używa CMake jako swojego systemu kompilacji, a nie tylko gtest. Znajdziesz je tutaj:

Aktualizacja: to podejście jest teraz również częścią dokumentacji googletest .

Craig Scott
źródło
2
IMO, to chyba najczystszy sposób na wdrożenie testu Google z projektem CMake. Chciałbym, żeby moderatorzy zwracali większą uwagę na treść i jakość odpowiedzi.
NameRakes
Połączony uogólniony moduł DownloadProject.cmake jest świetny. Wydaje mi się, że podstawą cmake jest posiadanie systemu zarządzania pakietami, w którym wszystko, czego potrzebuję, to lista linków do adresów URL na github zgodnych z CMake.
Josh Peak
13

Najprawdopodobniej przyczyną takich błędów jest różnica w opcjach kompilatora między testowym plikiem binarnym a biblioteką Google Test. Dlatego zaleca się wprowadzenie Google Test w formie źródłowej i zbudowanie go razem z testami. W CMake jest to bardzo łatwe. Po prostu wywołujesz ADD_SUBDIRECTORYścieżkę do katalogu głównego gtest, a następnie możesz użyć celów biblioteki publicznej ( gtesti gtest_main) zdefiniowanych tam. Więcej informacji ogólnych znajduje się w tym wątku CMake w grupie googletestframework.

[edytuj] Ta BUILD_SHARED_LIBSopcja działa na razie tylko w systemie Windows. Określa typ bibliotek, które ma zbudować CMake. Jeśli ustawisz to na ON, CMake utworzy je jako biblioteki DLL, a nie statyczne biblioteki. W takim przypadku musisz zbudować swoje testy z -DGTEST_LINKED_AS_SHARED_LIBRARY = 1 i skopiować pliki DLL utworzone przez CMake do katalogu z testowym plikiem binarnym (CMake domyślnie umieszcza je w oddzielnym katalogu wyjściowym). O ile gtest w bibliotece statycznej nie działa, łatwiej jest nie ustawiać tej opcji.

VladLosev
źródło
1
Wielkie dzięki, nie zdawałem sobie sprawy, że możesz tworzyć całkowicie oddzielne projekty w ramach tych samych CMakeLists. Mogę teraz śmiało powiedzieć, że EXPECT_EQ (1,0 == 1,0) przechodzi, a EXPECT_EQ (0,0 == 1,0) zawodzi. Teraz czas na bardziej prawdziwe testy ...
Chris,
2

Po dokładniejszym zagłębieniu się, myślę, że mój problem jest związany z rodzajem biblioteki, w której buduję gtest. Podczas budowania gtest z CMake, jeśli BUILD_SHARED_LIBS jest odznaczone, a mój program jest powiązany z tymi plikami .lib, pojawiają się błędy wymienione powyżej. Jeśli jednak opcja BUILD_SHARED_LIBS jest zaznaczona, tworzę zestaw plików .lib i .dll. Podczas łączenia z tymi plikami .lib program kompiluje, ale po uruchomieniu narzeka, że ​​nie może znaleźć pliku gtest.dll.

Dzieje się tak, ponieważ musisz dodać -DGTEST_LINKED_AS_SHARED_LIBRARY = 1 do definicji kompilatora w swoim projekcie, jeśli chcesz używać gtest jako biblioteki współdzielonej.

Możesz także użyć bibliotek statycznych, pod warunkiem, że skompilowałeś je z włączoną opcją gtest_force_shared_crt, aby wyeliminować błędy, które widziałeś.

Biblioteka mi się podoba, ale dodawanie jej do projektu to prawdziwy ból. I nie masz szans zrobić tego dobrze, chyba że zaczniesz kopać (i zhakować) do plików cmake gtest. Wstyd. W szczególności nie podoba mi się pomysł dodania gtest jako źródła. :)

Slava
źródło
1

OP korzysta z systemu Windows, a znacznie łatwiejszym sposobem korzystania z GTest jest dziś użycie vcpkg + cmake.


Zainstaluj vcpkg zgodnie z https://github.com/microsoft/vcpkg i upewnij się, że możesz uruchomić vcpkgz wiersza cmd. Zwróć uwagę na folder instalacyjny vcpkg, np. C:\bin\programs\vcpkg.

Zainstaluj gtest używając vcpkg install gtest: to pobierze, skompiluje i zainstaluje GTest.

Użyj pliku CmakeLists.txt jak poniżej: pamiętaj, że możemy używać celów zamiast włączać foldery.

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)

Uruchom cmake z: (w razie potrzeby edytuj folder vcpkg i upewnij się, że ścieżka do pliku łańcucha narzędzi vcpkg.cmake jest poprawna)

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

i buduj cmake --build buildjak zwykle. Zauważ, że vcpkg skopiuje również wymagany plik gtest (d) .dll / gtest (d) _main.dll z folderu instalacyjnego do folderów Debug / Release.

Testuj z cd build & ctest.

Daniele
źródło
0

Rozwiązania Twoje i VladLosevsa są prawdopodobnie lepsze niż moje. Jeśli jednak potrzebujesz rozwiązania brutalnego, spróbuj tego:

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
źródło
0

Najprostszy plik CMakeLists.txt, który wydestylowałem z odpowiedzi w tym wątku, a kilka prób i błędów to:

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 powinien być już zainstalowany w twoim systemie.

AlexBriskin
źródło
Dodanie takiej biblioteki w CMake naprawdę nie jest dobrą praktyką. Jednym z głównych celów cmake jest to, aby nigdy nie robić założenia typu „Te biblioteki powinny być już zainstalowane ...”. CMake sprawdź, czy biblioteka jest tutaj, a jeśli nie, generowany jest błąd.
Adrien BARRAL
0

Podobnie jak aktualizacja komentarza @ Patricia w zaakceptowanej odpowiedzi i komentarza @ Fraser do pierwotnego pytania, jeśli masz dostęp do CMake 3.11+, możesz skorzystać z funkcji FetchContent CMake .

Strona FetchContent CMake używa googletest jako przykładu!

Podałem małą modyfikację zaakceptowanej odpowiedzi:

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)

Możesz użyć INTERFACE_SYSTEM_INCLUDE_DIRECTORIESwłaściwości target celów gtest i gtest_main, tak jak są one ustawione w skrypcie Google test CMakeLists.txt .

Panie Splat
źródło
W CMake> = v3.14 możesz zrezygnować z jawności target_include_directoriesi FetchContent_MakeAvailable(googletest)zamiast tego użyć . Spowoduje to zarówno wypełnienie zawartości, jak i dodanie jej do głównej wersji. CMake FetchContent - więcej informacji
67 Hz
0

Postanowiłem wrzucić coś ogólnego, naprawdę szybko demonstrując inny sposób zrobienia tego niż wcześniej opublikowane odpowiedzi, w nadziei, że może to komuś pomóc. Poniższe działały dla mnie na moim Macu. Najpierw uruchomiłem polecenia konfiguracyjne dla gtests. Właśnie użyłem skryptu, który znalazłem, aby wszystko skonfigurować.

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

Następnie stworzyłem prostą strukturę folderów i napisałem kilka szybkich zajęć

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

Stworzyłem plik CMakeLists.txt najwyższego poziomu dla folderu narzędzi i CMakeLists.txt dla folderu testów

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)

To jest CMakeLists.txt w folderze testy

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

Pozostaje tylko napisać sample gtest i gtest main

sample 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

próbka gtest main

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

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

Następnie mogę skompilować i uruchomić gtesty za pomocą następujących poleceń z folderu utils

cmake .
make 
./tests/gtestProject
Pokaż Miłość
źródło