📄 200602281036115.html
字号:
<html>
<head><title>单元测试</title></head>
<center><h1>单元测试</h1></center>
<div><P align=right><FONT face=Verdana><FONT face=黑体 color=#ff0033><FONT face=Verdana><FONT color=#f70938><FONT face=黑体><a href="200604112229525.html" tppabs="http://www.itisedu.com/phrase/200604112229525.html" target="_new">中科永联</a>高级技术培训中心(</FONT><FONT face=黑体>www.itisedu.com</FONT><FONT face=黑体>)</FONT></FONT></FONT><IMG src="2006321215740134.jpg" tppabs="http://www.itisedu.com/manage/Upload/image/2006321215740134.jpg" border=0></FONT></FONT></P>
<P><FONT face=Verdana> <a href="200602281036115.html" tppabs="http://www.itisedu.com/phrase/200602281036115.html" target="_new">单元测试</a>(<a href="javascript:if(confirm('http://www.itisedu.com/phrase/200604240859415.html \n\nThis file was not retrieved by Teleport Pro, because it was unavailable, or its retrieval was aborted, or the project was stopped too soon. \n\nDo you want to open it from the server?'))window.location='http://www.itisedu.com/phrase/200604240859415.html'" tppabs="http://www.itisedu.com/phrase/200604240859415.html" target="_new">模块测试</a>)是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。例如,你可能把一个很大的值放入一个有序list 中去,然后确认该值出现在list 的尾部。或者,你可能会从字符串中删除匹配某种<a href="200603061709535.html" tppabs="http://www.itisedu.com/phrase/200603061709535.html" target="_new">模式</a>的字符,然后确认字符串确实不再包含这些字符了。</FONT></P>
<P><FONT face=Verdana> 单元测试是由<a href="200604232224305.html" tppabs="http://www.itisedu.com/phrase/200604232224305.html" target="_new">程序</a>员自己来完成,最终受益的也是程序员自己。可以这么说,程序员有责任编写功能代码,同时也就有责任为自己的代码编写单元测试。执行单元测试,就是为了证明这段代码的行为和我们期望的一致。</FONT></P>
<P><FONT face=Verdana> 工厂在组装一台电视机之前,会对每个元件都进行测试,这,就是单元测试。</FONT></P>
<P><FONT face=Verdana> 其实我们每天都在做单元测试。你写了一个函数,除了极简单的外,总是要执行一下,看看功能是否正常,有时还要想办法输出些数据,如弹出信息窗口什么的,这,也是单元测试,老纳把这种单元测试称为临时单元测试。只进行了临时单元测试的<a href="200604232134205.html" tppabs="http://www.itisedu.com/phrase/200604232134205.html" target="_new">软件</a>,针对代码的测试很不完整,代码覆盖率要超过70%都很困难,未覆盖的代码可能遗留大量的细小的错误,这些错误还会互相影响,当BUG暴露出来的时候难于调试,大幅度提高后期测试和维护成本,也降低了开发商的竞争力。可以说,进行充分的单元测试,是提高软件质量,降低开发成本的必由之路。</FONT></P>
<P><FONT face=Verdana> 对于程序员来说,如果养成了对自己写的代码进行单元测试的习惯,不但可以写出高质量的代码,而且还能提高编程水平。</FONT></P>
<P><FONT face=Verdana> 要进行充分的单元测试,应专门编写测试代码,并与产品代码隔离。老纳认为,比较简单的办法是为产品工程建立对应的测试工程,为每个<a href="200603090857555.html" tppabs="http://www.itisedu.com/phrase/200603090857555.html" target="_new">类</a>建立对应的测试类,为每个函数(很简单的除外)建立测试函数。首先就几个概念谈谈老纳的看法。</FONT></P>
<P><FONT face=Verdana> 一般认为,在结构化程序时代,单元测试所说的单元是指函数,在当今的<a href="200603101726185.html" tppabs="http://www.itisedu.com/phrase/200603101726185.html" target="_new">面向对象</a>时代,单元测试所说的单元是指类。以老纳的实践来看,以类作为测试单位,复杂度高,可操作性较差,因此仍然主张以函数作为单元测试的测试单位,但可以用一个测试类来组织某个类的所有测试函数。单元测试不应过分强调面向<a href="200603090845215.html" tppabs="http://www.itisedu.com/phrase/200603090845215.html" target="_new">对象</a>,因为局部代码依然是结构化的。单元测试的工作量较大,简单实用高效才是硬道理。</FONT></P>
<P><FONT face=Verdana> 有一种看法是,只测试类的接口(公有函数),不测试其他函数,从面向对象角度来看,确实有其道理,但是,测试的目的是找错并最终排错,因此,只要是包含错误的可能性较大的函数都要测试,跟函数是否私有没有关系。对于C++来说,可以用一种简单的方法区隔需测试的函数:简单的函数如数据读写函数的实现在头<a href="200602282323195.html" tppabs="http://www.itisedu.com/phrase/200602282323195.html" target="_new">文件</a>中编写(inline函数),所有在源文件编写实现的函数都要进行测试(构造函数和析构函数除外)。</FONT></P>
<P><FONT face=Verdana><STRONG>为什么要使用单元测试</STRONG></FONT></P>
<P><FONT face=Verdana> 我们编写代码时,一定会反复调试保证它能够编译通过。如果是编译没有通过的代码,没有任何人会愿意交付给自己的老板。但代码通过编译,只是说明了它的语法正确;我们却无法保证它的语义也一定正确,没有任何人可以轻易承诺这段代码的行为一定是正确的。</FONT></P>
<P><FONT face=Verdana> 幸运,单元测试会为我们的承诺做保证。编写单元测试就是用来验证这段代码的行为是否与我们期望的一致。有了单元测试,我们可以自信的交付自己的代码,而没有任何的后顾之忧。</FONT></P>
<P><FONT face=Verdana> 什么时候测试?单元测试越早越好,早到什么程度?<a href="200604231325145.html" tppabs="http://www.itisedu.com/phrase/200604231325145.html" target="_new">XP</a>开发理论讲究<a href="javascript:if(confirm('http://www.itisedu.com/phrase/200604232027375.html \n\nThis file was not retrieved by Teleport Pro, because it was unavailable, or its retrieval was aborted, or the project was stopped too soon. \n\nDo you want to open it from the server?'))window.location='http://www.itisedu.com/phrase/200604232027375.html'" tppabs="http://www.itisedu.com/phrase/200604232027375.html" target="_new">TDD</a>,即<a href="javascript:if(confirm('http://www.itisedu.com/phrase/200603121128285.html \n\nThis file was not retrieved by Teleport Pro, because it was unavailable, or its retrieval was aborted, or the project was stopped too soon. \n\nDo you want to open it from the server?'))window.location='http://www.itisedu.com/phrase/200603121128285.html'" tppabs="http://www.itisedu.com/phrase/200603121128285.html" target="_new">测试驱动开发</a>,先编写测试代码,再进行开发。在实际的工作中,可以不必过分强调先什么后什么,重要的是高效和感觉舒适。从老纳的经验来看,先编写产品函数的<a href="200603061723295.html" tppabs="http://www.itisedu.com/phrase/200603061723295.html" target="_new">框架</a>,然后编写测试函数,针对产品函数的功能编写<a href="200603291707535.html" tppabs="http://www.itisedu.com/phrase/200603291707535.html" target="_new">测试用例</a>,然后编写产品函数的代码,每写一个功能点都运行测试,随时补充测试<a href="200604240937105.html" tppabs="http://www.itisedu.com/phrase/200604240937105.html" target="_new">用例</a>。所谓先编写产品函数的框架,是指先编写函数空的实现,有返回值的随便返回一个值,编译通过后再编写测试代码,这时,函数名、参数表、返回<a href="200603051002565.html" tppabs="http://www.itisedu.com/phrase/200603051002565.html" target="_new">类型</a>都应该确定下来了,所编写的测试代码以后需修改的可能性比较小。</FONT></P>
<P><FONT face=Verdana> 由谁测试?单元测试与其他测试不同,单元测试可看作是编码工作的一部分,应该由程序员完成,也就是说,经过了单元测试的代码才是已完成的代码,提交产品代码时也要同时提交测试代码。测试部门可以作一定程度的审核。</FONT></P>
<P><FONT face=Verdana> 关于桩代码,老纳认为,单元测试应避免编写桩代码。桩代码就是用来代替某些代码的代码,例如,产品函数或测试函数调用了一个未编写的函数,可以编写桩函数来代替该被调用的函数,桩代码也用于实现测试隔离。采用由底向上的方式进行开发,底层的代码先开发并先测试,可以避免编写桩代码,这样做的好处有:减少了工作量;测试上层函数时,也是对下层函数的间接测试;当下层函数修改时,通过<a href="200603120943595.html" tppabs="http://www.itisedu.com/phrase/200603120943595.html" target="_new">回归测试</a>可以确认修改是否导致上层函数产生错误。</FONT></P>
<P><FONT face=Verdana> 在一种传统的结构化编程语言中,比如C,要进行测试的单元一般是函数或子过程。在象C++这样的面向对象的语言中, 要进行测试的基本单元是类。对Ada语言来说,开发人员可以选择是在独立的过程和函数,还是在Ada包的级别上进行单元测试。单元测试的原则同样被扩展到第四代语言(4GL)的开发中,在这里基本单元被典型地划分为一个菜单或显示界面。</FONT></P>
<P><FONT face=Verdana> 单元测试不仅仅是作为无错编码一种辅助手段在一次性的开发过程中使用,单元测试必须是可重复的,无论是在软件修改,或是移植到新的运行环境的过程中。因此,所有的测试都必须在整个<a href="200602281706245.html" tppabs="http://www.itisedu.com/phrase/200602281706245.html" target="_new">软件系统</a>的生命周期中进行维护。 </FONT></P>
<P><FONT face=Verdana> 经常与单元测试联系起来的另外一些开发活动包括代码走读(Code review),静态分析(Static analysis)和动态分析(Dynamic analysis)。静态分析就是对软件的源代码进行研读,查找错误或收集一些<a href="200604231331545.html" tppabs="http://www.itisedu.com/phrase/200604231331545.html" target="_new">度量</a>数据,并不需要对代码进行编译和执行。动态分析就是通过观察软件运行时的动作,来提供执行跟踪,时间分析,以及<a href="javascript:if(confirm('http://www.itisedu.com/phrase/200603121045465.html \n\nThis file was not retrieved by Teleport Pro, because it was unavailable, or its retrieval was aborted, or the project was stopped too soon. \n\nDo you want to open it from the server?'))window.location='http://www.itisedu.com/phrase/200603121045465.html'" tppabs="http://www.itisedu.com/phrase/200603121045465.html" target="_new">测试覆盖</a>度方面的信息。</FONT></P>
<P><FONT face=Verdana><STRONG>一些流行的误解</STRONG></FONT></P>
<P><FONT face=Verdana> 在明确了什么是单元测试以后,我们可以进行"反调论证"了。在下面的章节里,我们列出了一些反对单元测试的普遍的论点。然后用充分的理由来证明这些论点是不足取的。</FONT></P>
<P><FONT face=Verdana><STRONG> 它浪费了太多的时间<BR></STRONG> 一旦编码完成,开发人员总是会迫切希望进行软件的集成工作,这样他们就能够看到实际的系统开始启动工作了。 这在外表上看来是一项明显的进步,而象单元测试这样的活动也许会被看作是通往这个阶段点的道路上的障碍, 推迟了对整个系统进行联调这种真正有意思的工作启动的时间。 </FONT></P>
<P><FONT face=Verdana> 在这种开发步骤中,真实意义上的进步被外表上的进步取代了。系统能够正常工作的可能性是很小的,更多的情况是充满了各式各样的Bug。在实践中,这样一种开发步骤常常会导致这样的结果:软件甚至无法运行。更进一步的结果是大量的时间将被花费在跟踪那些包含在独立单元里的简单的Bug上面,在个别情况下,这些Bug也许是琐碎和微不足道的,但是总的来说,他们会导致在软件集成为一个系统时增加额外的工期, 而且当这个系统投入使用时也无法确保它能够可靠运行。</FONT></P>
<P><FONT face=Verdana> 在实践工作中,进行了完整计划的单元测试和编写实际的代码所花费的精力大致上是相同的。一旦完成了这些单元测试工作,很多Bug将被纠正,在确信他们手头拥有稳定可靠的部件的情况下,开发人员能够进行更高效的系统集成工作。这才是真实意义上的进步,所以说完整计划下的单元测试是对时间的更高效的利用。而调试人员的不受控和散漫的工作方式只会花费更多的时间而取得很少的好处。</FONT></P>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -