版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
单元测试调研与JunitExt实验报告林立明SC09011061什么是单元测试(引自《单元测试之道》)单元测试是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。例如,你可能把一个很大的值放入一个有序list中去,然后确认该值出现在list的尾部。或者,你可能会从字符串中删除匹配某种模式的字符,然后确认字符串确实不再包含这些字符了。执行单元测试,是为了证明某段代码的行为确实和开发者所期望的一致。对于客户或最终使用者而言,这种测试必要吗,它与验收测试有关吗?这个问题仍然很难回答。事实上,我们在此并不关心整个产品的确认、验证和正确性等;甚至此时我们都不去关心性能方面的问题。我们所要做的一切就是要证明代码的行为和我们的期望一致。因此,我们所要的测试是小规模的、非常独立的功能片段。通过对所有单独部分的行为建立起信心,确信它们都和我们的期望一致;然后我们才能开始组装和测试整个系统。毕竟,要是我们对手上正在写的代码的行为是否和我们的期望一致都没有把握,那么其他形式的测试也都只是浪费时间而已。在单元测试之后你还需要其他形式的测试,可能是更正规的测试,那一切就都要看环境的需要来决定了。总之,做测试如同做善事,总是要从点滴开始。为什么要用单元测试(引自《单元测试之道》,部分文字有改动)有两种开发者:A和B。A每天都在着急地编写代码,写完一个类又写一个类,写完一个函数又接着写另一个函数,还经常不得不停下来做一些调整,使得代码能够编译通过。A一直保持这种工作方式,直到最后期限的前一天,这时已是演示所有代码的时候了。可惜代码不能工作了,困惑的A只能回过头来看代码,尝试着跟踪一下这个难以琢磨的调用流程。A终于找到并修正了这个bug,但是这个过程中,他又找到了其他好几个bug;如此几次之后,bug还是存在。A已经筋疲力尽了,完全搞不清楚为什么会这样,认为这样是毫无道理的。与此同时,B在写一个函数的同时会附带一个简短的测试程序来测试这个函数。只有等到已知函数都得到确认后,B才会继续编写下一个函数,然后调用前面的函数。最后期限终于到了,A未能完成任务,而B的代码被集成到整个系统中,并且能够很好地运行。之后,在B的模块中,出现了一个小问题,但是B很快就发现了问题所在,在几分钟之内就把问题解决了。在上面的小故事里,A因为假设底层的代码是正确无误的而卷入麻烦之中,先是在高层代码中使用了底层代码;然后这些高层代码又被更高层代码所使用,如此往复。在对这些代码的行为没有任何信心的前提下,A等于是在假设上面用竖立卡片堆起了一间房子——只要将下面的卡片轻轻移动,整间房子就会轰然倒塌。当基本的底层代码不再可靠时,那么必需的改动就无法局限在底层。虽然你可以修正底层的问题,但是这些对底层代码的修改必然会影响到高层代码,于是高层代码也连带地需要修改;依次递推,就很可能会动到更高层的代码。于是,一个对底层代码的修改,可能会导致对几乎所有代码的一连串改动,从而使修改越来越多,也越来越复杂。于是,整间由卡片堆成的房子就由此倒塌,从而使整个项目以失败告终。所以,单元测试势在必行。项目背景实验室为KD50,KD60设计了一套监控系统,先后采用了串口、USB、网络等通讯方式,最新版本里PC将采用http与底层单片机的进行数据传输。结构如下图所示:图1换句话说,需要在单片机上实现一个小型的webserver,服务器端包括两个网页文件login.html以及control_page.html。login.html是登陆页面,用来获得用户的姓名和密码。当在浏览器地址栏输入0并回车的时候,等价于使用GET方式向服务器请求默认页面,此时单片机返回login.html,如图1所示。用户输入姓名和密码并提交时,等价于使用POST方法向服务器请求页面,服务器检查用户名和密码,如果不合法,则停留在登陆页面;如果合法,则返回control_page.html,如图3所示,控制页面包含监控需要的所有数据以及动作按钮,一旦有按钮被按下,则等价于使用POST方法向服务器提交请求,服务器根据http报文数据执行相应的顶层动作(比如关掉电源、提高风扇速度等),然后刷新控制页面。当没有按钮被按下时,服务器也会定时刷新控制页面上的数据并传输给浏览器。图2图3实现以上功能的一个关键的地方在于准确无误地建立http报文,一个典型的http包包括包头和数据,包头和数据之间用回车-换行符隔开,在项目中,数据包含了服务器返回的完整页面的html描述,所以代码实现上,整个http包(分为html_header和html_body)表示为:charcodehtml_header[]={"HTTP/1.1200OK\n""Cache-control:no-cache\n""Connection:Keep-Alive\n""Content-Length:TAG:LEN1\n""Content-Type:text/html\r\n\r\n"};charcodebody[]={"<html>\n""<head>\n""<metahttp-equiv=\"Content-Type\"content=\"text/html;charset=gb2312\">\n""<title>监控主页</title>\n""</head>\n""<body>\n"……(此处略去相关代码)"</body>\n""</html>\n"};服务器响应客户端的数据长度由“Content-Length:TAG:LEN1\n”头域表示,这里用了长度固定为8个字符的“TAG:LEN1”来充当占位符,实际传输时则替换成数据的总长度,比如控制页面上的相关变量更新完毕后总的页面数据为3078,则将“TAG:LEN1”替换成“3078”,得到“Content-Length:3078\n”,接下来只需将header和body组装成完整的http包即可发送给浏览器了。另外,控制页面的html代码中,对需要实时更新的所有数据域也是采用占位符的处理方式,需要时用实际数值替换之,如此一来,在字符串中查找特定的模式串,并加于替换就成了单片机代码需要实现的一个重要功能。这里通过编写两个函数来实现,一个负责查找,一个负责替换:////从haystack中找出和needle匹配的第一个字符串,并返回其位置的首址//char*strstr(char*haystack,char*needle){ char*ptr1,*ptr2; if(*needle==0)return(haystack); for(;*haystack;haystack++) { for(ptr1=needle,ptr2=haystack;*ptr1&&(*ptr1==*ptr2);++ptr1,++ptr2); if(*ptr1==0)return(haystack); } returnNULL;}////先在start里面把tag找到,然后将sub填充到start里面去//voidreplace_tag(UCHARxdata*start,char*tag,char*sub){UCHARidatai,flg;UCHARxdata*ptr;ptr=strstr(start,tag); if(ptr==NULL)return; flg=TRUE;for(i=0;i<8;i++) { if(sub[i]==0)flg=FALSE; if(flg)ptr[i]=sub[i];elseptr[i]=SPACE; }}软件需求根据前面对项目背景的介绍,我们把软件需求重新描述如下:给定一个字符串a,一个模式串b,以及一个替换串c,在a中匹配b,将a中出现第一次匹配部分替换成c,如果没有匹配存在,则不对a做任何改动。初步实现由于有前面所述C语言版本的函数实现,很容易用Java语言对其进行重写,无非是前者用到了指针操作,后者用数组下标等效替代而已。另外,原来项目代码出于单片机资源有限以及代码简单性考虑,假设模式串长度始终为8个字符(详见项目背景部分的的描述),新的软件需求取消了这个假设,故代码实现也要做相应改动。重写后的代码如下(只给出主要函数实现,其余部分详见附录部分代码清单):……(省略号表示此处略去相关代码,下同)publicclassFind_and_Replace{ privateStringexpectedString; privateStringmatchString; privateStringpreString; privateStringreplaceString; publicintFind(char[]target,char[]matchment) { inti,j,k; for(i=0;i<target.length;i++) { for(j=i,k=0;k<matchment.length&&matchment[k]==target[j];j++,k++); if(k==matchment.length) returni; } return-1; } publicStringReplace(Strings) { Stringresult=newString(); char[]cs=s.toCharArray(); char[]target=this.preString.toCharArray(); char[]matchstr=this.matchString.toCharArray(); intindex=Find(target,matchstr); if(index==-1)returnthis.preString; booleanflg=true; for(inti=0;i<matchstr.length;i++) { if(i==cs.length)flg=false; if(flg)target[i+index]=cs[i]; elsetarget[i+index]=''; } for(inti=0;i<target.length;i++) result+=target[i]; returnresult; } @XMLParameters("/org/junitext/samples/Find_and_Replace.xml") …… @Test publicvoidnamesAreEqual(){ assertEquals("Theexpectednamedoesnotequaltheactualname.", expectedString,this.Replace(replaceString)); }}冒烟测试“冒烟测试”是微软首先提出来的概念,与微软一直提倡的每日构建(build)有很密切的联系。具体来说,冒烟测试就是在每日构建完成后,对系统的基本功能进行简单的测试。这种测试强调功能的覆盖率,而不对功能的正确性进行验证。至于冒烟测试这个名称的来历,大概是从电路板测试得来的。因为当电路板做好以后,首先会加电测试,如果板子没有冒烟再进行其它测试,否则就必须重新来过。类似的,如果软件冒烟测试没有通过,那么这个build也会返回给开发队伍进行修正,测试人员测试的版本必须首先通过冒烟测试的考验。冒烟测试是第一条防线,是第一组该被编写的测试,因为如果一个实现连冒烟测试都通不过,那更进一步的测试就是在浪费时间。编写测试脚本Find_and_Replace.xml如下:<?xmlversion="1.0"encoding="UTF-8"?><tests> <test> <stringid="preString"value="helloworld"/> <stringid="matchString"value="hello"/> <stringid="replaceString"value="great"/> <stringid="expectedString"value="greatworld"/> </test></tests>如前所述,代码需要在字符串“helloworld”中匹配“hello”,并将其替换成“great”,最终结果应该是“greatworld”。为了更直观的显示结果,在断言语句assertEquals之后增加控制台打印语句:System.out.println(this.Replace(replaceString))。令人高兴的是测试通过了。如下图所示,Junit报告测试失败例子为0,同时控制台也打印出了“greatworld”,说明代码正确地实现了了基本功能。图4进一步测试冒烟测试中,替换字符串与匹配字符串长度相同,都是5个字符。现在长度不相等的情况,比如将替换串改为“hi”,得到第二组测试数据:<test><stringid="preString"value="helloworld"/><stringid="matchString"value="hello"/><stringid="replaceString"value="hi"/><stringid="expectedString"value="hiworld"/></test>这组测试失败了,Junit提示:org.junit.ComparisonFailure:Theexpectednamedoesnotequaltheactualname.expected:<hi[]world>butwas:<hi[]world>仔细检查发现,Replac()函数假定了替换串replaceString最长为匹配串的长度即matchstr.length,如果matchstr.length大于replaceString.length,则用空格填充,即:for(inti=0;i<matchstr.length;i++) { if(i==cs.length)flg=false; if(flg)target[i+index]=cs[i]; elsetarget[i+index]=''; }这个bug是由原单片机代码设定匹配串matchstr始终为“TAG:LEN1”,共8个字符,且后续任何替换串replaceString长度都不会多于8个字符,在用Java重写代码时又没有注意到这一点所导致。很容易想见,不但matchstr.length大于replaceString.length时,被测代码不能正常工作,matchstr.length小于replaceString.length同样也会导致错误。改进办法是,不采用逐字符替换的方法,而是将目标串preString剔除掉匹配串matchString,形成两个子串s1和s2,然后采用字符串连接得到结果result=s1+replaceString+s2,代码如下: publicStringReplace(Strings) { Stringresult=newString(); char[]target=this.preString.toCharArray(); char[]matchstr=this.matchString.toCharArray(); intindex=Find(target,matchstr); if(index==-1)returnthis.preString; for(inti=0;i<index;i++) result+=target[i]; result+=s; for(inti=index+matchstr.length;i<target.length;i++) result+=target[i]; returnresult; }现在增加matchstr.length小于replaceString.length的测试用例: <test> <stringid="preString"value="helloworld"/> <stringid="matchString"value="hello"/> <stringid="replaceString"value="wonderful"/> <stringid="expectedString"value="wonderfulworld"/> </test>不出预料地通过了测试,如下图所示:图5前面的测试用例都是匹配串在目标串中匹配成功的情形,现在考虑如果失配会出现什么结果(预期结果是目标串保持不变),第4组测试用例如下,令人欣喜的是测试通过了:<test><stringid="preString"value="helloworld"/><stringid="matchString"value="hellohello"/><stringid="replaceString"value="wonderful"/><stringid="expectedString"value="helloworld"/></test>接下来考虑匹配串matchString为空的情形,理论上讲,若匹配串为空,那么应该视为在目标串中找不到任何匹配,否则空串可以在目标串任何位置匹配,则意味着在目标串任意位置均要插入替换串,这显然是不合理的,退一步说,只替换第一次匹配的话实际意义也不大(等价于直接在目标串开头增加替换串的内容),所以正确结果应该是保持目标串不变。采用第5组测试用例如下:<test><stringid="preString"value="helloworld"/><stringid="matchString"value=""/><stringid="replaceString"value="wonderful"/><stringid="expectedString"value="helloworld"/></test>我们遗憾地看到,测试失败了,Junit提示:org.junit.ComparisonFailure:Theexpectednamedoesnotequaltheactualname.expected:<[]helloworld>butwas:<[wonderful]helloworld>程序果然将目标串“helloworld”与空串“”的第一次匹配替换成了“wonderful”,得到“wonderfulhelloworld”,这不是我们希望的结果。可见应该对匹配串为空的情况特别处理,在调用Find()函数之前增加如下语句即可:if(matchString.length()==0)returnpreString;接下来考虑目标串为空以及目标串与匹配串均为空的情形:<test><stringid="preString"value=""/><stringid="matchString"value="hello"/><stringid="replaceString"value="wonderful"/><stringid="expectedString"value=""/></test><test><stringid="preString"value=""/><stringid="matchString"value=""/><stringid="replaceString"value="wonderful"/><stringid="expectedString"value=""/></test>经测试,程序均能正常工作。下面考虑目标串与匹配串相等的情况:<test><stringid="preString"value="helloworld"/><stringid="matchString"value="helloworld"/><stringid="replaceString"value="wonderful"/><stringid="expectedString"value="wonderful"/></test>也顺利通过测试。最后考虑替换串为空的情形:<test><stringid="preString"value="helloworld"/><stringid="matchString"value="helloworld"/><stringid="replaceString"value=""/><stringid="expectedString"value=""/></test>覆盖率测试EclEmma是一个基于EMMA的Java代码覆盖工具。它的目的是让你可以在Eclipse工作平台中使用强大的Java代码覆盖工具EMMA。EclEmma是非侵入式的不需要修改你的项目或执行其它任何安装,它能够在工作平台中启动像运行JUnit测试一样直接对代码覆盖进行分析。覆盖结果将立即被汇总并在Java源代码编辑器中高亮显示。你会发现有三种颜色,绿色,红色和黄色,它们分别表示该行:被测试到,未被测试到,以及部分被测试到。红色或黄色的部分是需要引起你注意的,bug也许就隐藏在这部分代码中,你所需做的就是设计一些测试用例,使它们运行以前未被执行到的语句。安装好EclEmma后,重启eclipse,对Find_and_Replace.java进行覆盖率测试,结果如下:图6结果显示覆盖率只有91.7%,另外用黄色和红色横条提示for(inti=0;i<index;i++)被部分执行,而result+=target[i]从未被执行!回顾前面的测试用例,出现匹配时,匹配串正好都是目标串的前缀。这就导致提示部分从未被执行,现在增加测试用例,该例子满足匹配位置出于目标串中间某个位置,而不是串的开头:<test><stringid="preString"value="thehelloworld"/><stringid="matchString"value="hello"/><stringid="replaceString"value="verybeautiful"/><stringid="expectedString"value="theverybeautifulworld"/></test>例子顺利跑通,然后测试覆盖率,结果如下:图7可以看到Find_and_Replace.java的覆盖率为100%!代码清单//Find_and_Replace.javapackageorg.junitext.samples;importstaticorg.junit.Assert.assertEquals;importorg.junit.Test;importorg.junit.runner.RunWith;importorg.junitext.XMLParameters;importorg.junitext.runners.XMLParameterizedRunner;@RunWith(XMLParameterizedRunner.class)publicclassFind_and_Replace{ privateStringexpectedString; privateStringmatchString; privateStringpreString; privateStringreplaceString; publicintFind(char[]target,char[]matchment) { inti,j,k; for(i=0;i<target.length;i++) { for(j=i,k=0;k<matchment.length&&matchment[k]==target[j];j++,k++); if(k==matchment.length) returni; } return-1; } publicStringReplace(Strings) { Stringresult=newString(); char[]target=this.preString.toCharArray(); char[]matchstr=this.matchString.toCharArray(); if(matchString.length()==0)returnpreString; intindex=Find(target,matchstr); if(index==-1)returnthis.preString; for(inti=0;i<index;i++) result+=target[i]; result+=s; for(inti=index+matchstr.length;i<target.length;i++) result+=target[i]; returnresult; } @XMLParameters("/org/junitext/samples/Find_and_Replace.xml") publicFind_and_Replace(StringpreString,StringmatchString,StringreplaceString,StringexpectedString){ this.expectedString=expectedString; this.matchString=matchString; this.preString=preString; this.replaceString=replaceString; } @Test publicvoidnamesAreEqual(){ assertEquals("Theexpectednamedoesnotequaltheactualname.", expectedString,this.Replace(replaceString)); System.out.println(this.Replace(replaceString)); }}//Find_and_Replace.xml<?xmlversion="1.0"encoding="UTF-8"?><tests><test><stringid="preString"value="helloworld"/><stringid="matchString"value="hello"/><stringid="replaceString"value="great"/><stringid="expectedString"value="greatworld"/></test><test><stringid="preString"value="helloworld"/><stringid="matchString"value="hello"/><stringid="replaceString"value="hi"/><stringid="expectedString"value="hiworld"/></test><test><stringid="preString"value="helloworld"/><stringid="matchString"value="hello"/><stringid="replaceString"value="wonderful"/><stringid="expectedString"value="wonderfulworld"/></test><test><stringid="preString"value="helloworld"/><stringid="matchString"value="hellohello"/><stringid="replace
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 贵阳职业技术学院《试验设计与数据处理》2023-2024学年第一学期期末试卷
- 2025上海市安全员-C证考试(专职安全员)题库附答案
- 2025江苏省安全员《B证》考试题库及答案
- 2025海南省建筑安全员C证考试(专职安全员)题库附答案
- 广州中医药大学《形体训练(Ⅱ)》2023-2024学年第一学期期末试卷
- 广州应用科技学院《环境艺术专题设计》2023-2024学年第一学期期末试卷
- 广州现代信息工程职业技术学院《运动解剖学》2023-2024学年第一学期期末试卷
- 广州铁路职业技术学院《针织物设计与试织》2023-2024学年第一学期期末试卷
- 2025四川建筑安全员B证(项目经理)考试题库
- 2025年福建建筑安全员《A证》考试题库及答案
- 2024年股东股权继承转让协议3篇
- 2025年中央歌剧院毕业生公开招聘11人历年高频重点提升(共500题)附带答案详解
- 北京市高校课件 开天辟地的大事变 中国近代史纲要 教学课件
- 监事会年度工作计划
- 2024年认证行业法律法规及认证基础知识
- SVG无功补偿培训
- 新生儿听力筛查技术规范卫生部2010年版
- 大猫英语分级阅读 六级1 Arthur's Fantastic Party课件
- SCA自动涂胶系统培训讲义
- LEC法取值标准对照表
- 华中数控车床编程及操作
评论
0/150
提交评论