单元测试实践的主要问题及解决

上传人:阳*** 文档编号:46372689 上传时间:2021-12-13 格式:DOC 页数:40 大小:3.41MB
收藏 版权申诉 举报 下载
单元测试实践的主要问题及解决_第1页
第1页 / 共40页
单元测试实践的主要问题及解决_第2页
第2页 / 共40页
单元测试实践的主要问题及解决_第3页
第3页 / 共40页
资源描述:

《单元测试实践的主要问题及解决》由会员分享,可在线阅读,更多相关《单元测试实践的主要问题及解决(40页珍藏版)》请在装配图网上搜索。

1、单元测试实践的主要问题与解决广州凯乐软件技术有限公司技术总监 王彤本文是我在“第十届中国系统与软件过程改进年会广东会场”所作演讲的整理稿,主要分享单元测试的一些要点、单元测试实践的主要问题,以及如何来解决这些问题。一、 单元测试概述1.1 什么是单元测试单元测试,就是针对代码单元的独立测试。为什么需要单元测试呢?这是代码的基本特性决定了的。代码有一个基本特性,就是对数据分类处理。代码通常会有很多的判定。一个判定,就是一次分类。嵌套的判定,会使分类次数的翻倍。如果我们在写代码的时候,有一个分类漏掉了,就会产生一个Bug;如果一个分类,虽然写了代码,但是处理不正确,也会产生一个Bug。一个函数要没

2、有错误,必须做到两点:1,对数据的分类必须完整;2,每一个分类的处理必须正确。做到了这两点,就可以说,代码的功能逻辑是正确的。那么,如何检测代码的功能逻辑是否正确呢?调试,是临时的,且不完整的,例如,一个函数有十种输入,调试能覆盖五六种就不错了。而系统测试,并不针对某个具体的函数,不关注某个函数的功能逻辑是否正确。要检测某个函数的功能逻辑,就必须要依照分类列出数据,检测代码是否对每一个分类都做了处理,而且每一个分类的处理是否正确。这就是单元测试。1.2 单元测试的基本方法由上面的分析可以看出,单元测试的基本方法就是:依数据的分类列出输入,执行被测试程序,然后,判断输出是否符合预期。单元测试能达

3、到什么样的效果呢?那就是:无论别人怎么样,我总是对的!这里的“别人”,是指关联代码。“我”,是指当前正在编写或测试的代码。单元测试要做到的是,无论关联代码是否有错,都要保证我是对的。具体来说,我要考虑关联代码会产生什么样的数据,这些数据要如何分类处理,只要我的分类和处理是正确的,那么,无论别人怎么样,我总是对的。1.3 单元测试的效益单元测试的效益可以说是立竿见影,并且会推动整个开发过程的改进。首先,单元测试可以保证代码的质量。因为只有单元测试,能够全面检测代码单元的功能逻辑,排除代码中大量的、细小的错误。其次,排错成本最小。如果在编码阶段同时进行单元测试,排错成本可以忽略不计。但若到了后期,

4、排错成本可能会增长上百倍,要是产品已经到了用户手里,那造成的损失就更难说了。第三,提升开发效率。单元测试可以让程序行为一目了然,也就是程序行为可视化。什么叫程序行为呢?就是什么输入下,会执行哪些代码,会产生什么输出。如下图,黑色的代码是当前输入下所执行代码。如果我们写几行代码,就可以看到程序的行为,相当于写文章时上下文可见,这可以促进我们的开发思维。如果我们的思维有了偏差,也可以及时发现。如果代码中有了错误,也可以随时排除。那么,是不是整个项目的所有代码都做了单元测试,才能得到这些效益呢?不是的。80:20规则,在软件开发过程中也存在。也就是说,80%的代码错误,可能存在于20%的代码中;80

5、%的编码、调试成本,可能会消耗在20%的代码上。这20%,就是算法密集度高的代码,也就是功能逻辑复杂的代码。这些代码可能只有20%,但是却可能包含了80%的错误,消耗了80%的编码、调试时间,即使只对这部分代码进行单元测试,在提升产品的质量和开发效率方面,也会产生立竿见影的效果。第四,自动回归。如果没有单元测试,系统测试发现了错误,当然要修改代码,而修改代码可能引入新的错误,又要进行全面的系统测试,这样就可能陷入循环,这通常也是项目延期的主要原因。如果有了单元测试,修改代码时可以通过回归测试马上检测是否引入了新的错误。所谓回归,就是回复到原来正确的状态。正是回归测试,使单元测试对整个开发过程的

6、改进都产生积极影响,使项目适应频繁变化的需求。单元测试是敏捷开发的基础和核心,反过来说,有了单元测试,开发过程会自动趋于敏捷。单元测试也降低了后期测试的压力。二、 单元测试实践的主要问题单元测试有个特点:测试简单独立的代码很容易,但要在实际工作中做好单元测试却很困难。根据我们的经验,企业在实施单元测试时,通常会面对四大问题l 不愿做:程序员没有单元测试习惯。l 没时间:编写测试代码需要耗费大量的时间,项目的周期可能不允许。l 做不了:代码具有较高的耦合性,使单元测试难以进行。l 做不好:测试效果不能令人满意。我们通常会以覆盖率来衡量测试效果,但要实现高标准的测试覆盖很困难。三、 解决思路和方法

7、如何解决上述问题呢?接下来,谈谈一些思路和方法,使用的工具是Visual Unit。Visual Unit,简称VU,是可视化的C/C+单元测试工具。3.1 如何解决“不愿做”和“没时间”对于“不愿做”,我们采用的对策是可视化,这个可视化,是指程序行为可视,后面我会用案例来演示;对于“没时间”,采用的对策是自动化,通过自动生成测试代码、自动打桩等功能,让测试的时间成本最小化。这两者结合起来,就是ETDD开发模式。那么,ETDD是什么呢?首先来介绍一下TDD,TDD就是测试驱动开发,这个大家可能听得比较多了。ETDD就是Easy TDD,即:易行版的TDD。ETDD具有以下一些特点:l 可视化,

8、在开发过程中,程序行为可视。l 自动化,除了测试数据需要人工设定外,其他基本上都自动完成。l 现实化,不一定要测试所有代码,在开始阶段,可以只测试功能逻辑复杂的20%代码。下面,我用一个案例,讲解一下ETDD的过程:假如我要编写一个函数,它的功能是删除字符串左边的空格。先写好函数的框架,能通过编译就行。在编写代码前,程序员必须要做的一件事情,是想清楚代码的功能。如果我们想的时候,顺手把它记录下来,就可以让代码的功能更清晰、更明确。我们现在来记录代码的功能。这里的记录,不是文字形式的宠统说明,而是数据形式的精确定义,也就是用输入和输出的方式来记录。首先,记录最基本的功能,也就是最基本、最常见的输

9、入和输出。输入一个左边有空格的字符串,输出是删除左边空格后的字符串,返回值跟参数的输出是一样的。然后,记录详细的功能。例如,左边没有空格的,全是空格的,还有空字符串。把每种输入的正确输出也记录一下。完成了这个工作后,代码的功能就完全定义下来了。现在,我们开始编写代码。我的编码思路是这样的:分为两步,第一步计算左边的空格数量;第二步,将非空格的字符向左移动,覆盖掉左边的空格。以下几行代码,计算左边的空格,现在编译一下。CTRL+F7。如果编译通过,测试就会自动运行。我们可以看到,输入是什么,执行了哪些代码,产生了什么输出。这里,黑色的是当前输入下所执行的代码,未执行的话会显示为红色。这里全是黑色

10、,表示当前输入下执行了全部代码。如果我们想看一下计算左边空格的结果对不对,这是内部的数据,要指定位置后才会打印出来。按ESC键回到开发环境。用这种语法可以输出内部数据,适合于程序员开发过程中使用。复杂类型也可以用同样的语法输出。另一种输出内部数据的语法是,在左边的代码窗口,在要输出的位置点击一下,右键菜单选择“输出内部数据”,这样填一下就行了。这种方式不会修改产品代码,适合于测试员使用。再次执行后,可以看到,左边的空格的数量是4,这是对的,那我们可以继续编写。新加的这几行代码完成字符串的移动。这样,代码基本上写完了,结果对不对呢?CTRL+F7编译一下。结果是完全不对的。我们来分析一下,输入是

11、这个,全部代码都是黑色,表示都执行到了,跟我设想的一样。问题在哪里呢?看一下计算左边空格的代码,经过计算后,指针偏移了,所以后面的计算,使用的是不正确的指针。我们把指针先保存一下,第二次计算前再恢复回来。看看结果怎么样。现在,参数的输出是正确的了。但是,返回值还是不对,返回值应该跟参数一样。分析一下,经过这里的计算后,指针再次偏移了,返回前没有恢复,所以,返回的是不正确的指针。返回前,再次把指针恢复。看看结果。现在,结果是正确的了。看一下测试结果,还有一个异常。点击它,可以看到,是空指针产生了这个异常,我们的代码没有对空指针进行处理。在这里,可以很清晰的看到代码的执行状况。前面三行是黑色的,第

12、四行开始都是红色的,表示代码只执行到第三行,也就是说,第三行产生了异常。添加处理空指针的代码。现在,代码写完了,单元测试也同步完成了。我们来回顾一下ETDD过程:跟传统开发模式相比,ETDD多付出的,是把以前仅在头脑里想的代码功能记录下来,从而精确地、完整地进行代码的功能设计。ETDD所得到的,是在编写代码的过程中,随时可以看到代码的行为,这可以让我们的编码过程变得轻松,而且也基本上不用调试,大家知道,调试,是最花费时间的。另一方面,只要这里设定的数据是完整的,那么,我们的代码就没有问题。将来,如果需要修改代码,只要重新执行一下测试,就可以知道是不是破坏了原有的功能。小结:ETDD通过可视化来

13、帮助程序员轻松地编写程序,单元测试不再是一个负担;ETDD通过自动化,使程序员只需要在考虑代码功能时顺手记录一下,其他工作都由工具完成。ETDD提升了编码的效率,也省略大部分调试,从而大幅提升了生产力。3.2 如何解决“做不了”上面我们只是用一个独立的函数来演示ETDD过程。在实际的工作中,代码之间通常是互相依赖的,这种依赖关系会造成测试难于进行,这就是“做不了”的问题。我们首先来分析一下。“做不了”主要是指可测性问题。可测性问题的核心是内部输入。在解释内部输入前,我们先来看一下一般的输入:外部输入。外部输入是指在被测代码的外部可以设定的输入,包括参数、成员变量、全局变量。外部输入一般可以直接

14、设定。单元测试的核心难点在于内部输入,什么是内部输入呢?像下面这个例子,这两个数据,都是在被测试代码的内部,通过调用关联代码来取得,也就是内部取得的数据。对于内部取得的数据,代码要如何处理呢?跟参数一样,也是分类处理。因此,测试时也要分类检测,这就是内部输入。内部输入有六种情形,我们利用工具都可以处理。解决内部输入的主要方法有打桩、模拟对象、底层模拟。先来介绍打桩。桩就是代替真实代码的一些代码。桩的功能主要有隔离、补齐和控制。可以通过编写桩代码,来解决内部输入问题。这是桩的控制功能。用打桩来解决内部输入,有一些问题:一是编写桩代码增加了工作量;二是内部输入和外部输入分离,难于管理;三是只能解决

15、部分内部输入问题。例如,要在一个用例中多次调用同一关联函数,要求每次输出不同,桩代码就很难做到。解决内部输入的另一个方法是模拟对象,这个比较复杂,另外,对于C和C+也不太适用。我们可以采用底层模拟来解决内部输入问题。底层模拟有三个特点:一是内部输入与外部输入一起管理;二是不需要考虑关联代码的状态,无所关联代码是否存在,是否隔离,都可以直接使用;三是不需要编写代码。下面我也用一个案例来讲解一下底层模拟。这个示例,是一个空调控制程序。代码的功能,是首先取得环境的温度,然后与预设的目标温度比较,计算出温度差,温度每差一度,制冷器运行60秒。首先,我们设定外部数据。假设,预设的目标温度是25度,是这个

16、全局变量,设为25。返回值为1,表示操作成功。假设环境温度是28度,那么,制冷器应该运行180秒,这里填180。然后执行测试。由于环境温度还没有设定,测试进行不下去。环境温度由这个函数来取得。即使这个函数可以正常工作,取到的环境温度也不可能满足我们的测试需求。我们可以用底层模拟来解决。首先,我们要让这个取温度的函数返回1,表示取温度成功。双击函数名。模拟值填1。然后,设定环境的温度。双击这个表示环境温度的参数。模拟值填28。再看测试结果。现在测试就可以正常进行了。这个参数的输出是180,跟我们预期的一样。内部输入这里,显示了两个内部输入。这是我们设定的内部输入,和外部输入可以一起管理。我们也可

17、以把它移到表格中。在表格中,我们增加一个用例,把温度设为30,直接设定就是了。这是环境温度为30度时的测试结果,制冷器的运行时间为300。上面演示的是简单类型的底层模拟,复杂类型也一样可以模拟,下面我演示一下。这个底层函数返回的是一个对象指针,如何模拟呢?双击函数名,打开底层模拟器。首先,在前置代码中定义对象并初始化。然后,在模拟值中填写这个对象的地址。这是模拟的结果。复杂对象的数据一样可以移到表格中,这时,要移到表格中的不是对象本身,而是对象中包含的数据。例如,要把data.ui移到表格中,双击它的值“1234”就行了。我们还可以用局部数据模拟的功能,处理各种各样的复杂情形。例如,以下函数处

18、理的是由界面输入的数据,这也是单元测试的一个难点。可以使用局变数据模拟,把界面输入转换成普通的内部输入。这个函数的逻辑功能是计算SQL字符串,但计算结果没有输出到外部,这是内部输出,工具也可以判断内部输出是否正确。下图是测试结果:内部输入解决之后,无论别人(关联代码),是否存在,是否正确,是否被隔离,都可以完整检测我(当前代码)。检测我是否对所有数据,包括内部输入,都做了正确的分类和处理。从而实现单元测试的目标:无论别人怎么样,我总是对的! 如果所有代码单元都做到了这一点,那会怎么样呢?整个项目就没有代码错误。来看看嵌入式测试。在设备上进行单元测试不仅难度大、成本高,也无法达到应有的效果。如果

19、在设备上测试,设备的一些输出是难于控制的,例如这个例子,假设只有在发生雷击时,获取前车距离的函数才会返回失败,那我们是不是等着雷击呢?即使不考虑成本,嵌入式单元测试也应该在PC上进行,这样才能做到“我总是对的”。3.3 如何解决“做不好”现在来看做不好的问题。做不好的主要原因,是高标准的测试覆盖难以实现。为什么要关注测试覆盖呢?因为未覆盖的单位,通常对应未测试的数据分类,也就是说,可以用覆盖率来检查测试的完整性,衡量测试效果。应该在完成功能测试的基础上,统计覆盖率,找出遗漏用例来完成白盒覆盖,而不是功能测试做一遍,白盒覆盖又做一遍。下面,我用一个案例来演示讲解覆盖。首先是覆盖率统计,工具可以支

20、持六种覆盖:语句、条件、分支、C/DC(判定条件覆盖)、路径覆盖、MC/DC(修正判定条件覆盖)。哪些单位没覆盖呢?这个红色且带淡红色背景的,是未覆盖语句;这个T是未覆盖的条件真值;这个F是未覆盖的条件假值;这个M是未覆盖的MC/DC。淡红色背景的分支是未覆盖分支,淡绿色背景的是已覆盖分支。路径是从入口到出口的路线,这条用绿色画出的是已覆盖的路径。这条用红色画出的是未覆盖路径。如何完成覆盖呢?点击未覆盖的单位,比如这个T,右键菜单选择“用例设计”。工具会自动计算出一个近似用例,所谓近似用例,就是经过最小修改就可以覆盖选中单位的用例。如何修改呢?工具提供了修改提示,按这个蓝色粗体的提示修改就可以

21、了。这里的提示是A >1,把它改为大于1的数,如2。在实际工作中,输出也要根据功能进行修改,这里忽略。执行测试后,可以看到刚才那个T已经覆盖了。点击F,打开用例设计器。这里的提示是B不等于0,把B改为不等于0,比如1。现在来覆盖这个T。把X改为大于1的数,如2。现在,代码这边已经完成全部覆盖了,看一下覆盖率,还有一条路径未覆盖。在这里选择未覆盖的路径,打开用列设计器。提示是:A不等于2,X小于等于1,X本来就小于1,不用改它,把A改为不等于2的数就行了,如3。现在,完成了全部覆盖。总结我们用可视化来解决“不愿做”,用自动化来解决“没时间”,这两者结合起来,就是ETDD开发模式。造成做不了的主要原因是代码的耦合关系形成的内部输入问题,我们用底层模拟来解决内部输入,真正可以做到“无论别人怎么样,我总是对的”。在覆盖方面,我们利用工具不仅统计覆盖率,清晰标示未覆盖单位,而且,用例设计器可以帮助我们快速找出遗漏用例,实现高覆盖,解决做不好的问题。40 / 40文档可自由编辑打印

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

copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!