[经验总结] 关于单元测试

简介: 偶然想起@jeffz_cn在twitter上问:“私有方法真的不应该单元测试吗?为什么?我觉得有的组件只是逻辑复杂一些,因此会提取私有方法,并且测试这些私有方法的逻辑。如果把这些内容统统从外部“注入”,这样私有的逻辑就变公开了……但是这样难道没有过渡设计的味道吗?”。

偶然想起@jeffz_cntwitter上问:“私有方法真的不应该单元测试吗?为什么?我觉得有的组件只是逻辑复杂一些,因此会提取私有方法,并且测试这些私有方法的逻辑。如果把这些内容统统从外部“注入”,这样私有的逻辑就变公开了……但是这样难道没有过渡设计的味道吗?”。

然后就想起来我在项目中推动单元测试的经过。觉得还是应该总结一下比较好。

先说现状 (下面的数据我现在无法核实,但是,应该和实际值误差不大)

我目前负责的项目,有代码200K+,控件产品,尤其是Grid控件产品的代码复杂度远比应用程序的产品复杂度高。因为功能级的耦合度就很高。因此,我认为我的产品的复杂度应该相当于普通应用程序500K+的水平。

目前单元测试有1300+。这些单元测试主要是自5.1和6.0阶段引入的。对遗留代码的单元测试很少。这两个阶段添加和修改的代码应该在130K+。(呵呵,看到这里你一定觉得数据有问题。呵呵,确实看起来有问题。但是,细节这里就不能多说了。)

目前的单元测试代码覆盖率应该在20%~25%之间。

目前单元测试集成在每日构建中。至今没有发现单元测试失败的情况。(这一点很费解,目前归结为狗屎运)

再说经验

1. 单元测试应该在物理设计阶段进行规划,而不是完成代码后补单元测试。

2. Mock类库一般情况下是鸡肋

3. 对已有代码编写单元测试的难度非常高

4. 当单元测试很多的时候,组织和命名会比较有挑战。

5. 目前很少遇到单元测试影响重构的情况。

6. 单元测试对重构的帮助不如预期

7. 目前的现状下,很多平台的限制,使能够单元测试的部分很少。

再说想法

1. 单元测试可以作为开发Leader掌控设计的一种工具

2. 单元测试可以帮助开发人员设计出更好的结构

3. 单元测试不需要对private成员进行。

-=-=-=-=-=-=-=-=-=-=-=-不明真相围观的分割线-=-=-=-=-=-=-=-=-=-=-=-=-=-

好,接下来在一个一个展开来说。

1. 单元测试应该在物理设计阶段进行规划,而不是完成代码后。

实践告诉我,单元测试是需要良好的设计来支撑的。一个耦合度很高的模块几乎没有办法进行单元测试。我曾经几次相对已有的代码进行一些重构来支持单元测试。最终都放弃了。因为对这些耦合度很好的模块的重构总是会引入一些不可预期的问题。最终投入都要远远超过我的预计。因此,我得出的经验是:单元测试需要在物理设计时期就思考所涉及模块的可测试性,为了可测试性,需要对设计进行一些调整。往往这种调整都会使设计更好。因为,耦合性和可测试性是成反比的,因此可测试性越高,也就证明耦合性越低。低耦合是目前大家已经公认的良好设计的标准。

2. Mock类库一般情况下都是鸡肋

我在开始推动单元测试的时候就详细的研究了Rhino.Mocks类库。当时也被它强大语法能力所折服。并且实际将该类库应用在了我们项目的单元测试中。可是,过了一段时间后,当我再次需要使用Mock对象的时候。我才发现,我自己写一个Mock对象的成本其实非常低。远低于学习Rhino.Mocks抽象的语法的成本低。因此,我建议你除非能够确认你每天(至少每周)都要用到Mock对象。否则,建议不要使用Mock类库。

因为,Mock类库的接口设计往往和我们开发人员(尤其是静态类型语言开发人员)的思维方式不一致。一段时间不用这些类库的时候,你就会忘记他们抽象的语法。就需要再付出时间去学习他们的语法。但是,对于一些特定的测试场景,编写简单的Mock对象的成本本身就非常低的。往往5分钟就可以写出来自己用着很爽的Mock对象。

但是,不推荐使用Mock类库,不等于你不需要学习和了解Mock类库。因为学习他们的接口会对你自己设计Mock对象非常有帮助。

3. 对已有代码编写单元测试的难度非常大

因为我们做的是控件产品,在兼容性方面的要求很多时候会很苛刻。有时候一个产品发布之后,发现了Bug,下一个版本也要保证这个Bug原封不动的表现在那里(这时候,我们会说这是我们产品的一个Designimg_4d19918100143f9b3fa98140edaa97c4.gif)。因此,对代码的重构就会成本很高。因此,要想在不破坏原有结果的情况下进行单元测试的难度就非常大了。这一点,也许有我们产品的特殊性所在。但是,我觉的目前现实中的很多项目其实和我们的项目的要求还是很像吧。

4. 当单元测试很多的时候,组织和命名会比较有挑战。

我一直没有建立起来一套好的单元测试命名体系。目前在项目中的组织方式是:两个平行的工程,产品工程使用InternalVisibleToAttribute为测试工程提供Internal成员的访问权限。两个工程保持相似的组织方式。但是,当一个被测类型很庞大的时候,测试代码就很难组织好了。

5. 目前很少遇到单元测试影响重构的情况

不好的单元测试或过度测试都会对重构带来不好的影响,在我参与的上一个项目中就出现过这种情况。当时,项目突击了一段时间的单元测试。硬任务,每人必须写nnn个单元测试。后来,产品升级的过程中,就不断的删除原有的单元测试。那是因为后来没有人将单元测试作为指标了。否则,可能很多有价值的重构都会不做了。因为修改单元测试太费劲了。这一点对我造成了阴影。以至于我在当前项目的前期没有很高调的推动单元测试。这是这个项目过程中我最大的遗憾之一。

也许是因为,单元测试覆盖率较低;也许是因为我们没有拿单元测试再作为指标,因此大家写的单元测试的质量更高了。总之在当前这个项目的升级中,似乎很少发现维护单元测试付出较大的情况。

6. 单元测试对重构的帮助不如预期

正如前面所说,我目前负责的项目中,较大的重构发生次数并不多。小规模的重构中确实有单元测试帮助我发现问题的情况。但是,远不像我的预期那么多。这一点,应该说和单元测试的覆盖率较低有关。

7. 目前的现状下,很多平台的限制,使能够单元测试的部分很少。

虽然我很有意识的推动单元测试,并且在实际开发中使用单元测试。但是,目前的情况,在WinForm平台下的开发中进行单元测试的桎梏还是很多。也许和我们的产品特性有关,实际过程中,我经常发现,能够测试的代码不是那些经常出问题的代码。而经常出问题的代码,往往因为和平台关系太密切,而无法切割出来进行单元测试。

ASP.NET MVC在一开始设计的时候就考虑了可测试性,因此,这一方面应该更好一些。但是,至少我目前没有看到微软在其他平台下的可单元测试方面的努力。这是我在使用单元测试过程中最郁闷的地方。

-=-=-=-=-=-=-=-=-=-=-=-不明真相围观的分割线-=-=-=-=-=-=-=-=-=-=-=-=-=-

以下最后两点属于我的想法,目前的项目中因为前面的一些约束,导致我还无法证明我的想法是正确的或错误的。列在这里也仅供大家参考了。

8. 单元测试可以作为开发Leader掌控设计的一种工具

和聪明的人在一起工作的最大困难就是你没有办法控制项目的设计。聪明人并不一定出好的设计。但是,你又没有办法说服他采用你的设计。我觉得,单元测试是一个开发Leader掌控设计质量的很好的工具。因为它可以成为一个简单的指标:“你别给我说你的设计有多么好,如果你的设计不可测试,那么抱歉,你不能放入产品代码。”,反过来说,如果你的设计可测试,那么意味着,即使你的设计再烂,它也是可以替换的。总有一天,我会把它从产品里面干掉的。img_26c2d317e57da3ed54c554e8e22faa54.gif

说说而已,其实大多时候,我也不确定我的设计是好的。但是,我相信,可测试≈低耦合≈好的设计。我相信,当项目复杂到一定程度的时候,建立一些这样简单粗暴可测量的规矩,对产品的健康发展很有帮助。

9. 单元测试可以帮助开发人员设计出更好的结构

因为那个简单粗暴可测量的规矩,迫使开发人员降低自己设计的耦合度。从而产生更好的设计。

-=-=-=-=-=-=-=-=-=-=-=-不明真相围观的分割线-=-=-=-=-=-=-=-=-=-=-=-=-=-

最后,来和老赵探讨一下他的问题。我的观点是:

10. 单元测试不需要对private成员进行。如果需要,那么抽象Strategy类。并对Strategy类进行测试。这个不属于过度设计。

因为,我认为需要测试的方法一定具有以下几个特点中的至少一个:

  • 它有出错的可能。
  • 它具有的复用的可能。
  • 它具有变化的可能。

对于第一点,我认为应该是可以通过对public成员的测试来完成对该private方法的测试的。而二三两点,正是抽象的用武之地。抽象的重要目的就是在封装变化和复用。如果这个函数具有了变化和复用的可能性,我们就应该将它抽象为一个独立的对象,并且对他进行测试。这是一个更好的设计,而不应该归入过度设计的范畴。

如果不符合上面的二三两点,我觉的对这个private成员的测试就属于过度测试的范畴了。是应该杜绝的。因为,你的测试代码很可能没有起到保证质量的作用,而是成为了将来重构的桎梏。因为,理论上重构的过程不需要保持私有成员的行为不变。但是,你的单元测试又要求私有成员的行为不变。这个其实就是我上文中提到的“单元测试影响了重构进行”的情况。

相关文章
|
12月前
|
测试技术 API C#
C# 软件开发之单元测试
在日常开发中,一般通过启动调试或运行程序来查看功能是否符合预期,如果不符合预期,则需要优化程序,再次运行,如此反复,直到程序的输出符合预期需求为止。随着程序的不断复杂化,某些功能的测试也变得越来越复杂,可能为了验证一个很小的改动项,就需要操作很多步骤,才能验证成功,如果验证不成功,则需要多次重复验证,这对于开发者来说,将大大的拖延了开发进度。如何才能将复杂的功能进行拆分,每一个都可以单独进行验证呢?如果其他的功能没有问题,则只需要验证修改的那部分内容即可,这就是本篇文章需要介绍的单元测试。通过创建和运行单元测试,检查代码是否按预期工作。
324 1
|
5月前
|
Java 测试技术 Maven
5个编写技巧,有效提高单元测试实践
本文作者详细讲解了关于单元测试的相关知识,做好单元测试能有效地保障代码质量,本文将手把手教你学会应用单元测试并附有案例、测试插件。
|
前端开发 测试技术
如何使用 Vitest 在前端项目中做单元测试 TDD
如何使用 Vitest 在前端项目中做单元测试 TDD
如何使用 Vitest 在前端项目中做单元测试 TDD
|
JSON Java 测试技术
SSH框架下单元测试的实现
实现了部门的增删改查 对Action进行了单元测试 对Service 进行了单元测试,通过mock的方式实现。
143 0
|
测试技术
软件测试面试题: 单元测试和集成测试的区别?
软件测试面试题: 单元测试和集成测试的区别?
125 0
|
Java 测试技术 程序员
从零开始写单元测试
作为一个程序员,或多或少听说过单元测试,但很多小伙伴还没有在实际项目中用到。
162 0
|
Web App开发 监控 JavaScript
前端单元测试那些事
很长一段时间以来,单元测试并不是前端工程师应具备的一项技能,但随着前端工程化的发展,项目日渐复杂化及代码追求高复用性等,促使单元测试愈发重要,决定整个项目质量的关键因素之一
472 0
前端单元测试那些事
|
JSON Java 测试技术
谈一谈单元测试
写在前面对于我们开发人员来说,单元测试一定不会陌生,但在各种原因下会被忽视,尤其是在我接触到的项目中,提测阶段发现各种各样的问题,我觉得有必要聊一下单元测试。对于单元测试到底有没有存在的必要,这里不是我想要说的重点。有兴趣的可以去了解一下:【单元测试和TDD】【单元测试到底是什么】为了写而写的单元测试没什么价值,但一个好的单元测试带来的收益是非常客观的。问题是怎么去写好单元测试?怎么去驱动写好单元
651 0
谈一谈单元测试
|
测试技术 Python
Testify Pythoinc的单元测试框架
Testify 是Python的一款测试框架,可以替代unittest和nose。
167 0
Testify Pythoinc的单元测试框架
|
Java 测试技术 API
单元测试实践
工欲善其事,必先利其器. 单元测试三剑客: - TestNg:单元测试框架 - AssertJ:断言工具 - Jmockit:mock工具
3471 0