如何开始使用GTest和CMake


125

最近,我因使用CMake编译我的C ++项目而受宠若惊,现在想开始为我的代码编写一些单元测试。我已决定使用Google Test实用程序来解决此问题,但在入门方面需要一些帮助。

我整天都在阅读各种指南和示例,包括PrimerIBM介绍和有关SO的一些问题(此处此处)以及其他我所追踪的资源。我知道那里有很多东西,但是不知何故我仍然遇到困难。

我目前正在尝试实施最基本的测试,以确认我已经正确编译/安装了gtest,并且该测试无法正常工作。唯一的源文件(testgtest.cpp)几乎完全来自先前的答案:

#include <iostream>

#include "gtest/gtest.h"

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

我关联的CMakeLists.txt如下:

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
)

请注意,我选择链接到gtest_main,而不是在cpp文件的末尾提供main,因为我相信这将使我可以更轻松地将测试扩展到多个文件。

不幸的是,当生成生成的.sln文件时(在Visual C ++ 2010 Express中),我得到了一长串形式的错误

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)

我认为这意味着我没有成功链接到gtest库。我已确保在链接调试库时,然后尝试在调试模式下进行构建。

编辑

经过更多的挖掘,我认为我的问题与我将gtest构建到的库类型有关。当使用CMake构建gtest时,如果BUILD_SHARED_LIBS未选中,并且将程序链接到这些.lib文件,则会遇到上述错误。但是,如果BUILD_SHARED_LIBS选中,那么我将生成一组.lib和.dll文件。现在,当针对这些.lib文件进行链接时,程序会编译,但在运行时会抱怨找不到gtest.dll。

a SHARED和not SHARED库之间有什么区别,如果我选择不共享,为什么它不起作用?缺少我的项目的CMakeLists.txt中有一个选项吗?


4
您可以使用ExternalProject_Add而不是避免自己包含GTest源代码add_subdirectory。有关详细信息,请参见此答案
弗雷泽

为什么在上面的解决方案示例中我们可以访问$ {gtest_SOURCE_DIR}?该变量如何/在哪里声明?
dmonopoly

哦,它在gtest-1.6.0 / CMakeLists.txt中声明:“ project(gtest CXX C)”,这使变量gtest_SOURCE_DIR和gtest_BINARY_DIR可用。
dmonopoly

1
怎么enable_testing()办?
updogliu

1
@updogliu:它启用ctest和“测试”(或“ RUN_TESTS”)目标。它与add_test()cmake命令一起播放。
Ela782 2014年

Answers:


76

解决方案涉及将gtest源目录放置为项目的子目录。如果对任何人有帮助,我会在下面列出可工作的CMakeLists.txt。

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
我不确定add_test()的作用,但似乎并不会导致测试二进制文件运行...我是否缺少某些内容?
weberc2

4
不要打死马,但我认为这值得一提。弗雷泽(Fraser)在上面的评论中指出了一个非常重要的观点:“您可以使用ExternalProject_Add而不是add_subdirectory来避免自己包含GTest源代码。” 有关详细信息,请参见Fraser的答案和评论: stackoverflow.com/a/9695234/1735836
Patricia

1
就我而言,我还需要添加pthread到链接的库中,将倒数第二行更改为target_link_libraries(runUnitTests gtest gtest_main pthread)
panmari

3
@ weberc2您必须运行make test以运行测试,或ctest从构建目录运行。运行ctest -V以查看Google测试输出以及ctest输出。
Patrick

38

这是我刚刚测试过的完整示例。它可以直接从Web(固定的tarball或最新的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
我不知道你为什么为此而down之以鼻。您的解决方案可避免某人不必将Google Test签入版本控制。解决方案的荣誉。
2015年

4
您使用的网址现在已损坏。最新的网址是https://github.com/google/googletest/archive/release-1.8.0.zip
oscfri

好答案。应该是数字
1。– Mr00Anderson

1
很好的答案!我们也可以用它GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG release-1.8.1代替URL
TingQian LI

gtest最新版本的URL为:https://github.com/google/googletest/archive/release-1.10.0.zip
vahancho

16

您可以两全其美。可以用于ExternalProject下载gtest源,然后用于add_subdirectory()将其添加到您的构建中。这具有以下优点:

  • gtest被构建为您的主版本的一部分,因此它使用相同的编译器标志等,因此避免了问题中所述的问题。
  • 无需将gtest源添加到您自己的源树中。

以常规方式使用,ExternalProject不会在配置时(即,运行CMake时)进行下载和解压缩,但是您只需一点点工作就可以做到。我写了一篇有关如何执行此操作的博客文章,其中还包括一个通用的实现,该实现适用于所有使用CMake作为其构建系统的外部项目,而不仅仅是gtest。您可以在这里找到它们:

更新:这种方法现在也是googletest文档的一部分


2
IMO,这也许是通过CMake项目实施Google测试的最干净的方法。我希望主持人将更多地关注答案的内容和质量。
NameRakes'17

链接的DownloadProject.cmake通用模块很棒。感觉就像cmake拥有一个软件包管理系统的基础,其中我所需要的只是指向CMake兼容github url的链接列表。
乔什·皮克

13

测试二进制文件和Google Test库之间在编译器选项上的差异很可能归咎于此类错误。因此,建议您以源代码形式引入Google Test并将其与您的测试一起构建。在CMake中很容易做到。您只需ADD_SUBDIRECTORY使用gtest根目录的路径进行调用 ,然后就可以使用在那里定义的公共库目标(gtestgtest_main)。googletestframework组中的此CMake线程中有更多背景信息。

[edit]该BUILD_SHARED_LIBS选项目前仅在Windows上有效。它指定要CMake构建的库的类型。如果将其设置为ON,则CMake会将它们构建为DLL,而不是静态库。在这种情况下,您必须使用-DGTEST_LINKED_AS_SHARED_LIBRARY = 1构建测试,并将CMake生成的DLL文件复制到带有测试二进制文件的目录中(默认情况下,CMake会将它们放置在单独的输出目录中)。除非静态lib中的gtest对您不起作用,否则不设置该选项会更容易。


1
非常感谢,没有意识到您可以在相同的CMakeList中构建完全独立的项目。我现在可以肯定地说EXPECT_EQ(1.0 == 1.0)通过了,而EXPECT_EQ(0.0 == 1.0)失败了。现在该进行更多真实的测试了……
克里斯(Chris)

2

经过更多的挖掘,我认为我的问题与我要建立gtest的库类型有关。使用CMake构建gtest时,如果未选中BUILD_SHARED_LIBS,并且我将程序链接到这些.lib文件,则会遇到上述错误。但是,如果选中了BUILD_SHARED_LIBS,那么我将生成一组.lib和.dll文件。现在,当针对这些.lib文件进行链接时,程序会编译,但在运行时会抱怨找不到gtest.dll。

这是因为如果要将gtest用作共享库,则必须在项目的编译器定义中添加-DGTEST_LINKED_AS_SHARED_LIBRARY = 1。

如果使用gtest_force_shared_crt选项进行编译,还可以使用静态库来消除看到的错误。

我喜欢这个库,但是将它添加到项目中确实很痛苦。除非您挖掘(和破解)gtest cmake文件,否则您将没有正确的选择。耻辱。特别是我不喜欢将gtest添加为源代码的想法。:)


1

OP正在使用Windows,而今天使用GTest的一种更简单的方法是使用vcpkg + cmake。


按照https://github.com/microsoft/vcpkg安装vcpkg ,并确保可以vcpkg从cmd行运行。记下vcpkg安装文件夹,例如。C:\bin\programs\vcpkg

使用安装gtest vcpkg install gtest:这将下载,编译和安装GTest。

如下使用CmakeLists.txt:请注意,我们可以使用目标而不是包含文件夹。

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)

使用以下命令运行cmake :(如有必要,请编辑vcpkg文件夹,并确保vcpkg.cmake工具链文件的路径正确)

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

cmake --build build照常使用。请注意,vcpkg还将将所需的gtest(d).dll / gtest(d)_main.dll从安装文件夹复制到Debug / Release文件夹。

用测试cd build & ctest


0

您和VladLosevs的解决方案可能比我的更好。但是,如果您需要强力解决方案,请尝试以下操作:

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

我从该线程的答案中提炼出的最简单的CMakeLists.txt和一些试验和错误是:

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应该已经安装在您的系统上。


在CMake中添加这样的库确实不是一个好习惯。cmake的主要目标之一是永远不必进行诸如“此库应该已经安装...”之类的假设。CMake检查库是否在这里,否则,将引发错误。
Adrien BARRAL '16

0

就像对已接受答案中的@Patricia注释和对原始问题的@Fraser注释的更新一样,如果您有权访问CMake 3.11+,则可以使用CMake的FetchContent函数。

CMake的FetchContent页面以googletest为例!

我对接受的答案做了一些小的修改:

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)

您可以使用INTERFACE_SYSTEM_INCLUDE_DIRECTORIES在Google测试CMakeLists.txt脚本中设置的gtest和gtest_main目标的target属性。


在CMake> = v3.14中,您可以放弃显式target_include_directoriesFetchContent_MakeAvailable(googletest)改为使用。这将填充内容并将其添加到主版本中。CMake FetchContent-更多信息
67hz

0

我决定快速地将一些通用的东西放在一起,以演示与以前发布的答案不同的方式,希望它能对某人有所帮助。以下在我的Mac上为我工作。首先,我为gtests运行了设置命令。我只是使用发现的脚本来设置所有内容。

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

接下来,我制作了一个简单的文件夹结构,并编写了一些快速的类

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

我为utils文件夹做了一个顶层CMakeLists.txt,为tests文件夹做了一个CMakeLists.txt。

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)

这是测试文件夹中的CMakeLists.txt

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

然后剩下的就是编写示例gtest和gtest main

样本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

gtest主样本

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

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

然后,我可以使用utils文件夹中的以下命令编译并运行gtests

cmake .
make 
./tests/gtestProject
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.