Skip to content


测试驱动开发 重构 Mock 单元测试

今天看了很多关于测试驱动开发TDD和单元测试相关的文章和帖子。把所学总结和整理一下,方便自己以后复习。

  • 测试驱动开发和重构是极限编程的核心部分;
  • 什么是测试驱动开发?
    测试驱动开发是一种开发方式:
    1) 你要维护一套详尽的程序员测试集。除非存在其目的是为了让产品代码通过的测试,否则就不存在任何产品代码。
    2) 除非存在相关测试,否则不编写任何产品代码。这样做的原因是系统中的一切都必须是可测试的。
    3) 首先编写测试。先编写少量的测试,随后编写足够使测试通过的代码,然后再多编写一些测试,接着再编一些代码,编写测试……
    4) 由测试来决定需要编写怎样的代码。编写足够让测试通过的代码,仅此而已。
  • 两顶帽子:
    编码:在你带上这顶帽子的时候,你向系统中增添新的功能但不进行重构。
    重构:在你带上这顶帽子的时候,你进行重构但不增加新的功能。

    在你要让某个测试通过的时候,带上编码的帽子。一旦测试通过,你就换顶帽子执行重构来进行清理。
  • 为什么要重构?
    1、改进软件设计。重构就是要让所有代码回到应该在的位置。重构还可以消除重复代码。
    2、使软件更容易理解。修改代码,让代码反映我的意图。重构可以帮助你看到你以前看不到的设计层面的东西。重构带你到更高的理解层次上。
    3、帮助你debug。弄清楚程序结构的同时,也很清楚地看到自己所做的一些假设。
    4、助你提高编程速度。良好设计才是快速开发软件的根本。
  • 单元测试并不是可以无条件实行的,良好的设计,清晰的层次是必须的。对一团乱麻搞单元测试,人基本要累死。
  • 关于单元测试是噩梦的讨论:
    • 项目进展到一定程度,也会产生出大量的单元测试代码。而且这些单元测试代码也被不断的增加、修改和删除。渐渐单元测试代码也变得难以维护了,里边夹杂了太多的业务逻辑。创建一个新的测试要做很多初始化的工作,很难在ide里直接运行所有测试,很难单独运行某项测试,运行所有的单元测试要比较长的时间……
      造成这种现象的原因有以下几种:

      • 1.未及时对单元测试进行重构,删除不必要的测试,合并类似的测试
        2.单元测试粒度过大,造成测试类过于臃肿。应该把大粒度的测试抽出来单独作为功能测试运行。
        3.大量依赖外界环境,如数据库。应使用mock object解除依赖关系。
    • 首先问自己几个问题
      • 1. 测试是不是程序员自己写的
        2. 是不是用的Test First
        3. 如果是unit test, 是不是 method by method写的
        4. 有没有区分什么是unit, integration test, 什么是functional test.
    • 单元测试的目的是为了让我们今后维护时能更快的定位BUG,所以简单独立测试代码更易读、更利于这个目的。
      (1)“创建一个新的测试要做很多初始化的工作”,其根本原因是在于单元测试做法有误,”单元测试粒度过大,造成测试类过于臃肿”,“大量依赖外界环境,如数据库”。
      只针对最复杂和最核心的逻辑来写单元测试,减少了单元测试代码的数量,也就减少了对测试代码本身的维护量。有一些不应该由单元测试来做的工作,还是留到集成测试和测试员的beta版测试吧。
      (2)单元测试要尽量做到环境无关性。比如测试EJB的单元测试,运行它并不需要启动Weblogic等EJB容器才能测试的。
  • 关于《不要把Mock当作你的设计利器》的讨论
    • 我倒觉得这本身就是不是问题的问题. Mock本身就是假设某种行为是正确的,从而保证单元的独立性,又何来行为依赖呢? 什么地方都去用Mock自然是带来重构也好,测试跟踪也好的麻烦了.
      我们总是谈单元测试,谈测试驱动, 才发现并不是大家对于单元的概念有比较统一的理解.自然而然,由此引申的相关Mock也就变得问题多多了.
    • Mock有自己的价值,但滥用Mock除了文里列出的种种弊端外还会使TDD失去一些很重要的意义。TDD能够优化设计的原因之一是你必须要仔细思考设计以使你的代码能够被自动测试,从而降低设计的耦合性。Mock的出现使得这种必要性明显降低。
      我认为Mock的价值主要在于模拟网络连接,消息队列,对象序列化服务之类依赖于环境的地方。
    • Mock还有一个更有用处的地方, 在一个多模块的商业项目中,  简单讲, 甲负责完成A, 乙负责完成B, 而A需要通过接口调用B实现, 这个时候, 因为项目的时间安排的关系, 甲完成了自己的代码, 而乙并没有完成B实现, 在这个时候,难道甲的单元测试不做了么? 使用Mock的目的就在这里了. 作为单元测试本身的概念, A是独立的单元, 它的单元测试只需要测试A本身的(如果连同B也考虑进去, 那只能是集成测试了).所以在这个时候需要做的就是假定B已经完成,并且正确, 基于此对A进行单元测试.
    • Mock用烂绝对不是Mock的错, 而是使用Mock的人根本就没有理解Mock的真正目的和使用范围.
    • mock的问题在于,你实际上不是在检查一个对象的接口,而是深入到了实现过程中。
      stub和mock的使用是有很大区别的,stub的用法是:假设一个对象是正确的,在这个基础上检查其他对象的接口。比如:我们假设A类肯定是对的,在此基础上检查B对不对,于是就制造一个A-stub。而mock是用来检查A的实现过程到底是不是对的。
      mock实际上是和面向对象的设计原则相违背的,他是在深入一个过程,去检查实现细节。因此mock用的多不多,体现了这个程序设计的内聚性好不好。如果程序中到处都需要用mock来测试,说明程序的内聚性不高。
      比如说有这样的需要:A向数据库里面写一个数据,然后B从数据库里面读这个数据。这样的设计从道理上来说就是不合理 的,既然是同一段逻辑,就应该封装在同一段代码中,外界只应该关注他的接口,而不应该知道这个数据的定义。对这段代码进行测试,也只需要测试他的接口。如 果需要知道实现过程对不对,数据在表里面到底保存了没有,这时候就可以使用mock。即使是这段代码里面错翻天了,也不应该对其他代码造成损害,至少接口 是对的。这就把mock的使用范围和影响力减到最小了。
  • 关于不要全程使用Mock对象的讨论:
    • 测试代码是功能代码的两三倍都是很正常的事情。不能因为测试代码量大就拒绝进行完备的单元测试编写。因为越是项目后期,有一个完备的单元测试集,你就对项目代码的信心越足。
    • 不同的测试目标决定了你选择什么样的测试方案
  • 单元测试vs集成测试
      在单元测试时,我们尽量在屏蔽模块间相互干扰的情况下,重点关注模块内部逻辑的正确性。而集成测试则是在将模块整合在一起后进行的测试,它的目的在于发现 一些模块间整合的问题。有些功能很难通过模拟对象进行模拟,相反它们往往只能在真实模块整合后,才能真正运行起来,如事务管理就是其中比较典型的例子。
      按照Spring的推荐(原话:You should not normally use the Spring container for unit tests: simply populate your POJOs in plain JUnit tests!),在单元测试时,你不应该依赖于Spring容器。换言之,你不应该在单元测试时启动ApplicatonContext并从中获取 Bean,相反你应该通过模拟对象完成单元测试。而集成测试的前提则是事先装配好模块和模块之间的关联类,如将DAO层真实的UserDao和 LoginLogDao装配到UserServiceImpl再进行测试。具体装配工作是在Spring配置文件中完成的,因此在一般情况下,集成测试需 要启动Spring容器,你可以在测试类中简单地从Spring容器中取出目标Bean进行测试。
Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • LinkedIn
  • description
  • TwitThis
  • MySpace
  • StumbleUpon
  • Google
  • FriendFeed
  • Mixx
  • Reddit

Posted in 全部, 读书笔记. Tagged with , , .

0 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

Some HTML is OK

(required)

(required, but never shared)

or, reply to this post via trackback.

使用新浪微博登陆