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

恒温 · 2012年10月18日 · 最后由 simple 回复于 2012年10月22日 · 2216 次阅读

Link http://blog.jobbole.com/29300/?utm_source=rss&utm_medium=rss&utm_campaign=%25e5%258d%2595%25e5%2585%2583%25e6%25b5%258b%25e8%25af%2595%25e5%25ae%259e%25e8%25b7%25b5%25e7%259a%2584%25e4%25b8%25bb%25e8%25a6%2581%25e9%2597%25ae%25e9%25a2%2598%25e4%25b8%258e%25e8%25a7%25a3%25e5%2586%25b3

来源:dellfox 的博客
摘要:本文是我在 “第十届中国系统与软件过程改进年会广东会场” 所作演讲的整理稿,主要分享单元测试的一些要点、单元测试实践的主要问题,以及如何来解决这些问题。
一、 单元测试概述

  1. 1 什么是单元测试 单元测试,就是针对代码单元的独立测试。为什么需要单元测试呢?这是代码的基本特性决定了的。代码有一个基本特性,就是对数据分类处理。 代码通常会有很多的判定。一个判定,就是一次分类。嵌套的判定,会使分类次数的翻倍。

如果我们在写代码的时候,有一个分类漏掉了,就会产生一个 Bug;如果一个分类,虽然写了代码,但是处理不正确,也会产生一个 Bug。一个函数要没有错误,必须做到两点:1,对数据的分类必须完整;2,每一个分类的处理必须正确。做到了这两点,就可以说,代码的功能逻辑是正确的。

那么,如何检测代码的功能逻辑是否正确呢?
调试,是临时的,且不完整的,例如,一个函数有十种输入,调试能覆盖五六种就不错了。而系统测试,并不针对某个具体的函数,不关注某个函数的功能逻辑是否正确。
要检测某个函数的功能逻辑,就必须要依照分类列出数据,检测代码是否对每一个分类都做了处理,而且每一个分类的处理是否正确。
——这就是单元测试。

  1. 2 单元测试的基本方法 由上面的分析可以看出,单元测试的基本方法就是:依数据的分类列出输入,执行被测试程序,然后,判断输出是否符合预期。

单元测试能达到什么样的效果呢?那就是:无论别人怎么样,我总是对的!
这里的 “别人”,是指关联代码。“我”,是指当前正在编写或测试的代码。单元测试要做到的是,无论关联代码是否有错,都要保证我是对的。具体来说,我要考虑关联代码会产生什么样的数据,这些数据要如何分类处理,只要我的分类和处理是正确的,那么,无论别人怎么样,我总是对的。

  1. 3 单元测试的效益 单元测试的效益可以说是立竿见影,并且会推动整个开发过程的改进。 首先,单元测试可以保证代码的质量。因为只有单元测试,能够全面检测代码单元的功能逻辑,排除代码中大量的、细小的错误。

其次,排错成本最小。如果在编码阶段同时进行单元测试,排错成本可以忽略不计。但若到了后期,排错成本可能会增长上百倍,要是产品已经到了用户手里,那造成的损失就更难说了。

第三,提升开发效率。单元测试可以让程序行为一目了然,也就是程序行为可视化。什么叫程序行为呢?就是什么输入下,会执行哪些代码,会产生什么输出。如下图,黑色的代码是当前输入下所执行代码。

如果我们写几行代码,就可以看到程序的行为,相当于写文章时上下文可见,这可以促进我们的开发思维。如果我们的思维有了偏差,也可以及时发现。如果代码中有了错误,也可以随时排除。
那么,是不是整个项目的所有代码都做了单元测试,才能得到这些效益呢?不是的。80:20 规则,在软件开发过程中也存在。也就是说,80% 的代码错误,可能存在于 20% 的代码中;80% 的编码、调试成本,可能会消耗在 20% 的代码上。这 20%,就是算法密集度高的代码,也就是功能逻辑复杂的代码。

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

正是回归测试,使单元测试对整个开发过程的改进都产生积极影响,使项目适应频繁变化的需求。单元测试是敏捷开发的基础和核心,反过来说,有了单元测试,开发过程会自动趋于敏捷。单元测试也降低了后期测试的压力。

二、 单元测试实践的主要问题
单元测试有个特点:测试简单独立的代码很容易,但要在实际工作中做好单元测试却很困难。
根据我们的经验,企业在实施单元测试时,通常会面对四大问题——
不愿做:程序员没有单元测试习惯。
没时间:编写测试代码需要耗费大量的时间,项目的周期可能不允许。
做不了:代码具有较高的耦合性,使单元测试难以进行。
做不好:测试效果不能令人满意。我们通常会以覆盖率来衡量测试效果,但要实现高标准的测试覆盖很困难。
三、 解决思路和方法
如何解决上述问题呢?接下来,谈谈一些思路和方法,使用的工具是 Visual Unit。Visual Unit,简称 VU,是可视化的 C/C++ 单元测试工具。

  1. 1 如何解决 “不愿做” 和 “没时间” 对于 “不愿做”,我们采用的对策是可视化,这个可视化,是指程序行为可视,后面我会用案例来演示;对于 “没时间”,采用的对策是自动化,通过自动生成测试代码、自动打桩等功能,让测试的时间成本最小化。这两者结合起来,就是 ETDD 开发模式。 那么,ETDD 是什么呢? 首先来介绍一下 TDD,TDD 就是测试驱动开发,这个大家可能听得比较多了。ETDD 就是 Easy TDD,即:易行版的 TDD。ETDD 具有以下一些特点: 可视化,在开发过程中,程序行为可视。 自动化,除了测试数据需要人工设定外,其他基本上都自动完成。 现实化,不一定要测试所有代码,在开始阶段,可以只测试功能逻辑复杂的 20% 代码。 下面,我用一个案例,讲解一下 ETDD 的过程: 假如我要编写一个函数,它的功能是删除字符串左边的空格。 先写好函数的框架,能通过编译就行。在编写代码前,程序员必须要做的一件事情,是想清楚代码的功能。如果我们想的时候,顺手把它记录下来,就可以让代码的功能更清晰、更明确。

我们现在来记录代码的功能。这里的记录,不是文字形式的宠统说明,而是数据形式的精确定义,也就是用输入和输出的方式来记录。
首先,记录最基本的功能,也就是最基本、最常见的输入和输出。输入一个左边有空格的字符串,输出是删除左边空格后的字符串,返回值跟参数的输出是一样的。

然后,记录详细的功能。例如,左边没有空格的,全是空格的,还有空字符串。

把每种输入的正确输出也记录一下。完成了这个工作后,代码的功能就完全定义下来了。

现在,我们开始编写代码。我的编码思路是这样的:分为两步,第一步计算左边的空格数量;第二步,将非空格的字符向左移动,覆盖掉左边的空格。

以下几行代码,计算左边的空格,现在编译一下。CTRL+F7。如果编译通过,测试就会自动运行。

我们可以看到,输入是什么,执行了哪些代码,产生了什么输出。这里,黑色的是当前输入下所执行的代码,未执行的话会显示为红色。这里全是黑色,表示当前输入下执行了全部代码。如果我们想看一下计算左边空格的结果对不对,这是内部的数据,要指定位置后才会打印出来。按 ESC 键回到开发环境。

用这种语法可以输出内部数据,适合于程序员开发过程中使用。复杂类型也可以用同样的语法输出。

另一种输出内部数据的语法是,在左边的代码窗口,在要输出的位置点击一下,右键菜单选择 “输出内部数据”,这样填一下就行了。这种方式不会修改产品代码,适合于测试员使用。

再次执行后,可以看到,左边的空格的数量是 4,这是对的,那我们可以继续编写。

相关文章陈皓:单元测试要做多细?老派程序员——徒手实现伟大成就迈出单元测试的第一步编写综合的单元测试高效的 JavaScript 单元测试

Link http://blog.jobbole.com/29300/?utm_source=rss&utm_medium=rss&utm_campaign=%25e5%258d%2595%25e5%2585%2583%25e6%25b5%258b%25e8%25af%2595%25e5%25ae%259e%25e8%25b7%25b5%25e7%259a%2584%25e4%25b8%25bb%25e8%25a6%2581%25e9%2597%25ae%25e9%25a2%2598%25e4%25b8%258e%25e8%25a7%25a3%25e5%2586%25b3

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 1 条回复 时间 点赞

开发没时间写 UT,怎么办?就算上头下命令也不行,因为开发确实已经一直在加班了,且抛开加班是否合理不谈,确实没时间让他们去写 UT

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册