`
tinyyea
  • 浏览: 1119 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
最近访客 更多访客>>
社区版块
存档分类
最新评论

对于TDD的一点看法,总结一下自己的观点

阅读更多

 

bloodrate 写道
你说TDD是从测试开始思考,然后由测试思路指引开发,那举个例子吧,比如做个最简单的用户注册登陆系统,那么我关心三个问题: 1、新用户确实可以通过页面将数据正确插入到数据库中 2、用户可以通过ID,将数据从数据库中正确取出来 3、假设取出来的数据正确的前提下,比较这个用户的登陆条件是否满足,满足则允许登陆,否则则不允许 我在做这个用户注册登录功能的时候,只作了上面三点假设,如果假设成立,则系统正常运行,此时我还没有提及任何架构设计思路的事情,因为一切从测试角度开始,我针对这3点各写一个测试用例,然后用例出来了,类的层次结构就出来了,比如按照上面设计的3个用例,肯定是应该用用户ID将记录取出来然后再比较用户密码是否和输入的一样,而肯定不会是通过ID和密码联合将结果查出来而用count是否为0判断,也就是说测试先于一切




借用此例,阐述一下我会如何实施TDD,
好的,现在我打算实现一个用户注册登录系统,假设我现在项目中有0个类,目前我考虑当前的需求里有两个用例,一个注册一个登录,我想它们分别对应两个Service或者一个Service的两个方法,后者看起来不是那么复杂,所以我只想写一个Service类,即便我觉得将来有什么不妥,我可以重构它。
于是我想了一个类名LoginService和两个方法名register和login,首先为此编写测试,这是这个项目中的第一个类,先来写register的测试吧,根据需求用户注册需要输入自己的信息,用户会输入很多信息,于是register方法接受用户输入的所有信息做为参数,我要基于需求构思了一些测试用例,比如Tom和Andy,Tom填写了完全正确的用户信息,而Andy则因为没有提供Email而导致注册失败,把所有Tom的信息做为参数传入register,它会返回true,而对于Andy而言,会得一个false。

Void testRegisterTom() {
....
assertTrue(loginService.register( Tom's information));
}
void testRegisterAndy() {
...
assertFalse(logingService.register( Andy's information));
}


现在开始着手设计实现,不过我觉得这里有些问题,把用户所有信息当做参数实在不太OO,于是我重构了一下,我现在就写一个UserViewObject,重构伴随于TDD整个过程之中,并且我还想到我最好是针对接口编程,于是LoginService变成了一个接口,并且我告诉我的同事,如果你需要调用它请使用这个接口,一个类对应一个接口好像没有什么意义但它成了我与我同事之间的约定,现在我终于要实现它了。
我在实现LoginService的register接口过程中,我发现我要做一些数据库的操作,我需要一个插入操作,把用户信息插入到数据库,因为我对于DAO模式有一些经验,于是我思考把这个操作包装到DAO中,但是现在没有这个DAO,没关系,我先定义一个这样的接口UserDAO,有一个createUser(UserViewObject)接口方法,这里又是一个接口并且将来它极有可能只有一种实现,它出现的意义就是我对于代码将来的规划,我暂时不急着实现它,这并不会影响我写LoginService的逻辑,好,现在重新回到LoginService的实现,现在做一下UserViewObject里所有字段的必要性检查,然后调用userDao.createUser()插入,这里有一个假设,假设userDao.createUser()工作得很好,现在看来我之前写的测试可能要改点东西,我需要mock一个UserDAO的实例再做一些其它假设的工作,然后运行测试,最终把两个测试全部通过。
我的另一个同事告诉我,如果有另一个叫Tom的人也来注册,你怎么办?于是我又加了一个测试,对这“又一个Tom”进行测试,它应该返回false,

void testAnotherTom() {
....
assertFalse(logingService.register( AnotherTomObject));..
}


这个测试没法通过,因为LoginService里没判断这是不是another Tom。现在我在UserDAO中加了一个方法getUserByName(),然后改进了一下LoginService的逻辑,先调用userDao.getUserByName(),如果返回不为空就说明已经有一个叫Tom的人注册了,现在我又要改一下我的测试,要对getUserByName做必要的Mock,对于testAnotherTom的测试,这个Mock应该是 EasyMock.expectLastCall().andReturn(aNotNullObject).times(1)
我跑了一下测试,现在又全部通过了。我认为这样可以了,如果还有需要,我会再加新的测试,一般来说,写测试前仔细思考各种情况,这种增加测试用例的步骤会少很多。
事情并没有完,UserDAO没有实现,现在得把它实现了,它是一个DAO,经验告诉我使用一些工具来测试它,会比较快。单元测试,集成式单元测试,是的,我没有必要像LoginService那样做Mock测试,我的DAO可以直接访问数据库来进行测试,它是一个集成式单元测试,我可能选择DBUnit或者Spring的TransactionalTest来进行测试,反正它是一个运行在测试环境下的集成性的单元测试,今天跑可能成功,明天跑就失败了,因为测试环境里有一些对于这个测试而言的“脏”数据,这没有关系,集成式单元测试本来就依赖外部环境,这种测试可以用经验判断测试是否成功,不过最好保持测试环境的纯洁,所以测试结束时记得销毁那些“脏”数据。
我用同样的方式实现了login接口,不过我和我同事在鉴定用户登录许可方式上有分歧,我通过用户名和密码联合查询用判断Count是否为0来决定是否允许用户登录,我的同事认为此事不妥,他认为通过查询用户名取出纪录再比对密码是否一致来决定是否允许用户登录,我最后认为他的方法比较容易理解于是我改了我的实现,但我不需要修改测试用例,因为测试用例基于需求。
我又TDD了一次,于是我总结一下。
1. 其实我不需要严格地先完成测试再写实现,TDD指测试驱动设计,一切基于测试用例,围绕需求产生,认真对待测试用例,TDD代表对测试严谨的态度,精心准备测试用例比严格地先写测试再写实现要实际得多。
2. 我写某类时不需要关注写这个类能给我带来多少额外的功能,所有的类都由于某个测试而产生,我根本不考虑是否应该重载某个方法以得到其它的便利,我也很少运用继承来重写什么功能,当我需要一个工具类时,我只考虑那些与测试用例有关的真正需要的工具方法,而不考虑把这个工具类写得多么强大,所以只写那些能保障所有测试通过的代码,这些才是真正必要的代码。
3. 随时随地重构,只要觉得有需要。就像我编写LoginService的过程中,我觉得验证那些非空值验证可以提取出来,或者用使用验证框架,反正我重构了它们并让测试也通过了。
4. 我始终认为TDD始于黑盒,测试先行,没有实现也就看不到实现,我编写LoginService引入了一个UserDAO,于是我不得不修改测试代码做一些Mock工作,实际上Mock同样在描述测试用例,比如,

userDao.getUserByName(“Tom”);
EasyMock.expectLastCall().andReturn(null).times(1);


这就表示当前没有存在一个叫做Tom的注册用户,这也是一个测试用例的一部分,它不是输入而是一个前置条件。

 

1
2
分享到:
评论

相关推荐

    TDD测试驱动开发

    这些资源对于学习和理解TDD至关重要,可以帮助开发者掌握如何有效地进行测试驱动的软件开发。 总的来说,TDD是一种以测试为驱动的软件开发方法,通过编写测试来推动代码的设计和实现,从而提高代码质量和设计的可...

    UMTS-TDD手册

    ### UMTS-TDD 手册知识点解析 #### 核心知识点概述 本文档主要针对的是**NS2网络仿真软件**中的**UMTS-TDD**(Universal Mobile Telecommunications System - Time Division Duplex)仿真方法进行了深入细致的介绍...

    phpunit-TDD驱动开发

    ### 使用PHPUnit进行TDD驱动开发 #### 一、引言 测试驱动开发(TDD, Test-Driven Development)是一种软件开发方法论,它要求在编写实际功能代码之前先编写测试用例。通过这种方式,可以确保代码的质量,并且有助...

    嵌入式 TDD

    8. **最佳实践**:最后,本书总结了一系列TDD的最佳实践,帮助开发者避免常见错误,提升代码质量和开发效率。 #### 结语 通过《嵌入式TDD》,读者不仅能够深入了解TDD的核心原理,还能学习到如何将其有效地应用于...

    GSM TDD noise分析

    "GSM TDD 噪声分析" GSM TDD 噪声是一种常见的干扰现象,发生在 GSM 通信系统中的射频部分。这种噪声的产生是由于天线辐射出的射频能量和 PA 突发工作时带动电源的干扰。为了减少这种噪声的影响,我们可以采用一些...

    GSM_TDD板振说明及分析解决方法总结.docx

    在移动通信领域,GSM TDD(Time Division Duplexing,时分双工)系统是一种广泛应用的技术,其中“板振”或“板震”问题是一个常见的工程挑战。本文将深入探讨GSM TDD板振的成因、分析方法以及解决策略。 首先,板...

    单元测试与TDD实践

    单元测试与TDD实践 **一、单元测试之测试目的** 单元测试,作为软件开发过程中的重要环节,其核心目标在于确保代码的质量、可维护性和可扩展性。它通过独立测试软件中的最小可测试单元,如函数或方法,来验证其...

    Laravel开发-tdd

    【Laravel开发-TDD(测试驱动开发)】 在软件开发领域,TDD(Test-Driven Development,测试驱动开发)是一种编程实践,它强调先编写测试用例,再编写...总之,掌握TDD对于任何Laravel开发者来说都是一项重要的技能。

    LTE TDD系统原理

    华为LTE TDD系统原理培训PPT文档

    手机TDD引起电流声问题理论和经验总结

    ### 手机TDD引起电流声问题理论和经验总结 #### 一、TDD基本概念与原理 **TDD(Time Division Duplexing)**,即时分双工技术,在移动通信领域应用广泛,尤其在GSM系统中,TDD机制是实现频率资源高效利用的关键。...

    Ruby-TDD实战TestDrivenDevelopmentinAction

    **Ruby-TDD实战:Test Driven Development in Action** 在软件开发领域,Test-Driven Development(TDD)是一种编程实践,它强调先编写测试用例,然后编写满足这些测试的最小功能代码。Ruby作为一种动态、灵活的...

    TDD-CDMA_for_Wireless_Communications

    ### TDD-CDMA在无线通信中的应用 #### 一、引言 TDD-CDMA(时分双工-码分多址)是无线通信技术中的一个重要分支,它结合了时分双工...同时,对于TDD-CDMA的研究也将不断深入,探索更多创新应用场景和技术优化方案。

    TDD驱动测试开发培训

    测试驱动开发(TDD)是一种软件开发方法,它要求开发者首先编写失败的单元测试用例,然后再编写足够的代码以使测试通过。接下来,开发者会对代码进行重构以改善设计,同时确保测试依然能够通过。这个过程循环进行,...

    tdd_by_example.pdf

    ### 测试驱动开发 (TDD) 知识点解析 #### 一、TDD概念与原理 **测试驱动开发(Test-Driven Development, TDD)** 是一种软件开发方法论,其核心思想是在编写功能代码之前先编写测试用例。这种方法能够确保软件的...

    抑止TDD noise 的措施

    ### 抑止TDD Noise 的措施及解决方案 #### TDD Noise 概述 TDD (Time Division Duplex) 是一种常见的无线通信技术,在移动通信领域应用广泛。然而,在实际使用过程中,用户可能会遇到由TDD Noise 引起的音频质量...

    c#_tdd.zip

    在C#编程环境中,测试驱动开发(Test-Driven Development,简称TDD)是一种软件开发方法论,它强调先编写测试用例,然后根据这些测试用例来实现功能代码。这种做法有助于确保代码的质量,减少错误,并提高开发效率。...

    Test Driven: Practical TDD and Acceptance TDD for Java Developers (PDF英文版)

    《Test Driven: Practical TDD and Acceptance TDD for Java Developers》是一本专注于Java开发者进行测试驱动开发(TDD)和验收测试驱动开发(Acceptance TDD)的专业书籍。这本书以PDF英文版的形式提供,旨在帮助...

    TDD读书报告

    - **高效开发的需求**: 高效的软件开发过程对于软件团队至关重要,TDD作为一种提供快速反馈和高效开发的方式逐渐受到重视。 - **极限编程的影响**: 尽管TDD最初与极限编程紧密相连,但它也可以作为一个独立的技术...

Global site tag (gtag.js) - Google Analytics