草庐IT

使用gtest做单元测试

尘归尘-北尘 2023-08-20 原文

使用gtest做单元测试

文章目录


gtest是一个跨平台的(Liunx、Mac OS X、Windows 、Cygwin 、Windows CE and Symbian ) C++单元测试框架,由google公司发布。gtest是为在不同平台上为编写C++测试而生成的。它提供了丰富的断言、致命和非致命判断、参数化、”死亡测试”等等

1.用gtest写测试工程的大致流程

配置gtest头文件及库

下载gtest源码:

Releases · google/googletest (github.com)

一般选择最新版应该就没啥问题

放到本地编译,这里假设下载的是1.8.0版本

tar -zxvf googletest-release-1.8.0.tar.gz 
cd googletest-release-1.8.0/ 
mkdir build 
cd build 
cmake .. 
# 编译 
make 
# 安装 
make install

编译完之后,把googletest/include下的gtest头文件拷贝放到自己测试工程的include目录下。

然后把编译出来的静态库(在build/googlemock/gtest下,分别叫libgtest.a和libgtest_main.a),放到自己测试工程下的lib文件夹下。我本地的lib目录如下,其中gmock是用来打桩的,打桩的意思是测试中一个函数还没有实现,然后在需要调用该函数的地方设置一个返回规则,这个加不加都无所谓。

然后在CMakeLists.txt加入相应的配置信息

# 包含头文件的路径 
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/include")
# 设置库文件的生成路径
set(LIBRARY_PATH "${CMAKE_CURRENT_SOURCE_DIR}/lib")

# 链接依赖的库: gtest库,pthread库 
target_link_libraries(Test目标文件 ${LIBRARY_PATH}/gtest/libgtest.a) 
target_link_libraries(Test目标文件 ${LIBRARY_PATH}/gtest/libgtest_main.a) 
target_link_libraries(Test目标文件 pthread)

然后在测试工程源文件中添加对gtest头文件,就可以使用gtest的一系列宏定义来写单元测试函数了

// Test.cpp 
#include <gtest/gtest.h> 
#include "待测试代码头文件.h" /* 待测试的业务代码头文件 */ 

/* 使用TEST宏定义测试名、用例名 */ 
TEST(Module1, function1) 
{ 
    int res = function1(...); /* 调用待测试函数 */ 
    ... 
    EXPECT_EQ(res, 1); 
    /* 比较返回和期望值 */ 
}

最后写上运行所有测试用例,就可以跑测试了。

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

以上都写得很简单,但大概流程就是,拉取gtest源码进行编译,生成gtest静态库,然后把gtest的头文件和库文件加入测试工程中,这样就可以调用gtest了。在自己测试工程中使用TEST或者TEST_F宏定义结合断言来写单元测试函数,最后运行RUN_ALL_TESTS()来运行所有测试用例。当然,CMakeLists.txt中也是必须添加相关路径引用设置的。

gtest的相关概念

TEST与TEST_F

  • TEST(test_case_name, test_name)

创建一个简单测试,它定义了一个测试函数,在这个函数里可以使用任何C++代码并使用提供的断言来进行检查。

  • TEST_F(test_fixture,test_name)

多个测试场景需要相同数据配置的情况,用 TEST_F 。TEST_F test fixture,测试夹具,测试套,承担了一个注册的功能。

断言

gtest中断言的宏可以分为两类:一类是ASSERT宏,另一类就是EXPECT宏了。

1、ASSERT_系列:如果当前点检测失败则退出当前函数

2、EXPECT_系列:如果当前点检测失败则继续往下执行

如果你对自动输出的错误信息不满意的话,也是可以通过operator<<能够在失败的时候打印日志,将一些自定义的信息输出。

比如:

ASSERT_TRUE(Abs(1) == 1) << "Abs(1)=1";

这样在判断不通过时会有后面打印"Abs(1)=1"。

ASSERT_系列:

bool值检查

1、 ASSERT_TRUE(参数),期待结果是true

2、ASSERT_FALSE(参数),期待结果是false

数值型数据检查

3、ASSERT_EQ(参数1,参数2),传入的是需要比较的两个数 equal

4、ASSERT_NE(参数1,参数2),not equal,不等于才返回true

5、ASSERT_LT(参数1,参数2),less than,小于才返回true

6、ASSERT_GT(参数1,参数2),greater than,大于才返回true

7、ASSERT_LE(参数1,参数2),less equal,小于等于才返回true

8、ASSERT_GE(参数1,参数2),greater equal,大于等于才返回true

字符串检查

9、ASSERT_STREQ(expected_str, actual_str),两个C风格的字符串相等才正确返回

10、ASSERT_STRNE(str1, str2),两个C风格的字符串不相等时才正确返回

11、ASSERT_STRCASEEQ(expected_str, actual_str)

12、ASSERT_STRCASENE(str1, str2)

EXPECT_系列,也是具有类似的宏结构的

事件机制

我理解的事件机制是,每个TestSuite事件是某个TestSuite下的所有测试可能需要有一些相同条件,那么在每个TestSuite前运行一遍的函数或者设置,会在这个TestSuite里的所有TestCase里都是一样的,然后每个TestCase运行前可能也有些需要预先运行的函数或者设置,这个需要在TestCase事件里设置。详细解释如下:

“事件” 本质是框架给你提供了一个机会, 让你能在这样的几个机会来执行你自己定制的代码, 来给测试用例准备/清理数据。gtest提供了多种事件机制,总结一下gtest的事件一共有三种:

1、TestSuite事件

需要写一个类,继承testing::Test,然后实现两个静态方法:SetUpTestCase 方法在第一个TestCase之前执行;TearDownTestCase方法在最后一个TestCase之后执行。

2、TestCase事件

是挂在每个案例执行前后的,需要实现的是SetUp方法和TearDown方法。SetUp方法在每个TestCase之前执行;TearDown方法在每个TestCase之后执行。

3、全局事件

要实现全局事件,必须写一个类,继承testing::Environment类,实现里面的SetUp和TearDown方法。SetUp方法在所有案例执行前执行;TearDown方法在所有案例执行后执行。

例如全局事件可以按照下列方式来使用:

除了要继承testing::Environment类,还要定义一个该全局环境的一个对象并将该对象添加到全局环境测试中去。

TetsSuite事件和TestCase事件如下设置:

class TestMap:public testing::Test
{
public:
//TestSuite级别的,在某一批案例中第一个案例前,最后一个案例执行后。
static void SetUpTestCase()
{
cout<<"SetUpTestCase"<<endl;
}
static void TearDownTestCase()
{
cout<<"TearDownTestCase"<<endl;
}
/*TestCase级别的,每个TestCase前后*/
virtual void SetUp() //TEST跑之前会执行SetUp
{
cout<<"SetUp"<<endl;
}
virtual void TearDown() //TEST跑完之后会执行TearDown
{
cout<<"TearDown"<<endl;
}
};
TEST_F(TestMap,Find) //此时使用的是TEST_F宏
{
map<int,int>::iterator it=test_map.find(1);
ASSERT_NE(it,test_map.end());
}
TEST_F(TestMap,Size)
{
ASSERT_EQ(test_map.size(),5);
}
int main(int argc,char *argv[])
{
testing::InitGoogleTest(&argc, argv);//将命令行参数传递给gtest
return RUN_ALL_TESTS(); //RUN_ALL_TESTS()运行所有测试案例
}

即一般在一个头文件里引用gtest和待测试的代码头文件,然后写一个类继承testing::Test,实现里面的SetUp和

TearDown等函数,最后在测试cpp文件里使用TEST_F()来写单元测试,这时TestCase必须是刚才写的类名。

参考

gtest的介绍和使用-测试 (uml.org.cn)

有关使用gtest做单元测试的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  7. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  8. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  9. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  10. ruby - 在 64 位 Snow Leopard 上使用 rvm、postgres 9.0、ruby 1.9.2-p136 安装 pg gem 时出现问题 - 2

    我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po

随机推荐