什么样的功能和/或类无法进行单元测试,为什么


21

开发人员没有良好的单元测试的主要借口是“代码不是以可单元测试的方式设计的”。我试图了解无法进行单元测试的设计和代码类型。


2
你的借口?一个同事?经理?您正在使用什么语言/框架?

1
应用程序中有很多遗留代码,没有时间重新设计。
knut

4
@gnat:我不同意。您引用的问题是关于单元测试无用的情况。当前的问题是关于使单元测试变得困难的情况。
阿森尼·穆尔琴科

@MainMa显然我们读了不同的问题。“我试图了解哪些类型的设计和代码无法进行单元测试。” =>“什么时候不进行单元测试合适?”
gnat 2013年

1
@manizzzz:您可能需要将其编辑为问题。
jmoreno 2013年

Answers:


27

有几个因素可能会使代码难以进行单元测试。在这种情况下,重构有助于改进代码以使其可测试。

一些可能很难测试的代码示例:

  • 1000-LOC函数,
  • 严重依赖全球状态的代码,
  • 需要具体,复杂的代码来构建对象(例如数据库上下文),而不是依赖于接口和依赖注入,
  • 执行缓慢的代码,
  • 意大利面代码,
  • 遗留代码经过多年的修改,并不关心可读性或可维护性,
  • 很难理解没有注释或暗示作者原始意图的代码(例如,使用诸如的变量名的代码function pGetDp_U(int i, int i2, string sText)

请注意,缺少清晰的体系结构不会使代码难以进行单元测试,因为单元测试只涉及代码的一小部分。不清楚的体系结构仍然会对集成和系统测试产生负面影响。


8
也很难测试的代码,不注入依赖非纯函数,如随机数,当前时间,硬连线的I / O等
9000

像这样测试代码很简单-您只需要正确的测试工具即可,而不需要修改代码以使其适合他们。以Microsoft假货为例。
gbjbaanb 2013年

@MainMa,我喜欢这个答案。您是否也愿意评论哪些因素将不同的测试推向集成和系统测试?我知道我之前问过类似问题的原因是,我没有一个路线图来说明哪种类型的测试最好放在哪里(或者也许是最具成本效益的地方)-我认为单元测试是唯一的。
J特拉纳2014年

14

有很多事情使代码难以进行单元测试。巧合的是,其中很多也使代码难以维护:

  • 违反Demeter法则
  • 在方法内部创建对象,而不是注入依赖项
  • 紧耦合。
  • 内聚力差。
  • 严重依赖副作用。
  • 严重依赖全局变量或单例。
  • 不会暴露太多中间结果。(我曾经不得不对具有单个输出的10页长的数学函数进行单元测试,而没有可用的中间结果。我的前任基本上对任何碰巧给出的答案都进行了硬编码)。
  • 在很大程度上直接依赖于难以模拟的服务,例如数据库。
  • 运行时环境与开发环境(例如嵌入式目标)有很大不同。
  • 仅以编译形式(如第三方DLL)可用的单元。

我认为这是一个很好的答案。您涉及许多代码级问题和全局状态问题。@MainMa还有其他一些我认为是有效的问题,但定义不明确。Jeffery Thomas提到了I / O和UI。我认为,如果您将这三个答案的重要部分加进去,您将获得很好的凝聚力。虽然我最喜欢这个答案,但是因为专注于代码反模式。
M2tM 2013年

1
Argh-没有比单元测试断言与业务需求不相似,并且只是给定时间的输出的最糟糕的情况-模拟被设置为例如被调用3次吗?为什么是3?因为第一次进行测试是3分/ rant :)
Michael

紧耦合只有在不合适时才是坏的。具有高度内聚力的代码必须紧密耦合。例如,在使用变量之后声明。紧密耦合,高度凝聚力。
Dietbuddha 2014年

1
在没有任何业务案例/理由的情况下,对照代码检查输出结果的单元测试称为特性测试。它们用于维护中,以前没有测试,也常常没有文件记录的维护要求,并且如果该函数的输出发生更改,您必须在其中放置一些会损坏的东西。他们总比没有好。
安迪·克鲁维尔

5

人们不希望进行单元测试的常见代码示例:

  • 直接与I / O交互的代码(读取文件,直接网络调用等)。
  • 直接更新用户界面的代码。
  • 直接引用单例或全局对象的代码。
  • 隐式更改对象或子对象状态的代码。

使用模拟框架,所有这些示例都可以进行单元测试。为内部依赖项设置模拟替换只是工作。

真正无法进行单元测试的东西:

  • 无限循环(用于线程管理器,驱动程序或其他长时间运行的代码)
  • 某些类型的直接汇编操作(某些语言支持)
  • 需要特权访问的代码(并非不可能,只是一个好主意)

2

有一些地方会使编写单元测试变得更加困难。但是,我要强调的是,这并不意味着您应该简单地放弃有用的技术,因为它们可能会增加测试的复杂性。与任何编码一样,应该进行自己的分析,以确定收益是否超过成本,而不是盲目接受网络上一些随意的家伙发布的内容。

设计代码写得不好

  • 不适当的耦合(通常是不应该的紧密耦合)
  • 厨房水槽代码(一个函数的逻辑/职责过多)

在不同范围内对国家的依赖

除非您知道自己的所作所为,否则这些成本中的大多数都会失去控制。不幸的是,许多人通常不知道如何使用这些技术来减轻诸如测试复杂性之类的事情。

  • 单身人士
  • 全球
  • 关闭

外部/系统状态

  • 硬件/设备依赖性
  • 网络依赖
  • 文件系统依赖性
  • 进程间依赖
  • 其他系统调用依赖性

并发

  • 线程(锁,关键部分等)
  • 分叉
  • 协程
  • 回电
  • 信号处理程序(不是全部,而是一些)

2

根本没有无法测试的代码。但是,有一些非常难于测试的代码示例(以至于可能不值得付出努力):

硬件交互-如果代码直接操纵硬件(例如,写入寄存器以移动物理设备),则对其进行单元测试可能太困难或太昂贵。如果您使用真实的硬件进行测试,则可能会很昂贵,从而无法在测试工具中获得适当的反馈(还需要更多设备!),否则,您必须模拟物理对象的确切行为-这并不是小菜一碟一些实例。

时钟交互-通常比较容易,因为几乎总是可以很简单地模拟出系统时钟功能。但是,如果您做不到,那么这些测试将变得难以管理-基于实时的测试往往需要花费很长时间才能运行,而根据我的经验,由于系统负载使事情花费的时间比应有的长,因此它们往往非常脆弱。 ,导致幻像测试失败。


0

我的主要三组是:

  • 依赖外部服务的代码

  • 不允许测试人员独立于应用程序修改状态的系统。

  • 不复制生产设置的测试环境。

这是我成为一名由开发人员转为质量检查工程师后所经历的最多的事情。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.