uncategorized

谈谈单元测试

是什么?

很多人写了几年代码,可能连一行单元测试都没有写过。单元测试是什么?是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作

就我的理解,它是一种持续集成的手段。

通常一个持续维护的项目,就算别的任何测试都没有,也几乎不会少了单元测试。尤其是库开发者,几乎会非常审慎的编写详尽的测试代码

为什么?

理解单元测试是一个有点hard的过程。基本上,如果你之前没写过单元测试,或者自己胡乱开始写单元测试,缺乏思考的话,那你可能一直认为unit test make no sense。

单元测试有什么用?我认为最重要的主要有两点

  • 确保其他人修改代码后,原来的功能维持正确(这称为回归测试/regression testing)。
  • 强迫程序员设计合理的API

什么时候?

关于什么时候写单元测试,这是个问题。应该在编写完功能的时候写吗?或者是编写功能代码之前?还是同步进行?

谁?

有人会误解单元测试是测试工程师的任务,但实际上的原则是,谁改动的源代码,谁就负责增加/修改/减少被影响代码的单元测试

自动化?

好的测试应该同时包含自动化和手工的部分,自动化测试是一个广泛的话题,所涵盖的内容不仅仅限于单元测试。

推荐使用脚本或者自动化工具来做自动化测试

手动测试也是必要的,否则会进入一种非常盲目的开发状态

应该写多细?

TDD他爸Kent Beck:

老板为我的代码付报酬,而不是测试,所以,我对此的价值观是——测试越少越好,少到你对你的代码质量达到了某种自信(我觉得这种的自信标准应该要高于业内的标准,当然,这种自信也可能是种自大)。如果我的编码生涯中不会犯这种典型的错误(如:在构造函数中设了个错误的值),那我就不会测试它。我倾向于去对那些有意义的错误做测试,所以,我对一些比较复杂的条件逻辑会异常地小心。当在一个团队中,我会非常小心的测试那些会让团队容易出错的代码。

我认为这是不同场景下的策略,当你在维护一个多人合作的项目时,提高覆盖率是非常有必要的,当然,这不代表有必要写冗余测试代码

单元测试的弊端

好的单元测试代码通常是主体代码量的2-3倍,但是,代码量并不是单元测试的弊端。

单元测试的真正问题在于,一些不够好的测试代码可能会带来隐患。并给程序员一种盲目的错觉,

其次,单元测试并不是足够有效的保证,在google那样体量的公司,单元测试是远远不够的

另外一个通常认为的弊端是,开发工程师通常不愿意编写测试代码,这导致一些开源项目的patch经常被打回,原因是增加的feature没有提供相应的测试代码。对于hacker来说可能非常反感单元测试

一些技巧

  1. 不应该编写成功通过的单元测试-它们应该被写成不通过的。你可以在几分钟内让任何一组测试通过,但这只是在欺骗你自己。
  2. 测试类应该只测试一个功能-你应该用一个功能去测试一个方法。否则,你会违反了单一职责原则
  3. 测试类具备可读性-确保测试类标有注释并且容易理解,就像其他的代码一样。
  4. 良好的命名规范-再次测试时应该像其他代码一样-便于人们理解。
  5. 把断言从行为中分离出来-你的断言应该用来检验结果,而不是执行逻辑操作的。
  6. 使用具体的输入-不要使用任何的自动化测试数据来输入,像date()这些产生的数据会引入差异。
  7. 把测试类分类,放在不同的地方-从逻辑的角度看,当没有错误指向特定的问题时这更容易去查找。
  8. 好的测试都是一些独立的测试类-你应该让测试类与其他的测试、环境设置等没有任何依赖。这利于创建多个测试点。
  9. 不要包含私有的方法-他们都是一些具体的实现,不应该包含在单元测试里。
  10. 不要连接数据库或者数据源-这是不靠谱的。因为你不能确保数据服务总是一样的并且能够创建测试点。
  11. 一个测试不要超过一个模拟(mock对象)-我们努力去消除错误和不一致性。
  12. 单元测试不是集成测试-如果你想测试结果,不要使用单元测试。
  13. 测试必须具有确定性-你需要一个确定的预测结果,所以,如果有时候测试通过了,但是不意味着完成测试了。
  14. 保持你的测试是幂等的-你应该能够运行你的测试多次而不改变它的输出结果,并且测试也不应该改变任何的数据或者添加任何东西。无论是运行一次还是一百万次,它的效果都应该是一样的。
  15. 测试类一次仅测试一个类,测试方法一次仅测试一个方法-组织方法能够在问题出现时检测出来,并帮你确定测试依赖。
  16. 在你的测试里使用异常-你在测试里会遇到异常,所以,请不要忽略它,要使用它。
  17. 不要使用你自己的测试类去测试第三方库的功能-大多数好的库都应该有它们自己的测试,如果没考虑用mocks去产生一致性的结果的话。
  18. 限制规则-当在一些规则下写测试时,记住你的限制和它们(最小和最大)设置成最大的一致性。
  19. 测试类不应该需要配置或者自定义安装-你的测试类应该能够给任何人使用并且使它运行。“在我的机器上运行”不应该出现在这。

关于测试驱动开发

即为test-driven development, TDD。这种开发方法很难想象的会在极限编程中使用,其流程为

  1. 加入一个测试。
  2. 运行所有测试,新的测试应该会失败。
  3. 编写实现代码。
  4. 运行所有测试,若有测试失败回到3。
  5. 重构代码。
  6. 回到 1。

    TDD 是先写测试,再实现功能。好处是实现只会刚好满足测试,而不会写了一些不需要的代码,或是没有被测试的代码。

TDD太过于极端,不过非常大胆,我个人不是很倾向TDD,因为这会浪费非常多时间修改代码

Share