• 1. 单元测试 ?Test
  • 2. Brief Agenda程序员为什么要做测试 测试的一些基本概念 测试先行的概念 单元测试的基本做法和常见工具
  • 3. 测试不是我的工作 你是这样的程序员么? 测试是测试部门的责任,我的责任应该关注在写代码上 测试不是一种技术工作,毫无乐趣可言,请不要骚扰我。我可是一个了不起的EJB程序员 我们有测试人员,有集成/系统/确认测试,他们迟早会发现我的错误。请不要浪费我的时间。 不要侮辱我,我写的程序,怎么可能有错误。测试是完全没必要的。离我远一点,我是程序员
  • 4. 你做了测试了吗? 大部分中小公司,和软件开发管理处于早期阶段的团队,没有专门的测试工作和测试流程,测试只是在产品提交给用户之前,组织若干人员对最终产品作一次基本功能的确认测试而已。更多时候,测试的实际工作是用户来完成的。
  • 5. 后果软件的质量完全取决于程序员的个人技能和责任心,具有很大的随机性 后期维护成本高昂 1个月的开发,几天的测试,然后花1,2年的时间去修补错误 这个项目我已经维护了3年了 根本原因是软件自身复杂的结构 虫虫和天上的星星一样多
  • 6. 软件的结构方法类功能模块系统用户需求系统结构集成单元错误可能会随机的分布在任何一个地方
  • 7. 测试模型软件自身的复杂程度决定了即使是最优秀的程序员也不可能不制造任何错误。 软件自身的复杂程度决定了不可能只通过对最终产品的1,2种类型的测试就可以排除系统中的大部分错误 测试必须并行的融入到软件开发生命周期的各个阶段,以覆盖软件结构和开发生命周期的不同关注点。 主要的模型有传统的瀑布模型,和v模型,以及改良的x模型
  • 8. V模型测试活动融入整个软件开发的生命周期 不同阶段的测试强调的是从不同视角关注不同的方面,尽可能早而全的出去错误,不累计错误。 每一种类型测试的效果,将严格依赖于前期阶段其他类型测试的执行的正确,完整与否。 测试有分工,合适的人在合适的时候承担合适的测试 改良的有x模型
  • 9. 时间和成本缺陷的发现时间越晚,修复的成本越高,在部署阶段每个缺陷的修复成本都会及其高昂(每一个major以上的缺陷修复都不得不作完整的系统测试和确认测试),严格实施scm的组织尤其昂贵。
  • 10. 什么是单元测试单元测试测试的软件最小的可执行单元的正确性,即类或方法 单元测试通常是一段可执行代码,并能验证执行结构是否和预期相等 每个单元测试至少应该有两个测试例子( Test Case ): Negative/Positive 单元测试可以是黑盒也可以是白盒,取决于执行方法盖房子,至少要保证每一块砖都是好的
  • 11. 什么是单元测试(Unit Test)单元测试是其他类型测试的基础。不认真,完整的单元测试会导致其他类型测试起不到好的效果 程序员最了解自己的程序单元,最适合做单元测试 传统的重量级的方法学里,UT test case由设计人员在系统设计阶段开发,并用来验证编码人员的工作质量盖房子,至少要保证每一块砖都是好的
  • 12. 单元测试是成本最低的测试活动发现一个Defect所需要的时间 91年数据
  • 13. 程序员的责任程序员的价值在于和他人合作,开发出高质量的代码,而不是一堆新技术名词堆砌的虫件(bugware)。 程序员必须对自己的代码质量负责,单元测试是对自己代码质量的基本承诺。 程序=UT+CODE 不做单元测试,就会影响团队其他人员的工作。测试人员有权利对没有做过UT的代码说No.不愿意做UT的人,不属于任何团队。程序=UT + CODE
  • 14. 实践证明(个人工作经历)代码质量最好的程序员是UT做的最好的程序员。 开发速度最快的程序员,是UT做的最好的程序员 80%以上的非需求性错误,都集中在没有做UT和不方便做UT的代码块。 UT的质量直接影响了IT/ST/UAT的质量。程序=UT + CODE
  • 15. 测试很重要,但是 我已经调试运行过代码了,他应该是正确的,为什么还要浪费额外的时间 程序已经能跑起来了,这期间我实际已经手工作了测试,只是没有记录和编写代码而已 我没有时间做测试,工期已经很紧张了,能写完代码就不错了,虽然我知道以后要返工。 我不知道应该怎么样去做测试
  • 16. 调试不等于UT调试只会关注于程序的某个方面,通常是最优路径。UT至少要关注正/负2个方面,还需要保证一定的覆盖率 调试,尤其是J2EE应用的调试要耗费大量的时间,没有UT的效率高(后面再细讲) 调试不可重现,在代码变跟以后,软件质量无法保证
  • 17. UT一定要做到自动化只有用代码编写的UT,才能够重现,才能真正节约未来手工测试的时间。 只有用代码编写的UT ,才能做到自动化,才能在软件开发的任何时候都能快速,简单的大批量执行,保证能准确地定位错误,保证不会因为修改而引入新的错误。在系统开发的后期尤为明显。 自动化的UT,才能保证回归测试的有效执行。
  • 18. UT节约的是你未来的时间编写UT代码的时间节约了未来修改/维护低质量代码的时间 学习做UT的时间,是为了以后你可以更好的关注你的代码 如果使用Test-driven的思想 自身就变成设计的一部分,你不会再感到是在浪费时间,编写UT的过程,就是设计的过程 UT快速的定位错误所在,节约了你调试的时间。
  • 19. 需求设计评审编码reworkUT如何进行UT ---传统的做法,在it之前进行UT严格的瀑布模型,UT作为生命周期的一个阶段 UT 的Test Case一般做为设计的一部分。 UT的执行通常在代码review以后 Test case单独编写和执行 成本高昂,尤其是严格执行SCM的情况。 UT本身是作为一种最终的质量检测手段,只能节约未来的时间. 完整全面的UT 需要大量的时间,精力,需要和PR结合 程序员通常感觉不到UT带来的眼前好处,得不到任何乐趣,抵制执行。
  • 20. 如何进行UT ---传统的做法 *场景程序员先开始设计数据表。然后开始依次编写数据访问对象,strust action,jsp页面。他检查了一遍代码,确定每件事看起来都是不错的,于是他开始把所有的东西集成在一块。当然,通常是跑不起来的,经过若干个小时辛苦的调试,中间不断的修改代码,重起app server。终于在数据访问对象中发现了一个错误。 若干时间以后,在代码被review以后,程序员开始做枯燥无味的单元测试工作,这个时候,他最想做的事情其实是睡觉。迫于时间和成本的压力,pm/程序员往往会简化或者不执行UT的过程
  • 21. 需求设计运行ut,定位错误编码rework编写Test case 如何进行UT ---改良, 写一点,测一点程序员每编写完一个程序单元,就编写UT代码 首先通过运行UT代码来定位程序的错误,而不是直接调试代码,节约了大量调试的时间。 如果能把程序划分为尽可能小的单元,通过执行ut代码,就能快速的定位错误的所在,调试就可以避免 可有效的保证以前编写的代码的正确性,不需要拖到增量结束以后再执行。 错误能尽早的发现,不需要等到Code review以后再修复,成本很低Code review
  • 22. 如何进行UT ---改良, 写一点,测一点程序员开始设计数据库表,然后开始编写数据访问对象,接着他对数据访问对象编写了UT test case,运行TC,发现了若干错误,然后迅速的改正了他。所有的TC都通过以后,他开始编写ACTION,同样,他对ACTION做了些测试。最后他编写了JSP,并把所有的东西集成在一起,当然因为JSP的不好测试性,他花了点时间在修订JSP页面的几个TAG错误上。 若干时间以后,在做IT之前,他去问QA部门的人,你看,我所有的代码都写了UT TC,这些TC能不能直接作UT的REPORT呢?在得到确定的回到以后,他很高兴得回去做别的事了。UT作为一种帮助提高开发效率的有效手段融入到开发过程中,程序员也不需要再去做枯燥无聊的事后UT了
  • 23. 如何进行UT ---改良, 写一点,测一点 .实践在一段时间以后,我发现如果自己不写UT,我的开发速度就开始大打折扣 我的一位同事在1个月以后,很乐于向我展示作UT的好处。每次他做任何修改,他都可以通过执行所有UT的SUITE来确保没有引入任何新的错误,节约了大量的时间,看到屏幕上的一排绿灯,那种心情非常好。 “整个团队只有我能按时下班,终于CTO问我为什么,我告诉他,我总是在做UT,所以我很少化时间去做调试。”编程的80%时间都是用在调试,节约了调试的时间,也就提高了开发的速度每次看到绿灯的时候,心情就很好
  • 24. 如何进行UT ---改良, 写一点,测一点 缺点经常性的,当你写完一个方法,你或许过于自信,而不愿意再去为他写一个test case.若干时间以后,你发现他的问题终于出来了 有时候,当辛苦编写完代码,开始编写test case的时候,你会发现因为设计上错误,这段代码是不可以测试的。这就为未来留下了隐患
  • 25. 如何进行UT– 测试驱动的做法失败通过时间单元测试 100% 通过设计先写 单元测试重构运行 单元测试编程发现BUG
  • 26. 如何进行UT– 测试驱动的做法先写Test case,可以保证我们每一个public/protected的方法都确实有Test Case 编写 test case的过程也就是一个设计的过程 你需要考虑实现代码的可测试行 你需要考虑代码和测试代码的关系,仔细考虑实现代码的边界和逻辑,测试代码应该如何保证实现代码的正确性当测试代码完成的时候,就会对实现代码有清晰地了解 你仔细思考要做什么和不要做什么,可以避免过度设计 此时的UT实际是白盒测试,能起到更好的效果。可以把关注点放在某些重要的地方。
  • 27. Example 通过测试代码来思考设计 //空置必须抛出异常 Try { boy.add(null); fail(“don’t throw exception ”); } catch (xxx) { } // add // boy.add(new Boy()); String id = boy.add(new Boy()); //需要检测是否增加成功,那么add就应该返回id值,修改上面的代码 assertEqual(id, boy.find(id).getID()) If (value = null) { throw new IllegalArguementException(‘xxx’); } String id = DAOSupport.addObject(boy); Return id; Test caseadd
  • 28. 如何进行UT– 测试驱动的做法Test-Driven Design是一种开发风格,它要求程序员做到: 在写产品代码之前,先写它的单元测试( Unit Tests ) 没有单元测试的Class不允许作为产品代码 单元测试例子决定了如何写产品代码 不断地成功运行所有的单元测试例子 不断的完善单元测试例子 Test-Driven Design是把需求分析,设计,质量控制量化的过程!也是一个对软件持续改进的过程
  • 29. 如何进行UT– 测试驱动的做法 测试代码/实现代码的交替编写,能让大脑始终处在一种松驰/紧张,工作效率会比较高。 编写测试代码也是设计的过程,总体的时间花费就下降了,也避免了因为对不可测试代码的修改而导致的时间成本文物之道,一张一弛
  • 30. 常用单元测试框架JUnit Struts test case, junit的扩展,用于测试struts的action HttpUnit 测试http页面 JFC Unit. Cactus 专门用来测试服务器类 JsUnit 测试javascript
  • 31. JUnit一个开源的单元测试框架,也是其他java单元测试框架的基础
  • 32. JUnit Test casepublic class TestPerson extends TestCase { /** A unit test to verify the name is formatted correctly. */ public void testGetFullName( ) { Person p = new Person("Aidan", "Burke"); assertEquals("Aidan Burke", p.getFullName( )); } /** A unit test to verify that nulls are handled properly. */ public void testNullsInName( ) { Person p = new Person(null, "Burke"); assertEquals("? Burke", p.getFullName( )); // this code is only executed if the previous assertEquals p = new Person("Tanner", null); assertEquals("Tanner ?", p.getFullName( )); } } Test case 扩展自TestCase,每个方法负责对某种情况测试,测试结果为true/false
  • 33. JUnit assertXXX( ) 使用一系列的assertXXX方法来判断执行结果是否和预期相符,不符则执行失败,不会再继续case(当前方法)的余下部分Assert Method summaryMethodDescriptionassertEquals( )进行等值比较assertFalse( )进行boolean值比较assertNotNull( )比较对象是否不为空assertNotSame( )对2个对象应用的内存地址进行比较.assertNull( )比较对象是否为空assertSame( )对2个对象应用的内存地址进行比较.assertTrue( )进行boolean值比较fail( )引发当前测试失败,通常用于异常处理
  • 34. Junit Test Case执行结果将显示一次运行通过/不通过的TC数目,通常很快。可以把多个TC集合成一个suite一起测试.和ANT这类的工具配合还可以自动生成测试报告
  • 35. JUnit Set Up and Tear Down setUp() 提供初始化方法 tearDown提供清除方法 如果几个测试case(方法)共享同样的初始化合清除方法,如dao对象的创建,可以使用setUp/tearDown方法 如果需要在几个test方法中共享某一个变量,请使用static的类变量,因为junit为每个方法都重新创建一个对象。setUp()TestXXX()tearDown
  • 36. JUnit One-Time Set Up and Tear Down public class TestPerson extends TestCase { public void testGetFullName( ) { ... } public void testNullsInName( ) { ... } public static Test suite( ) { TestSetup setup = new TestSetup(new TestSuite(TestPerson.class)) { protected void setUp( ) throwsException { // do your one-time setup here! } protected void tearDown( ) throws Exception { // do your one-time tear down here! } }; return setup; } } 某些情况下,我们希望一组test case的setup/tear down仅仅运行一次,通常是为了提高速度,可以使用此种做法 在这种情况,如果为TestPerson自己实现了setup/tearDown方法,他们还是会被执行的。
  • 37. JUnit Repeating Tests public static Test suite( ) { // run the entire test //suite ten times return new RepeatedTest(new TestSuite(TestGame.class), 10); } 如果想重复的运行某个测试若干次,可用此法,可用此法来制造模拟数据或性能测试
  • 38. JUnit Organizing Tests into Test Suites public class TestGame extends TestCase { ... public static Test suite( ) { return new TestSuite(TestGame.class); } } public static Test suite( ) { TestSuite suite = new TestSuite( ); suite.addTest(new TestGame("testCreateFighter")); suite.addTest(new TestGame("testSameFighters")); return suite; } Test suite的目的是把所有的test case放在一起执行。为了验证程序的其他部分,或者作night test,我们通常需要构造test suites 第一种方法将自动执行testGame所有的testXXX方法 第二种方法只执行指定的方法
  • 39. JUnit Exception Handling public void testPassNullsToConstructor( ) { try { Person p = new Person(null, null); fail(“xxx"); } catch (IllegalArgumentException expected) { // it’s ok } } // don't do this! public void testBadStyle( ) { try { SomeClass c = new SomeClass( ); c.doSomething( ); } catch (IOException ioe) { fail("Caught an IOException"); }如果期盼代码抛出某种异常可以使用第一种做法 对不期盼处理的异常可以直接抛出,不要做try catch处理,junit会自动转为fail
  • 40. JUnit Running Tests Concurrently public static Test suite( ) { TestSuite suite = new ActiveTestSuite( ); suite.addTest(new TestGame("testCreateFighter")); suite.addTest(new TestGame("testGameInitialState")); suite.addTest(new TestGame("testSameFighters")); return suite; } 要求同时跑多个线程,可以使用ActiveTestSuite来启动,此法可对多线程应用进行测试
  • 41. JUnit 测试的命名规范/** * @TGID 001 //测试组id * 这个类测试XXX */ Class XxxTest //测试类以Test届尾 /** @TCID 001 //测试case id 此方法测试 xxxxx */ public void testXXX() { //测试方法以test开头 }遵守规范的命名可以方便作night test和最后用javadoc/java2hmtl提出文档,作UT report
  • 42. JUnit// add user String id = Dao.add(boy); // confirm the result of add op boy = dao.findByID(id); assertNotNull(boy); // add the existed user. Negative test Dao.add(boy); 测试CASE的考虑应该全面,至少要检测方法的执行效果,也要从正反2面进行考虑. 否则就起不到效果。
  • 43. JUnit integerated with eclipse将Junit集成到eclipse要做以下工作(确省已经安转) 下载相应的插件文件org.junit,org,eclipse.jdt.junit 将该文件解压到eclipse_install_dir\plugins 重新启动eclipse 将junit.jar加入项目的classpath,也就是build lib里
  • 44. JUnit integerated with eclipse新建junit的test case 选择run as junit test运行 也可选择debug,可以对细节进行跟踪调试
  • 45. JUnit struts test caseJunit的扩展,提供对struts框架action的测试 主要有2种方式 使用mock object 使用cactus框架
  • 46. JUnit struts test case with Mock objectpublic class TestLoginAction extends MockStrutsTestCase { public void testSuccessfulLogin() { setRequestPathInfo("/login"); addRequestParameter("username","deryl"); addRequestParameter("password","radar"); actionPerform(); //执行struts的action verifyForward("success"); assertEquals("deryl",(String) getSession().getAttribute("authentication")); verifyNoActionErrors(); } } Test case 从MockStrustTestCase扩展
  • 47. JUnit struts test case with Mock object提供一部分的模拟方法如addRequestParameter来进行模拟web调用 增加了若干成功判断方法。 如verifyForward(), verifyNoActionErrors()针对struts的forward,action error处理等 使用mock 对象,可以不使用web环境,配置简单 应该将逻辑从action移走,避免针对action的大量逻辑测试
  • 48. Cactus(http://jakarta.apache.org/cactus )主要提供对server-side的java code的测试,也提供对EJB/JSP tag 的单元测试
  • 49. Cactus设置Client端classpath需要包括junit.jar, cacturs.jar,httpclient.jar,aspectjrt.jar,common-logging.jar,可选配置httpunit.jar,tidy.jar,xercex.jar 客户端需要有cactus.properties 描述基本配置,也要在classpath中能找到 cactus.contextURL=http://localhost:8080/xptest cactus.servletRedirectorName=ServletRedirector cactus.jspRedirectorName=JspRedirector cactus.filterRedirectorName=FilterRedirector 服务器端需要包括测试用列和上述类
  • 50. Cactus web.xml的配置 ServletRedirector org.apache.cactus.server.ServletTestRedirector ServletRedirector /ServletRedirector 可以针对servlet/jsp/filter三种情况进行配置,cactus.properies里面的项目要和此对应
  • 51. Example public class SimpleJspTest extends ServletTestCase { public void beginValidFormParameters(WebRequest webRequest) { webRequest.addParameter("username", "coyner_b", WebRequest.POST_METHOD); webRequest.addParameter("password", "secret", WebRequest.POST_METHOD); } public void testValidFormParameters( ) { assertTrue("Valid Parameters.", this.servlet.validateParameters(this.request)); } } } beginXXX, endXXX方法仅在在客户端执行
  • 52. 单元测试的质量通常使用测试通过率和测试覆盖率2项指标 测试通过率必须是100%,表明所有test都通过 测试覆盖率有语句/路径/条件/分支等等,一般要求语句100%覆盖,路径60%左右覆盖。可以估计你大致的测试力度。 可以用jtest/nounit这样的工具来获得这些指标
  • 53. 提示尽可能的不要使用cactus,配置有一定难度,代码编写也有限制 尽可能的把逻辑代码从jsp/servlet中移出,使用junit做单元测试
  • 54. Mock对象使用mock对象来提供更好的单元测试支持 有些对象不容易测试,如jsp/servlet,可以使用mock对象来模拟他们的行为 有些需要调用到的对象需要消耗一定的资源,比较费时间,而test本身的关注点又不在这个方面,如测试用户是否登陆,用户数据访问就不是很重要。 对其他层面应用的接口
  • 55. Examplepublic class MockCustomerDAO implements CustomerDAO {     // the only account list in memory     protected static List list = new ArrayList();     public Customer findByUsername(String username)  {         for (int index = 0; index < list.size(); index++) {              Customer   customer  = (Customer) list.get(index);             if (customer.getUsername().equals( username )){                 return  customer;             }         }         return null;     } }
  • 56. 其他程序员要考虑的测试功能测试 针对某个功能集合的测试,大部分情况也可以用junit做到自动化测试。对web应用,可以使用http unit来做。功能测试,确认测试更好的做法是自动化,测试数据和测试代码分离 回归测试 简单的说,就是把所有以前的测试都再作一遍,在修订defect和加入新功能时需要执行,以确保不引入新的defect.回归测试的执行效果依赖于能否做到完整的自动化的UT/FT/ACT 夜测试 来自持续集成的概念,项目可能会试行 确认测试 可以使用WEBART, win runner等工具来做自动化的确认测试
  • 57. 单元测试和功能测试如果一个单元测试的约束和依赖过多,那么它可能就是一个功能测试 单元测试一般是用什么语言写的代码,就用什么语言去测。功能测试则不一定 程序员写代码去做功能测试也是一个好的工作习惯。
  • 58. 持续测试只有在开发过程中不断的进行持续测试才能及早发现问题 持续测试的基础是完整的自动化的单元测试和其他类型的自动化测试 一旦程序员养成良好的工作习惯,持续测试就成为软件开发过程中和编码一样自然的东西了。
  • 59. ReferenceJava™ Extreme Programming Cookbook Test-driven development by example Thanks