白盒测试项目实践经验总结(一)

572 阅读6分钟
原文链接: mp.weixin.qq.com

点击蓝字关注这个神奇的公众号~

序言

曾经给某项目的重要底层模块做单元测试,打算写个系列文章来总结下。前期主要是测试实践中的一些经验总结,后续有时间的话会补上白盒测试理论篇。

测试对象

某项目重要底层模块代码(C语言,约6000+行代码)

测试方法

白盒测试,先后进行了静态测试和动态测试。

静态测试主要是code review,这也是我做单测之前耗时最多的准备工作,边研究代码边做静态测试(事实证明,此项目80%的bug都是在此阶段发现的)。

动态测试,使用了开源的gtest框架编写test case,针对6000多行的被测代码,写了1万多行测试代码,被测代码与测试代码量大约为1:1.8的比例,被测函数与test case比约为1:9。

测试时发现了一些比较经典的问题,主要有以下:

>内存泄漏                       >边界值错误

>访问非法内存                >返回码错误

>分支不可达                    >被零除

>冗余参数                       >判断逻辑不严密

>日志记录错误                >代码健壮性建议

接下来的几篇文章会挑出几个比较典型的bug来展开说明。本篇就先以内存泄漏问题开始。

(说明:为了保护项目代码隐私,文章中均不会贴出被测代码,尽量以图文的形式来描述问题。)

内存泄漏是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

内存泄漏缺陷具有隐蔽性、积累性的特征,比其他内存非法访问错误更难检测。因为内存泄漏的产生原因是内存块未被释放,属于遗漏型缺陷而不是过错型缺陷。此外,内存泄漏通常不会直接产生可观察的错误症状,而是逐渐积累,降低系统整体性能,极端的情况下可能使系统崩溃。

测试时发现的内存泄漏bug主要有两个场景,但实际会非常多,后面会对此进行补充说明。

内存泄漏Bug1:

Bug描述:函数内申请了堆内存,中途抛出异常直接return了,导致写在函数末尾的内存释放语句未执行。

解决:

各异常分支,在return前添加内存释放语句。

内存泄漏Bug2:

对于一个复杂的结构,内存分多次申请,在第N次申请内存失败或者遇到异常情况,直接返回,未将前N-1次申请的内存释放。如下图:

结构体有两个成员,一个是int型名为count,另外一个是指向char*的指针,这个指针指向一个char*的数组,数组总共有count个元素,每个元素的值代表了一个char字符串的存放地址。

这个结构体和数组都是存放在栈上的,然后会有一个循环依次申请一块相应大小的堆内存,并将分配好的堆内存地址赋给相应的数组元素,一直循环count次,这个复杂数据结构的内存才算分配完成。代码中在每次分配堆内存之前会进行一些预置操作,在这个操作过程中如何出现异常,就会停止内存分配并退出。这里的问题就出现在退出之前没有将前面N-1次循环时申请的内存给释放掉,所以这里会产生内存泄漏。

解决: 在需要的所有堆内存空间被完全分配成功之前,发生任何异常,调用预先写好的      free_String_vector(nodes, j, 0)函数将前面申请的J块内存释放掉。

总结

其实上面两个bug出现的根本原因就在于在某些场景下内存释放语句被跳过去了。稍微分析下会发现代码中出现这种情况的场景会非常多,这里罗列了一些场景供大家参考:

比较容易出现内存泄漏的情况:

  •  本函数内申请内存,并在本函数内释放。由于一些原因导致内存释放语句被跳过或未来得及执行。(这种检查起来相对容易,甚至可以通过code reivew的方法就能发现)

  •  函数内抛出异常中断函数执行,在此之前未释放申请的内存;

  •  函数内有多个分支return,即函数内部有多重回传路径,代码中是否在每次return之前都释放了内存;

  •  申请、释放内存的语句位于循环体中,某些情况下continue、break,导致可能直接跳过了释放内存语句;

  •  以上情况下都相应释放了内存,但后期进行代码扩展,新增加了return路径或者重写了循环,忘记释放;

  •  函数内申请内存,由调用处释放或者穿越多层调用后释放,并且调用函数内部可能也有各种抛出异常,return,continue,break等。这种情况仅通过code review明显是行不通的,这时就需要借助工具+动态测试结合进行。

  •  对于一个复杂的结构,需要分多次申请内存,在未完全分配完成之前,申请内存失败或遇到其他异常情况,应该释放掉前面申请的部分内存。

  •  C++:析构函数中(本次测试的项目是C语言的,后来接触的C++的项目,大多使用了智能指针来规避内存泄漏的问题)

        涉及到指针指向动态内存并且存在继承:虚析构函数

         显式定义赋值操作符,在赋值前,先释放掉之前指向的内存

 

Tips:不止堆内存的清理,类似的场景可以扩展到任意资源的清理,比如数据库连接、文件描述符、互斥锁、socket套接字等。

 

作者后续会分享边界值方面的相关经验。敬请期待~

Qtest是360旗下的专业测试团队!

是WEB平台部测试技术平台化、效率化的先锋力量!

陪伴是最长情的告白

每日为你推送最in的测试技术

识别二维码

关注我们