资源描述
单元测试单元测试第五讲第五讲主要内容1.单元测试介绍 2.单元测试 Quick Start3.使用JUNIT4.测试的一些技巧5.边界条件6.MOCK对象简介7.单元测试与软件设计1.单元测试介绍1.1什么是单元测试单元测试是开发者写的一小段代码,用于检验被测代码的一个很小的、明确的功能是否正确。通常而言,一个单元测试是用于判断某个特定条件下某个特定的函数的行为。执行单元测试,是为了证明某段代码的行为确定和开发者所期望的一致。1.2单元测试的目的及早发现软件开发过程中实现或者设计带来的缺陷跟踪详细设计文档中设计的实现,发现详细设计文档中存在的错误验证单元代码和详细设计文档的一致性发现在编码过程中引入的错误单元测试标准是什么单元测试标准通常为详细设计说明书但是在没有详细设计说明书的情况下可以以注释为测试标准单元测试过程单元测试计划单元测试设计单元测试实现单元测试执行单元测试评估单元测试计划时间表工作量任务分配资源安排测试工具结束标准风险分析风险应对输出单元测试计划文档单元测试设计对哪些单元进行测试被测单元的关系被测单元与其他模块的关系测试策略选择如何设计测试用例如何设计单元测试代码输出单元测试用例文档单元测试实现编写测试用例编写测试规程测试脚本编写测试驱动构建桩构建输出测试用例输出测试规程输出测试代码和脚本单元测试执行搭建测试环境执行测试脚本记录测试结果跟踪缺陷回归测试输出单元测试报告单元测试策略自顶向下的单元测试方法 先对最顶层的单元进行测试,把顶层单元所调用的单元做成桩模块。其次对第二层单元进行测试,使用上面已测试的单元做驱动模块。依次类推直至测试完所有的模块。优点 可以节省驱动函数开发的工作量,测试效率较高缺点 随着被测单元一个个的加入,测试过程将变得复杂,并且开发和维护的成本将增加。自底向上的单元测试孤立的单元测试混合的单元测试自底向上的单元测试先对最底层的单元进行测试,模拟主调单元构建驱动模块。然后再对上面一层做单元测试,用下面已经测试通过的模块做桩模块。依次类推,直至测试完所有的模块。优点 可以节省桩模块开发的工作量,测试效率高缺点 不是纯粹的单元测试,底层函数的测试质量对上层函数的测试将产生很大的影响。孤立的单元测试方法 不考虑模块和模块之间的关系,为每个模块设立桩模块和驱动模块。每个模块进行独立的单元测试优点 该策略最简单,最容易操作。可以达到很高的覆盖率。似乎纯粹的单元测试缺点 该策略效率很低,需要构建大量的驱动和桩混合的单元测试自顶向下和自底向上的测试策略综合了集成的概念,随着单元测试的进行,可以看到系统一个初步集成的概貌,但是测试覆盖率会越来越难保证。并且在每个单元测试之前必须保证相关的单元的正确性。孤立的测试策略比较独立,覆盖率容易保证,并且可以并行进行,但工作量大。所以采用混合方法比较好。单元测试用例设计单元测试用例设计方法黑盒白盒单元测试用例编写思路为系统运行起来设计测试用例为正向测试设计用例为逆向测试设计用例为满足特殊需求而设计用例为代码覆盖而设计测试用例单元测试用例设计五要点单元接口局部数据结构独立路径出错处理边界条件理解需求和设计设计测试用例搭建单元测试环境执行测试补充和完善测试用例分析结果,给出评价1.3 我需要做什么呢它的行为和我的期望一致吗?它的行为一直和我的期望一致吗?我可以依赖单元测试吗?单元测试说明我的意图了吗?1.4不写单元测试的借口编写单元测试太花时间了运行测试的时间太长了测试代码并不是我的工作我并不清楚代码的行为,所以也就无从测试但是这些代码都能编译通过公司请我是为了写代码,而不是写单元测试如果我让测试员失去工作,我会觉得内疚公司不会让我在真实系统中运行单元测试2.单元测试 Quick Start2.1测试对象寻找数组中的最大数:public class Largest public static int largest(int list)int index,max=Integer.MAX_VALUE;for(index=0;index max)max=listindex;return max;2.2设计测试排序测试:【9,7,8】9【7,9,8】9【7,8,9】9重复值:【7,9,8,9】9单值:【1】1负值:【-9,-8,-7】-7.空值2.3执行测试执行测试并修正错误理解单元测试3.使用JUNITJunit简介Junit的定位Junit的安装Junit的体系结构Junit的使用3.1构建单元测试 TestAccount.java Account.java准备要测试的条件调用要测试的方法验证被测试方法的行为是否和结果一致完成后清理各种资源testCreateAccount()testCreateAccountDef()testCreateAccountDup()createAccount()3.2 JUNIT的各种断言lassertEquals(String message,expected,actual)lassertEquals(String message,expected,actual,tolerance)lassertNull(String message,Object object)lassertNotNull(String message,Object object)lassertSame(String message,expected,actual)lassertNotSame(String message,expected,actual)lassertTrue(String message,boolean condition)lassertFalse(String message,boolean condition)lfail(String message)l自定义断言3.3 JUNIT框架import junit.framework.*;/引入测试包public class TestSimple extends TestCase/继承 TestCase public TestSimple(String name)/默认使用父类的实例化方法 super(name);public void testAdd()/书写以test开头的断言,凡是以test开头的都会 /被junit自动运行 assertEquals(2,1+1);public void testAdds()/一个测试方法里也可以有多个断言 assertEquals(2,1+1);assertEquals(4,2+2);assertEquals(-8,-12+4);测试类也能调用其他测试类:单独的类、包、甚至完整的一个系统。这可以通过创建test suite来取得。任何测试类都能包含一个名为suite的静态方法:Public static Test suite();现假设有第2个类TestClassTwo,它使用brute-force算法来寻找旅行销售商Bob的最短行程。但这个算法是复杂度是指数级的。默认情况下你不想包括这些测试。3.4 JUNIT测试的组成(1)import junit.framework.*;public class TestClassTwo extends TestCase public TestClassTwo(String method)super(method);public void testLongRunner()public void testShortTest()public void testAnotherShortTest()public static Test suite()TestSuite suite=new TestSuite();suite.addTest(new TestClassTwo(“testShortTest”);/装入测试方法 suite.addTest(new TestClassTwo(testAnotherShortTest);return suite;3.4 JUNIT测试的组成(2)import junit.framework.*;public class TestClassComposite extends TestCase public TestClassComposite(String method)super(method);static public Test suite()TestSuite suite=new TestSuite();/执行第一个测试类所有的测试 suite.addTestSuite(TestClassOne.class);/执行第二个测试类中指定的测试 suite.addTest(TestClassTwo.suite();return suite;假设对于每个测试,你都需要某种数据库连接,这时,你不需要在每个测试方法中重复建立连接和释放连接了,而只须在setup和teardown方法中分别建立和释放连接。执行每个测试方法之前会执行setup,之后会执行teardownJUnit和异常对测试而言,下面两种异常我们可能会感兴趣:从测试代码抛出的可预测异常。由于某个某块(或代码)发生严重错误,而抛出的不可预测异常。如有一个名为sortMyList()的方法,如果传入参数是一个nulllist,那么我们希望该方法抛出一个异常。在这种情况下,我们就需要显式地测试这一点。assertTure(true)表示“我预期控制流程会达到这个地方。Junit可以捕获任何异常,并且把它报告为一个错误,这些都不需要你的参与。更好的是,Junit不只是让一个断言失败,而是能够跟踪整个堆栈,并且报告bug的堆栈调用顺序,当你需要查找一个失败测试的原因时,这将非常有用。4.测试哪些内容测试哪些内容4.1测试内容(Right-BICEP)Right-结果是否正确?B-是否所有的边界条件都是正确的?I-能查一下反向关联吗?C-能使用其它手段交叉检查一下结果吗?E-你是否可以强制错误条件发生?P-是否满足性能要求?完全伪造或者不一致的输入数据,例如一个名为“!*w:gjagja;,/.d;”的文件。格式错误的数据,例如没有顶层域名的电子邮件地址,如fredfoobar空值或不完整的值一些与意料中的合理值相去甚远的数值。如一个岁数为10000岁。如果要求的是一个不允许出现重复数值的list,但是传入的是一个存在重复数值的list如果要求的是一个有序list,但是传入的是一个无序list;或者反之事情到达的次序是错误的,或者碰巧和期望的次序不一致,如未登陆系统之前,就尝试打印文档。可能的边界值反向关联如对结果进行平方的方式来检查一个计算平方根的函数,然后测试结果是否和原数据很接近。类似地,为了检查某条记录是否成功地插入了数据库,你也可以通过查询这条记录来验证。其它手段交叉检查通常计算一个量会有一种以上的算法。我们可能会基于运行效率或者其他的特性,来选择算法,那是我们要在产品中使用的,但在测试时可以使用剩下的算法来做交叉测试。其它手段交叉检查另一种办法,使用类本身不同组成部分的数据,并且确信它们能“合起来”。如,正在做一个图书馆的数据系统。在这个系统中,对每一本具体的书,它的数量永远是平衡的。我们可以用一种数量检查另一种数量。强制产生错误条件真实世界中,错误总是会发生:磁盘会满,网络连线会断开,电子邮件会多得像掉进了黑洞,而程序会崩溃。你应当能够通过强制引发错误,来测试你的代码是如何处理所有这些真实世界中的问题的。强制产生错误条件性能问题不是性能本身如:“随着输入尺寸慢慢变大,问题慢慢变复杂”的趋势。确保性能曲线能够保持稳定5.CORRECT边界条件5.1 边界条件o思考public int calculate(int a,int b)return a/(a+b);5.2 边界条件o一致性(Conformance)o值是否和预期的一致o有序性(Ordering)o值是否如应该的那样,是有序或无序o区间性(Range)o值是否位于合理的最小值和最大值之内。o引用,耦合性(Reference)o代码是否引用了一些不在代码本身控制范围之内的外部资源5.2 边界条件o存在性(Existence):值是否存在o基数性(Cardinality)o是否恰好有足够的值o时间性(Time)区间性如果数据不能像你期望的那样与结构相一致,将会出现什么情况?象这种情况,你就需要测试你的结果并确保其一致性。(一致性)任何一个搜索程序都应该针对搜索目标位于最前或最后的条件做测试。(有序性)如果这种情况可能发生,并且在发生的时候,你的程序需要处理她,那么你就需要测试这种情况。耦合性如,网站上一个用来显示用户记录的方法,可能要求用户先登录。栈的pop()方法要求一个非空的栈。给汽车换挡。前条件、后条件存在性通过询问:“给定的事物存在吗?”对于你传入或者维护的值,先询问如果值不存在如果它为null、或者等于0,方法的行为将会怎样?面对这些不存在的数据,java库的许多方法会抛出异常。然而要调试一个隐藏在库深处的运行期异常通常并不容易。在期望值不存在的时候,大多数方法都会失败,但这可能并不是你所期望的结果。因此,你可以对这些情况进行测试,查看当网络不通、文件不存在的时候,又会发生什么事情。确认你的方法处理了“不存在”的情况。一年中的每一天都是24小时吗?并发问题练习(思考栈的测试方法)public interface StackExercise public String pop()throws StackEmptyException;public void push(String item);public String top()throws StackEmptyException;public boolean isEmpty();答案(思考栈的测试方法)o对于空栈 isEmpty=true,pop(),top(),抛异常。opush(“字符”),top(),返回刚压入的字符串,isEmpty()=false。o多次调用push();验证每一次top()。opush(null),top()=null。o发生异常后,栈仍可以使用。6.MOCK对象简介对象简介6.1 MOCK对象 概念:MOCK对象是真实对象在调试期间的替代品。简单的替换一般,我们建议对应用程序范围外的功能调用进行包装。因为被测试代码只会通过接口来引用对象,所以它完全不知道它引用的究竟是真实对象还是mock 对象。在产品环境(卖给客户的真正的代码)中,当初始化这个类的对象时,传入的是一个真实的systemEnvironment,而另一方面,测试代码传入的是则是mockSystemEnvironment。6.2 什么情况下使用MOCK对象o真实对象行为不确定(如股票行情)。o真实对象很难被创建。o真实对象的某些行为很难被触发。o真实对象令程序的运行很慢。o真实对象有(或者是)用户界面。o测试含有回调函数。o真实对象并不存在。6.3 使用MOCK对象的步骤o使用一个接口来描述这个对象。o为产品代码实现这个接口。o以测试为目的,在MOCK对象中实现这个接口。练习:实现MP3的MOCK对象import java.util.ArrayList;public interface Mp3Player public void play();public void pause();public void stop();public double currentPosition();public String currentSong();public void next();public void prev();public boolean isPlaying();public void loadSongs(ArrayList names);7.单元测试与软件设计单元测试与软件设计7.1 软件设计关注的几个层面o通过面向测试的软件设计,更好地分离关注点。o通过测试驱动的方法,改善接口设计。o确立和局部化验证的责任。7.2 面向测试的软件设计例:public void sleepUtilNextHour throws InterrupedExceptionint howlong;计算休眠时间的一系列代码Thread.sleep(howlong);Return;等一个小时?设一个计时器,调用这个函数,等结束后回来再检查时间,如果失败了,再来处理一下,重新测试?7.3 面向测试的软件设计重构代码public void sleepUtilNextHour throws InterrupedExceptionint howlong=milliSecondsTONextHour(new Date();Thread.sleep(howlong);Return;测试:asserEquals(10000,milliSecondsTONextHour(TEST_DAY_10));如果测试代码非常丑陋或难写,暗示软件设计需要调整。7.5 测试驱动开发(一)例如:一个把标准纸张规格输出的方法addCorpMarks(PPstream str,int paper_width,int paper_height,int body_width,int body_height);测试代码Public Process()addCorpMarks(str,8.5,11,6,8.5);.addCorpMarks(str,8.5,11,6,8.5);addCorpMarks(str,8.5,11,6,8.5);7.5测试驱动开发(二)把标准纸张尺寸定义抽出成为一个类后,测试代码变为:Public Process()PaperSpec p1=new PaperSpec(8.5,11,6,8.5);PaperSpec p2=new PaperSpec(5,7,4,5);addCorpMarks(str,p1);addCorpMarks(str,p2);7.5 测试驱动开发(三)因为纸张是标准的,所以再把张纸的取得抽象为一个工厂类:Public class StandardPaperFactory()public static PaperSpec getA4Instance();public static PaperSpec getB5Instance();测试代码变为:StandardPaperFactory f=new StandardPaperFactory();addCorpMarks(str,f.getA4Instance(););addCorpMarks(str,f.getB5Instance();7.6小结 通过追求让测试代码的编写变得简洁和容易,你同时也令软件产品的代码的的编写变得更加简洁和容易。人有了知识,就会具备各种分析能力,明辨是非的能力。所以我们要勤恳读书,广泛阅读,古人说“书中自有黄金屋。”通过阅读科技书籍,我们能丰富知识,培养逻辑思维能力;通过阅读文学作品,我们能提高文学鉴赏水平,培养文学情趣;通过阅读报刊,我们能增长见识,扩大自己的知识面。有许多书籍还能培养我们的道德情操,给我们巨大的精神力量,鼓舞我们前进。
展开阅读全文