单元测试FIRST原则以及基于GTest进行单元测试

转载时请表明出处

作者联系方式:liuyuxin0829@qq.com

一、前言

  在大学的时候,写代码随心所欲,想到什么就写什么,只顾实现功能,也不会去验证代码的可行性和稳定性,往往都会在在后续的使用过程中出现各种各样的问题,然后再去捉虫,这样写出来的代码质量差,在后期又耗费大量的时间修复旧代码bug。

  参加工作后,接触到了单元测试,在第一个月的考核项目(智能家居控制面板)中,通过请教同事和参考AWTK源码中的单元测试代码,磕磕绊绊得写了一些单元测试,但由于没有设计好项目基础框架,业务逻辑和用户界面没有完全分离,因此只做了文件读写模块和网络通信模块的单元测试。刚好在本周的培训内容中又详细讲了单元测试的FIRST原则,以下记录了单元测试的学习感悟,并使用Google公司开源的C/C++单元测试框架——GTest进进行单元测试。

AWTK是ZLG开源的GUI框架:https://github.com/zlgopen/awtkS

二、什么是单元测试?

  软件测试包括单元测试、集成测试、系统测试。

  1. 单元测试:对软件设计的最小单位进行正确性测试,以检验程序单元是否满足功能、性能、接口、设计规约等要求。
  2. 集成测试:将各个程序单元进行有序的、递增的组合进行测试,以检验各个程序单元的配合情况。
  3. 系统测试:对集合了应用软件、系统软件、硬件的产品进行测试,以验证产品在实际应用中的功能、性能等特性。

  根据传统的开发模型,如瀑布模型,软件开发过程和软件测试活动的关系可以反映为经典的软件测试V模型人,如下图:

Software_test_V_model.png

  其中单元测试中的单元指软件中承担单一职责的单位,通常在程序中体现为一个函数、一个文件、一个类、一个模块等。单元测试都是以自动化方式执行,所以在大量回归测试的场景下更能带来高收益。并且单元测试代码里会提供函数的使用示例,因为单元测试的具体表现形式就是对各种函数以各种不同输入参数组合进行调用。

三、为什么需要单元测试?

  在我随心所欲写程序时,经常会遇到一些问题,例如:

  • 编译通过,但是要调试好久才能正常运行;
  • 好不容易调试好了,但是一测试就会出一堆bug;
  • 修复已有的bug,总会产生新的bug;
  • bug难以重现,又无法定位;
  • 等等......

  以上问题总结一下,就是“你写的代码并不是你想要的结果”,而单元测试则是能保证“你写的代码是你想要的结果”的最有效办法。

  单元测试阶段发现的bug更容易定位,并且由于单元测试自动化的特点,更加方便重现bug。在单元测试阶段发现bug,立即修复,不会将各种问题留到最后的系统测试阶段,让代码更可靠、更容易维护,减少后期测试、维护的成本。

  总的来讲,单元测试能够提升代码质量,减少程序整体的调试时间。

四、如何做好单元测试?

  单元测试需要遵循FIRST原则:

  1. F-FAST(快速原则):单元测试应该是可以快速运行的,在各种测试方法中,单元测试的运行速度是最快的,大型项目的单元测试通常应该在几分钟内运行完毕。

  2. I-Independent(独立原则):单元测试应该是可以独立运行的,单元测试用例互相无强依赖,无对外部资源的强依赖。

  3. R-Repeatabl(可重复原则):单元测试应该可以稳定重复的运行,并且每次运行的结果都是稳定可靠的。

  4. S-Self Validating(自我验证原则):单元测试应该是用例自动进行验证的,不能依赖人工验证。

  5. T-Timely(及时原则):单元测试必须及时进行编写,更新和维护,以保证用例可以随着业务代码的变化动态的保障质量。

  下面会基于GTest框架来说明这五个原则。

五、基于GTest进行单元测试

1、快速了解GTest

  GTest全称GoogleTest,是Google公司发布的一款非常优秀的开源C/C++单元测试框架,已被应用于多个开源项目及Google内部项目中,包括ChromeWeb浏览器、LLVM编译器框架等。

GTest源码:https://github.com/google/googletest

  下载或克隆源码后,可以看见目录结构如下图,通常我们进行单元测试时需要用到目录是include和src。配置工程需要做以下三件事:

包含目录:[GTest目录名]\googletest\include; [GTest目录名]\googletest;

添加源文件:[GTest目录名]\googletest\src\gtest-all.cc;

包含头文件:#include<gtest/gtest.h>

gtest.png

  进行单元测试前我们需要了解两个概念:测试用户、测试用例集。

  • 测试用例:为了验证代码的行为与预期是否相符而进行的一系列活动,在单元测试中,这一系列的活动依靠代码来完成。

  • 测试用例集:多个相似或相关的测试用例的集合,是为了方便我们对测试用例进行管理而产生的一个概念。通俗一点,测试用例集就是对测试用例进行分组。

TEST(IsLeapYearTest, leapYear)          /* 用例集IsLeapYearTest,用例leapYear */
{
  EXPECT_TRUE(IsLeapYear(2000));        /* 测试IsLeapYear函数,传入参数2000 */
  EXPECT_TRUE(IsLeapYear(1996));
}

  写好测试用例后,需要运行测试用例,代码如下:

int main(int argc, char** argv)
{
  testing::FLAGS_gtest_filter = “*”;        /* 选择需要运行的用例 */
  testing::InitGoogleMock(&argc, argv);     /* 初始化测试框架 */
  return RUN_ALL_TESTS();                   /* 运行所选测试用例 */
}

  写好测试用例后,GTest中可以用一下方式表示测试用例:

  • “用例集.用例”,例如: “IsLeapYearTest. leapYear ”

  • 可以使用通配符“*”和“?”,例如:“IsLeapYearTest.*”

  • 使用“:”连接多个匹配条件,例如:“*. leapYear : *. commonYear”

  • 使用“-”排除用例,例如:“-IsLeapYearTest.*”

2、断言

  断言可以理解为判断一个值或多个值是否满足指定条件,例如:

说明 断言的宏调用
判断一个值是否为真 EXPECT_TRUE(val)
判断一个值是否与期望值相等 EXPECT_EQ(exp, val)
判断两个值的大小 EXPECT_LE(val1, val2)
判断一个字符串是否与期望值相等 EXPECT_STREQ(exp, val)

更多GTest中断言的宏请查阅文档:[GTest目录名]\googletest\docs\primer.md

  当判定通过时,无输出,如下图:

断言成功.png

  当判定失败时,GTest会输出断言位置和失败原因,如下图:

断言失败.png

  GTest中断言的宏可以理解为两类:ASSERT、EXPECT。

  • ASSERT_*:当检查点失败时,退出当前函数(执行return操作)。

  • EXPECT_*:当检查点失败时,继续往下执行。

3、使用GTest说明FIRST原则

  看完以上内容,应该对GTest有了简单的认识,接下来就使用GTest框架来举例说明FIRST原则。

(1)F-FAST(快速原则)

  在调试程序的过程中,需要多次运行单元测试去验证被测试模块是否正确,应该为了节省时间,单元测试必须可以快速执行。

TEST(Fs, basic) {                       /* 测试文件接口的基本功能 */
  ASSERT_EQ(fs_test(os_fs()), RET_OK);  /* 测试获取文件系统对象函数os_fs() */
}
(2)I-Independent(独立原则)

  单元测试可独立运行,测试用例直接无依赖,对外部资源无依赖,测试顺序不影响测试结果,测试过程中产生的外部资源文件需要在测试完成后销毁。

TEST(Fs, read_part) {       /* 测试文件接口的读取功能 */
 char buff[128];
 uint32_t size = 0;
 const char* str = "hello world";
 const char* filename = "test.bin";
 /* 测试前要创建被读取的文件 */
 file_write(filename, str, strlen(str));            
 char* ret = (char*)file_read(filename, &size);
 /* 进行测试,对读取到的结果ret进行验证 */
 ASSERT_EQ(file_read_part(filename, buff, sizeof(buff), 0), strlen(str));
 ASSERT_EQ(strcmp(ret, str), 0);
 ASSERT_EQ(size, strlen(str));
 /* 测试完成后删除被读取的文件,并释放缓冲区 */
 file_remove(filename);
 TKMEM_FREE(ret);
}
(3)R-Repeatabl(可重复原则)

  单元测试需要可以稳定重复的运行,每次得到的结果需要保持一致,如果连测试结果都不稳定,或者测试过程经常出现失败的情况,那么单元测试也没有意义了。

TEST(RandomNumber, Compared) {                  /* 测试随机数比较大小 */
 ASSERT_TRUE((rand() % 100) > (rand() % 100));  /* 无法保证每次结果都一致 */
}
(4)S-Self Validating(自我验证原则)

  单元测试由用例自动进行验证的,不依赖人工验证,这是因为人工验证耗费不必要的时间,而且没有办法保证验证结果的准确性,通常来说单元测试的自我验证就是由测试程序直接告诉开发者通过或不通过,不需要让开发者通过输出结果来判断自己的测试用例是否通过。例如GTest的测试用例输出,输出OK表示通过,如下所示:

[ RUN      ] Fs.basic               /* RUN表示执行Fs.basic测试用例 */
[       OK ] Fs.basic (4 ms)        /* OK表示Fs.basic用例测试通过 */
[ RUN      ] Fs.read_part
[       OK ] Fs.read_part (0 ms)
(5)T-Timely(及时原则)

  单元测试必须及时进行编写,更新和维护,以保证用例可以随着业务代码的变化动态的保障质量。单元测试通常是在写函数实现前就需要写好的,这样能让单元测试在开发者写函数实现的过程中起到校验的作用,避免开发者犯错。

TEST(Module, Function) {        /* 测试某模块的功能(函数) */
 ASSERT_TRUE(function());       /* 先按照期望结果测试function(),再去写function()的实现 */
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343