我最近被推荐使用 CMake 来编译我的 C++ 项目,现在想开始为我的代码编写一些单元测试。我决定使用 Google Test 实用程序来帮助解决这个问题,但在开始时需要一些帮助。
我整天都在阅读各种指南和示例,包括 Primer、introduction at IBM 和一些关于 SO(here 和 here)的问题以及我忘记的其他来源。我意识到那里有很多,但不知何故我仍然遇到困难。
我目前正在尝试实施最基本的测试,以确认我已经正确编译/安装了 gtest 并且它不起作用。唯一的源文件 (testgtest.cpp) 几乎完全取自 this 先前的答案:
#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 中是否有我缺少的选项?
enable_testing()
有什么作用?
该解决方案涉及将 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)
https://github.com/google/googletest/archive/release-1.8.0.zip
GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG release-1.8.1
代替 URL
find_package(GTest REQUIRED)
?
你可以两全其美。可以使用 ExternalProject
下载 gtest 源代码,然后使用 add_subdirectory()
将其添加到您的构建中。这具有以下优点:
gtest 是作为主构建的一部分构建的,因此它使用相同的编译器标志等,因此避免了问题中描述的问题。
无需将 gtest 源代码添加到您自己的源代码树中。
以正常方式使用,ExternalProject 不会在配置时(即运行 CMake 时)进行下载和解包,但您只需做一些工作就可以做到这一点。我写了一篇关于如何做到这一点的博客文章,其中还包括一个通用实现,它适用于任何使用 CMake 作为其构建系统的外部项目,而不仅仅是 gtest。你可以在这里找到它们:
https://crascit.com/2015/07/25/cmake-gtest/
https://github.com/Crascit/DownloadProject
更新:这种方法现在也是part of the googletest documentation。
最有可能的是,您的测试二进制文件和 Google 测试库之间编译器选项的差异应归咎于此类错误。这就是为什么建议以源代码形式引入 Google Test 并与您的测试一起构建它。在 CMake 中很容易做到。您只需使用 gtest 根目录的路径调用 ADD_SUBDIRECTORY
,然后您就可以使用在那里定义的公共库目标(gtest
和 gtest_main
)。在 googletestframework 组中的此 CMake thread 中有更多背景信息。
[编辑] BUILD_SHARED_LIBS
选项目前仅在 Windows 上有效。它指定您希望 CMake 构建的库的类型。如果将其设置为 ON
,CMake 会将它们构建为 DLL,而不是静态库。在这种情况下,您必须使用 -DGTEST_LINKED_AS_SHARED_LIBRARY=1 构建测试,并将 CMake 生成的 DLL 文件复制到包含测试二进制文件的目录中(默认情况下,CMake 将它们放在单独的输出目录中)。除非静态库中的 gtest 对您不起作用,否则不设置该选项会更容易。
OP 使用的是 Windows,而今天使用 GTest 的一种更简单的方法是使用 vcpkg+cmake。
按照 https://github.com/microsoft/vcpkg 安装 vcpkg,并确保您可以从 cmd 行运行 vcpkg
。记下 vcpkg 安装文件夹,例如。 C:\bin\programs\vcpkg
。
使用 vcpkg install gtest
安装 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
进行测试。
在进行了更多挖掘之后,我认为我的问题与我正在构建 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 添加为源的想法。 :)
正如@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
目标属性,因为它们是在 google test CMakeLists.txt 脚本中设置的。
target_include_directories
并改用 FetchContent_MakeAvailable(googletest)
。这将填充内容并将其添加到主构建中。 CMake FetchContent - more info
您和 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)
我从这个线程的答案中提取的最简单的 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 应该已经安装在您的系统上。
我决定将一些通用的东西放在一起,快速展示一种与之前发布的答案不同的方法,希望它可以帮助某人。以下内容在我的 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,为测试文件夹创建了一个 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
pthread
添加到链接库中,将倒数第二行更改为target_link_libraries(runUnitTests gtest gtest_main pthread)
make test
才能运行测试,或者从构建目录运行ctest
。运行ctest -V
以查看 google 测试输出以及ctest
输出。googletest
,我有一个googletest
目录。在那个googletest
目录中,我还有另一个googletest
目录,我应该在那里设置哪个目录?另外,我在dependencies
目录中有外部googletest
目录,而不是在 CMake 项目的根目录中。那么我应该在 ADD_SUBDIRECTORY 函数中添加什么来使其真正起作用呢?