软件单元测试方法.ppt_第1页
软件单元测试方法.ppt_第2页
软件单元测试方法.ppt_第3页
软件单元测试方法.ppt_第4页
软件单元测试方法.ppt_第5页
已阅读5页,还剩88页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

软件单元测试方法,工业通讯技术部 张国亮,提纲,序言 一个单元测试示例 测试内容及方法 边界条件 单元测试工具 Mock对象 好的测试品质 在项目中进行测试,序言(1),要向客户演示系统的基本功能,且临近最终期限时,两个开发者的两种不同的做法: 第一个开发者:每天着急地写代码,写完一个类又写一个类,写完一个方法又接着写两一个方法,有时不得不停下来做一些调整,使得代码能够编译通过。 第二个模块者:写一个模块的时候,会附带写一个简短的测试程序来测试这个方法。并且在未对刚写的方法做出确认(通过测试确认之前的方法和他所期望的结果一致)之前,是不会接着写新代码的。,序言(2),两种不同的做法带来的两种不同的结果: 第一个开发者:期限的前一天,集成演示时,一点输出都没有;利用调试器跟踪,经过长时间的琢磨,找到并纠正了这个bug,但是,同时又发现了其他好几个bug。结果,筋疲力尽,而且未能及时完成任务。 第二个开发者:期限的前一天,集成代码到整个系统中,并且能够很好的运行;虽然在其中也出现了一个小问题,但是很对就发现了问题所在,并在几分钟之内就解决了这个问题。,序言(3),不写测试的借口 编写单元测试太花时间了 运行测试的时间太长了 测试代码并不是我的工作 我并不清楚代码的行为,所以也就无从测试 这些代码都能够编译通过 公司请我来是为了写代码,而不是写测试 如果我让测试员或者QA人员没有工作,那么我会觉得很内疚 我的公司并不会让我在真实系统中运行单元测试,单元测试,什么是单元测试 单元测试是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。 通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。 执行单元测试,是为了证明某段代码的行为确实和开发者所期望的一致 为什么要使用单元测试 单元测试不但会使你的工作完成的更轻松,而且会令你的设计变得更好,甚至大大减少你花在调试上面的时间。 单元测试可以提高底层代码的正确性,从而提高调用它的高层代码的正确性。 使用单元测试这个简单有效的技术就是为了令代码变得更加完美。,目的与范围,目的 为了提高软件开发效率和质量 统一单元测试风格 保证文档与单元 测试编码的一致性 方便开发人员进行单元测试的开发 使单元测试能够有效正常的自动执行 范围 单元测试属于软件模块开发过程中的活动,提纲,序言 一个单元测试示例 测试内容及方法 边界条件 单元测试工具 Mock对象 好的测试品质 在项目中进行测试,一个单元测试(1),简单的例子: 查找list中的最大值:int Largest(int list, int length); 首份实现代码如下: int Largest(int list, int length) int i,max; for(i = 0; i max) max=listi; return max; ,一个单元测试(2),计划你的测试 给定一个数组7,8,9,这个方法返回9。这就构成了一个合理的测试。 你能想出其他一些测试么? 如果list中有两个相等的最大值,将会出现什么情况? 7,9,8,9 9 如果list中只有一个元素,结果会是怎么样的呢? 1 1 如果list所包含的是负数,结果又会怎么样呢? -9,-8,-7 -7,一个单元测试(3),测试一个简单的方法 编写测试代码 TEST(CheckResult, Max_Int) int list = 7,8,9; CHECK(Largest(List, 3) = 9); 编译结果如下,一个单元测试(4),源程序中被测试方法的错误查找与修改 发现max值没初始化 然后进行修改并且设置max的初始数值:max=0 重新编译并运行测试 测试运行结果如下:,一个单元测试(5),再次进行源文件被测试方法的bug查找与修改 为什么返回的最大值是8,好像程序根本没有考虑list中的最后一个元素似的。 查看源文件,发现for循环结束得早了。 修改:将for循环中i length-1修改成为i length 编译源文件并运行该测试,程序将没有failure输出。,一个单元测试(6),其他考虑到的测试方法的尝试 存在重复的最大值的情况 9,7,9,8 只有一个元素的情况 1 全是负值的情况 -9,-8,-7 编译源文件并运行修改后的测试,发现重复最大值和只有一个元素的情况,测试都通过了。但是全是负值的情况出现了问题,如下:,一个单元测试(7),第三次进行源文件被测试方法的bug查找与修改 为什么返回的最大值是0,它是从哪里来的? 看来用0来初始化max是个错误的做法 我们应该用list0来作为max的初始值 修改过后,便以并重新运行该测试,结果也通过了该测试。,一个单元测试(8),是否说明获取数组最大值的方法已经完全没有问题了呢? 如果数组为null,会出现什么情况? 如果数组的长度为0,会出现什么情况? 通常,我们会认为这是一个错误,将抛出一个异常,修改方法: int Largest(int list, int length) int i, max; if ( list = null | length = 0) return 0; ,提纲,序言 一个单元测试示例 测试内容及方法 边界条件 单元测试工具 Mock对象 好的测试品质 在项目中进行测试,测试内容,单元测试的对象:软件设计的最小单位模块或函数,输入数据和形参的定义是否一致; 是否修改了只做输入用的形式参数; 全局变量的定义在各模块中是否一致; ,模块接口,出错处理,独立路径,边界条件,局部数据结构,局部变量类型是否正确 是否初始化了 错误的初始值或错误的默认值 ,运算是否正确 逻辑是否正确 是否会产生死循环、不可终止的迭代 ,对错误条件的处理不正确 在对错误进行处理之前,错误条件已经引起系统的干预 模块是否能恢复正常 ,在循环的第0次、1次、n次 运算或判断中取最大最小值时 输入参数的最大最小值 ,测试方法:Right-BICEP(1),6个值得测试的具体部位,他们能够提高你的测试技巧 Right结果是否正确? B是否所有的边界条件都是正确的? I能查一下反向关联吗? C能用其他手段交叉检查一下结果吗? E你是否可以强制错误条件发生? P是否满足性能要求?,测试方法:Right-BICEP(2),结果是否正确 如果代码能够运行正确,我要怎么才知道他是正确的呢? 至少需要确认代码所做的和你的期望是一致的。 使用数据文件 对于有大量测试数据的测试,考虑使用一个独立的数据文件来存储这些测试数据,然后单元测试读取该文件。 对于验证被测方法是正确的这件事情,如果某些做法能够使它变得更加容易,那就采纳它吧。 边界条件 一个想到可能的边界条件的简单办法就是记住助记短语CORRECT。 Conformance(一致性):值是否和预期的一致 Ordering(顺序性):值是否如应该的那样,是有序或者无序的 Range(区间性):值是否位于合理的最小值和最大值之间 Reference(依赖性):代码是否引用了一些不在代码本身控制范围内的外部资源 Existence(存在性):值是否存在(是否非null,非0,在一个集合中等) Cardinatity(基数性):是否恰好有足够的值 Time(绝对或者相对的时间性):所有的事情的发生是否是有序的?是否是在正确的时刻?是否恰好及时?,测试方法:Right-BICEP(3),检查反向关联 对于某些方法,可以使用反向的逻辑关系来验证他们。 用对结果进行平方的方式来检查一个计算平方根的方法,然后测试结果是否和原数据很接近 为了检查某条记录是否成功插入了数据库,你可以通过查询这条记录来验证。 使用其他手段来实现交叉检查 计算一个量会存在一个以上的方法。可以利用另一个方法来交叉测试原方法的结果。 使用类本身不同组成部分的数据来进行交叉检查。如图书馆的数据系统,可以通过借出数和库存数之和必定等于所藏书籍总量这种约束来进行检查。,测试方法:Right-BICEP(4),强制产生错误条件 真实世界中出现的错误:磁盘满,网络断等,可以利用Mock对象 环境方面的约束的考虑:系统过载、内存耗光等 性能特性 要检查的是性能特性,而不是性能本身。 性能特性有着“随着输入尺寸慢慢变大,问题慢慢变复杂”的趋势 性能特性的快速回归测试 由于测试时间较长,可以考虑每隔几天运行一次 需要使用一些测试工具,提纲,序言 一个单元测试示例 测试内容及方法 边界条件 单元测试工具 Mock对象 好的测试品质 在项目中进行测试,CORRECT边界条件(1),边界条件 一致性(Conformance):值是否符合预期的格式 有序性(Ordering):一组值是该有序的,还是该无序的 区间性(Range):值是否在一个合理的最大值和最小值的范围之内 引用、耦合性(Reference):代码是否引用了一些不受代码本身直接控制的外部因素 存在性(Existence):值是否存在(例如,非null,非零,包含于某个集合等) 基数性(Cardinality):是否恰好有足够的值 时间性(Time):所有事情是否都是按顺序发生的?是否在正确的时间?是否及时?,CORRECT边界条件(2),一致性 很多情况下,你所期望的或产生的数据必须符合某种特定的格式。 例如Email地址: 可能还会有一些附加的用点(.)隔开的部分: 可能还会有更少见的格式:firstname.lastname% 再如一类报告数据,包括一个头部记录,这个头部记录链接到了一些数据记录,最后是尾部记录。需要测试多少情况? 1.如果没有头部记录,只有数据记录和尾部记录,要怎样处理? 2.如果没有数据记录,只有头部记录和尾部记录,要怎样处理? 3.如果没有尾部记录,只有头部记录和数据记录,要怎样处理? 4.如果只有一个尾部记录,要怎样处理? 5.如果只有一个头部记录,要怎样处理? 6.如果只有一个数据记录,要怎样处理?,CORRECT边界条件(3),有序性 有时需要考虑数据的顺序或者是一个很大的数据集合中某一数据的位置 有时一个数据位于集合中的最前或者最后的时候,程序中的bug会暴露出来。这是有序性的一个方面,如查找最大值的方法 有时需要对一系列数据集合按照某种预定的情况进行排序操作。这是有序性的另一个方面。例如餐馆的菜单的集合。 还需要考虑一个数据集合已经排好序的情况或者已经反向排好序的情况。,CORRECT边界条件(4),区间性 对于一个变量,它所属类型的取值范围可能比你需要或想要得更加宽广。 如我们通常用整形来表示一个人的岁数,但是显然没有人能够活到20000岁,尽管20000是一个合法的整型值。 一个罗盘头指向的角度不可能大于360度等。 几乎所有的索引概念都应该被大量的测试 开始索引和结束索引有相同的值 第一个索引值大于最后一个索引值 索引值是负的 索引值大于允许值 Count不能匹配确切索引的个数 ,CORRECT边界条件(5),引用/耦合性 程序引用了哪些位于程序之外的事物 程序引用了哪些外部依赖 类应该处于什么样的状态 程序运行还需要存在哪些其他的条件 一个已知方法的前条件:系统必须处于什么状态下该方法才能运行。 一个已知方法的后条件:你的方法将会保证哪些状态发生,CORRECT边界条件(6),存在性 给定的事物存在吗? 如果它为null 如果它为空值 如果它等于0 其他与存在性相关的陷阱 确认你的方法处理了“不存在”的情况,CORRECT边界条件(7),基数性 这里的基数指的就是计数(counting) “0-1-n”原则 这是一个存在性相关的问题,但是你需要确信:你计算得到的数目和你所需要的数目是一致的。在大部分情况下,只须考虑下面三个问题: 1.零 2.一 3.多于一 针对基数性的测试主要考虑:是否具有两个或者多个测试对象。在某些情况下,具体数目的不同可能也会引入一些差异。 一个网店系统,当老板要求实时将客户预定的前十个产品的条目发送到老板的PDA上时,你需要测试哪些内容? 列表条目不足十个,列表为空,产品本身就没有十个等等,所有这些情况下,能出报表么? 突然过了三天老板需要前20个产品的条目,怎么办? 过了一周后老板又马上只要前5个产品的条目,怎么解决? 正确答案是“一行代码”,类似下面的 #define NUMBER_TO_RETAIN = 20;,CORRECT边界条件(8),时间性 需要始终记得以下这些与时间相关的方面: 相对时间(时间上的顺序) 绝对时间(消耗的时间和钟表上的时间) 并发问题 问题: 一年中的每一天都是24小时吗? 答案:“要看情况”。 在UTC(Universal Coordinated Time,Greenwich Mean Time即GMT的现代版)中,答案是“正确”; 在不遵守DST(Daylight Savings Time)的地方,答案是“正确”; 而在美国的大部分地区(),答案是“错误”。四月,会遇到23小时的一天;十月,会遇到25小时的一天。 不要期望底层库会替你正确处理这些问题。不幸的是,底层库遇到时间问题,很多代码会出问题。,CORRECT边界条件(9),问题 在前面的例子中我们使用了哪些边界条件?,提纲,序言 一个单元测试示例 测试内容及方法 边界条件 单元测试工具 Mock对象 好的测试品质 在项目中进行测试,单元测试工具,针对C/C+ 开源 xUnit框架 CppUnit :JUnit移植而来 CppUnitLite:CppUnit的轻量级版本 Google Test:功能强大 Unity :C语言 cMockery :C语言,集成Mock功能,CppUnitLite(1),文件目录 om CppUnitLite Failure.cpp Failure.h . CppUnitTests Cpp StackMain.cpp StackTest.cpp Stack.h readme.txt,核心代码,主程序,测试代码,CppUnitLite(2),使用VC 2008新建工程,CppUnitLite(3),导入文件 CppUintLite的源代码 测试代码,CppUnitLite(4),设置路径,CppUnitLite(5),主函数修改 int main() TestResult tr; TestRegistry:runAllTests(tr); getch(); return 0; ,CppUnitLite(6),被测函数 int Add(int Num1, int Num2) return (Num1 + Num2); 编写测试代码 TEST(CheckResult, Add) CHECK(Add(0x1, 0x2) = 0x3); ,CppUnitLite(7),运行编译结果,CppUnitLite(8),编写测试代码 TEST(CheckResult, Add) CHECK(0x3 = Add(0x1, 0x2) ); CHECK(0x3 = Add(0x2, 0x2) ); ,Google Test的安装(1),下载源代码 /files/gtest-1.3.0.zip 编译生成Lib文件 使用VC 2008编译msvc目录下的工程,生成gtestd.lib,Google Test的安装(2),设置gtest头文件路径,Google Test的安装(3),设置gtest.lib路径,Google Test的安装(4),Runtime Library设置,Google Test的使用(1),主函数的修改 void _tmain(int argc, _TCHAR* argv) testing:InitGoogleTest( ,Google Test的使用(2),被测函数 int Add(int Num1, int Num2) return (Num1 + Num2); 测试代码 TEST(AddTest, HandleNoneZeroInput) EXPECT_EQ(3, Add(1, 2); ,Google Test的使用(3),编译结果,Google Test的使用(4),测试代码 TEST(AddTest, HandleNoneZeroInput) EXPECT_EQ(3, Add(1, 2); EXPECT_EQ(3, Add(2, 2); ,Google Test的使用(5),两类断言 ASSERT_* 系列 当检查点失败时,退出当前函数。 EXPECT_* 系列 当检查点失败时,继续往下执行。,Google Test的使用(6),检查的种类 布尔值检查 数值型数据检查 字符串检查 显示返回成功或失败 异常检查 浮点型检查 类型检查,Google Test的使用(7),数值型数据检查,Google Test的参数化(1),需要测试多组数据时 TEST(IsPrimeTest, HandleTrueReturn) EXPECT_EQ(4, Add(1, 3); EXPECT_EQ(5, Add(2, 3); EXPECT_EQ(6, Add(3, 3); EXPECT_EQ(7, Add(4, 3); EXPECT_EQ(8, Add(5, 3); ,Google Test的参数化(2),参数化 class IsPrimeParamTest : public:testing:TestWithParam ; INSTANTIATE_TEST_CASE_P(TrueReturn, IsPrimeParamTest, testing:Values(1, 2, 3, 4, 5); TEST_P(IsPrimeParamTest, HandleTrueReturn) int n = GetParam(); EXPECT_EQ(n+3, Add(n, 3); ,参数生成器,Google Test的参数化(3),Google Test的参数化(4),运行结果,Google Test的死亡测试(1),死亡测试 通常在测试过程中,我们需要考虑各种各样的输入,有的输入可能直接导致程序崩溃,这时我们就需要检查程序是否按照预期的方式挂掉,这也就是所谓的“死亡测试”。 Google Test的死亡测试能做到在一个安全的环境下执行崩溃的测试案例,同时又对崩溃结果进行验证。 宏定义,Google Test的死亡测试(2),示例 int Div(int Num1, int Num2) return Num1/Num2; TEST(DivDeathTest, Demo) EXPECT_DEATH(Add(1,0), “); ,Google Test的死亡测试(3),运行结果,Unity(1),C语言 2个文件 unity.c unity.h 可以用TC、GCC、STD等编译,并下载到板卡运行,Unity(1),主程序 int main(void) Unity.TestFile = “main.c“; UnityBegin(); RUN_TEST(test_Add); /*添加测试函数*/ UnityEnd(); getch(); return 0; ,Unity(3),被测函数 int Add(int Num1, int Num2) return (Num1 + Num2); 测试代码 void test_Add(void) TEST_ASSERT_EQUAL(3, Add(1,2); ,Unity(4),编译运行结果,单元测试工具总结,优点 自动化 高效率 容易进行回归测试 案例库(测试用例)不断丰富,提纲,序言 一个单元测试示例 测试内容及方法 边界条件 单元测试工具 Mock对象 好的测试品质 在项目中进行测试,Mock对象(1),设计了一个定时器,每天下午17:00时产生报警。 测试时,是否真要等待到17:00?,Mock对象(2),示例:调用getTime()来返回系统当前的日期和时间 int Alarm() int i = getTime();/读取时钟芯片 if ( 17=i ) Sound();/发出报警声 return 1; else return 0; ,Mock对象(3),在进行测试时,可能需要的时间并非是系统当前的时间,怎么办?我们可以如下替换: int getTime() if(debug) return debug_cur_time; else /读取时钟芯片 上面的方法是替换的真正功能的手段之一,但有些凌乱,Mock对象(3),Mock对象 Mock对象可以帮助我们解决之前的替换功能。 Mock对象是真实对象在调试器的替代品。 系统中真实对象的一些情况: 真实对象具有不确定的行为(产生不可预料的结果,如股票行情) 真实对象很难被创建 真实对象的某些行为很难触发(如网络错误) 真实对象令程序的运行速度很慢 真实对象有(或者是)用户界面 测试需要询问真实对象他是如何被调用的 真实对象实际上并不存在(当需要和其他开发小组,或者新的硬件系统打交道的时候,这是一个普遍问题),Mock对象(4),借助于Mock对象,我们可以解决上面提到的所有问题,使用Mock对象的三个关键步骤: 使用一个接口来描述这个对象 为产品代码实现这个接口 以测试为目的,在Mock对象中实现这个接口 因为被测试代码只会通过接口来引用对象,所以他完全可以不知道他引用的究竟是真实对象还是Mock对象,cMockery(1),Google在2008年9月15日公开 C单元测试的一个轻量级的框架 免费且开源,google提供技术支持 轻量级的框架,使测试更加快速简单; 避免使用复杂的编译器特性,对老版本的编译器来讲,兼容性好 并不强制要求待测代码必须依赖C99标准,这一特性对许多嵌入式系统的开发很有用,cMockery(2),C语言 2个文件 cmockery.c cmockery.h 可以用GCC等编译,并下载到板卡运行,cMockery(3),模拟接口 int getTime(void) return mock(); 测试代码 void test_Alarm_succ(void *state) will_return(getTime, 18); assert_true(Alarm() = 0); void test_Alarm_fail(void *state) will_return(getTime, 17); assert_true(Alarm() = 1); ,cMockery(4),主函数 void main() const UnitTest tests = unit_test(test_Alarm_succ), unit_test(test_Alarm_fail), ; run_tests(tests); getch(); ,cMockery(5),编译运行,cMockery(6),分配一块内存,用来存储你在will_return中设定的返回值,用函数名字符串做索引; 在mock()中则通过调用mock的函数的名字去匹配,得到已经设定好的存储在堆内存上的那个值,并返回; 使被测试代码并不知道测试环境和真实环境之间的区别,因为它们都实现了相同的接口; 这样,我们就可以借助Mock对象,通过把时间设置为已知值,进而编写测试; 所有这些,就是Mock对象的全部:伪装出真实世界的某些部分,使你可以集中精力测试好自己编写的代码。,提纲,序言 一个单元测试示例 测试内容及方法 边界条件 单元测试工具 Mock对象 好的测试品质 在项目中进行测试,好的测试品质(1),好的测试应该具有以下品质,合称为A-TRIP 自动化(Automatic) 彻底的(Thorough) 可重复的(Repeatable) 独立的(Independent) 专业的(Professional),好的测试品质(2),自动化 调用测试自动化 调用一个或多个单元测试对你而言必须是非常容易的 维护这个环境最重要的在于:不要引入一个由于需要手动步骤而打破这个自动化模型的测试。 确保任何代码的签入都不会损坏位于任何机器的任何测试的机制 能够持续构建和测试 检查结果自动化 测试必须能够自己决定它是通过了还是失败了。 一致性回归的一个重要特征就是让测试能够检查自身。,好的测试品质(3),彻底的 他们测试了所有可能会出问题的情况 一个极端:对于每行代码、代码可能达到的每个分支、每个可能抛出的异常等等,都可以作为测试的对象。 另一个极端:你仅仅测试最可能的情况边界条件、残缺和畸形的数据等等。 Bug并不是均匀分布在原代码之中的,它们更倾向于扎堆于一块问题区域之中。,好的测试品质(4),代码覆盖 路径覆盖,好的测试品质(5),可重复 测试应该能够以任意的顺序一次又一次的运行,并且产生相同的结果。 每个测试应当每次产生相同的结果。如果没有,那么它应当告诉你代码中存在有真正的bug。 独立的 测试应该是简洁而且精炼的,这意味着每个测试都应该有很强的针对性,并且独立于环境和其他的测试。 编写测试时,确保你一次只测试了一样东西。 使用每个测试都有的setup和teardown以及每个类的setup和teardown来确保每个测试获得一个全新的开始。,好的测试品质(6),专业的 必须使用和产品代码相同的专业水准来编写和维护测试代码。 测试代码必须以同产品代码相同的风格来编写。 测试必须是完整的 意料中的结果是编写的测试代码至少和产品代码一样多。,好的测试品质(7),对测试进

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论