如何开始使用 GTest 和 CMake

我最近一直在使用 CMake 编译我的 C + + 项目,现在想开始为我的代码编写一些单元测试。我已经决定使用谷歌测试工具来帮助这一点,但需要一些帮助才能开始。

一整天我都在阅读各种指南和例子,包括 雷管在 IBM 的介绍和一些关于 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。

SHARED和非 SHARED库有什么区别? 如果我选择不共享,为什么它不能工作?在 CMakeLists.txt 中是否有我缺少的用于项目的选项?

167003 次浏览

最有可能的是,您的测试二进制文件和 GoogleTest 库之间编译器选项的差异就是这些错误的原因。这就是为什么推荐使用源代码形式的 GoogleTest,并将它与您的测试一起构建。在 CMake 中很容易做到。您只需使用 gtest 根目录的路径调用 ADD_SUBDIRECTORY,然后就可以使用在其中定义的公共库目标(gtestgtest_main)。在 googleestFramework 组中的这个 制作线程中有更多的背景信息。

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

你和弗拉德洛塞夫的解决方案可能比我的更好。然而,如果你想要一个暴力的解决方案,试试这个:

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)

在进行了一些深入研究之后,我认为我的问题与构建 gtest 的库的类型有关。当使用 CMake 构建 gtest 时,如果 BUILD _ SHARED _ LIBS 未被检查,我会将我的程序链接到这些。Lib 文件我得到了上面提到的错误。但是,如果选中 BUILD _ SHARED _ LIBS,那么将生成一组。Lib 和。Dll 文件。当现在连接到这些。Lib 文件程序编译,但是当运行时抱怨它不能找到 gtest.dll。

这是因为如果希望将 gtest 用作共享库,就必须将 -DGTEST _ LINKED _ AS _ SHARED _ LIBRARY = 1添加到项目中的编译器定义中。

您还可以使用静态库,前提是使用 gtest _ force _ share _ crt 选项编译它,以消除您看到的错误。

我喜欢这个库,但是将它添加到项目中是一件非常痛苦的事情。除非您深入(并侵入) gtest cmake 文件,否则就没有机会正确地完成它。真可惜。我尤其不喜欢将 gtest 作为源代码添加的想法。:)

解决方案包括将 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 )

下面是我刚刚测试的一个完整的工作示例。它可以直接从 web 下载,可以是一个固定的 tarball,也可以是最新的 subversion 目录。

cmake_minimum_required (VERSION 3.1)


project (registerer)


##################################
# Download and install GoogleTest


include(ExternalProject)
ExternalProject_Add(gtest
URL https://github.com/google/googletest/archive/release-1.8.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)

你可以两全其美。可以使用 ExternalProject下载 gtest 源代码,然后使用 add_subdirectory()将其添加到构建中。这有以下优点:

  • Gtest 是作为主构建的一部分构建的,因此它使用相同的编译器标志等,从而避免了问题中描述的问题。
  • 没有必要将 gtest 源添加到您自己的源代码树中。

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

更新: 此方法现在也是 Googletest 文档的一部分

最简单的 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。

就像@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)

可以使用 gtest 和 gtest _ main 目标的 INTERFACE_SYSTEM_INCLUDE_DIRECTORIES target 属性,因为它们是在 google test CMakeLists.txt脚本中设置的。

我决定把一些通用的东西很快地放在一起,演示一种不同于之前发布的答案的方法,希望它能帮到某些人。下面这些对我的 Mac 很有用。首先,我运行 gtest 的 setup 命令。我只是用我找到的一个脚本来设置所有东西。

#!/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,为 test 文件夹创建了一个 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)

这是 test 文件夹中的 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 文件夹中的以下命令编译和运行 gtest

cmake .
make
./tests/gtestProject

OP 使用的是 Windows,现在使用 GTest 更简单的方法是使用 Vcpkg + cmake。


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

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

如下所示使用 CmaLists.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测试。

更新答案,使用官方谷歌测试文档

include(FetchContent)
FetchContent_Declare(
googletest
# Specify the commit you depend on and update it regularly.
URL https://github.com/google/googletest/archive/5376968f6948923e2411081fd9372e71a59d8e77.zip
)
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)


# Now simply link against gtest or gtest_main as needed. Eg
add_executable(example example.cpp)
target_link_libraries(example gtest_main)
add_test(NAME example_test COMMAND example)