单元测试是否会导致过早的泛化(特别是在C ++中)?


20

初步说明

我不会区分不同种类的测试,在这些站点上已经有一些与此有关的问题

我会采取什么样的存在,并且说:单位的“测试应用程序的最小单位可分离”的意义测试从这个问题实际上导出

隔离问题

程序的最小可隔离单元是什么。好吧,正如我所见,它(高度?)取决于您所使用的语言。

Micheal Feathers谈到接缝的概念:[WEwLC,p31]

接缝是一个您可以更改程序行为而无需在该位置进行编辑的位置。

而且,在不进行细节讨论的情况下,我了解到在单元测试的上下文中存在的缝隙—在程序中可以使“测试”与“单元”进行交互。

例子

单元测试-尤其是C ++中的单元测试-要求被测试的代码添加更多的接缝,这对于给定的问题是严格要求的。

例:

  • 在非虚拟实现就足够的地方添加虚拟接口
  • 拆分-generalizing(?)-一个(较小的)类,进一步“恰好”以方便添加测试。
  • 将单个可执行项目拆分为看似“独立”的库,“公正”以便于为测试而独立地编译它们。

问题

我将尝试一些可能会问相同问题的版本:

  • 单元测试是“仅”对应用程序代码进行结构化的一种方式,这对单元测试是有益的,还是实际上对应用程序结构有利。
  • 是需要的代码泛化作出任何它的单位可测试非常有用,但单元测试?
  • 添加单元测试是否会强制一个泛化?
  • 从问题域的角度来看,形状单元测试对代码的“总是”作用力是否也对代码总体而言是一种良好的形状?

我记得有一条经验法则,直到您需要/直到有第二个地方使用该代码时,它才会泛化。使用单元测试,总是有第二个地方使用代码-即单元测试。那么,这个理由足以概括吗?


8
一个常见的模因是,任何模式都可以过度使用而成为反模式。TDD也是如此。人们可以在收益递减点之后添加可测试的接口,在这种情况下,所测试的代码比所添加的通用测试接口要少,而且成本效益过低。具有添加接口的休闲游戏(例如深空任务操作系统)可能会完全错过其市场窗口。确保添加的测试在这些拐点之前。
2011年

@ hotpaw2亵渎!:)
maple_shaft

Answers:


23

单元测试-尤其是C ++中的单元测试-要求被测试的代码添加更多的接缝,这对于给定的问题是严格要求的。

仅当您不考虑测试解决问题的组成部分时。对于任何非同小可的问题,不仅在软件领域,它都应该如此。

在硬件世界中,这是很久以前就已经学到的-很难。数百年来,各种设备的制造商已经从无数的吊桥,爆炸的汽车,冒着CPU的烟雾等中吸取了教训。他们所有人都在他们的产品中建立了“额外的接缝”,以便使其可测试。如今,大多数新车都设有诊断端口,供维修人员获取有关发动机内部运行状况的数据。每个CPU上大部分的晶体管用于诊断目的。在硬件世界中,“额外的”东西的每一分钱都要花费,而当成千上万的产品被制造出来时,这些费用肯定会加起来很大一笔钱。尽管如此,制造商仍愿意花所有这些钱来进行可测试性。

回到软件世界,与后来的具有动态类加载,反射等功能的语言相比,C ++确实更难以进行单元测试。但是,大多数问题至少可以缓解。到目前为止,在一个我使用单元测试的C ++项目中,我们没有像在Java项目中那样频繁地运行测试-但它们仍然是CI构建的一部分,我们发现它们很有用。

单元测试要求“仅”结构化应用程序代码的方式对单元测试有利吗?还是实际上对应用程序结构有利?

以我的经验,可测试的设计总体上是有益的,而不仅仅是单元测试本身。这些好处有不同的层次:

  • 使您的设计可测试使您必须将应用程序分成较小的,或多或少的独立部分,这些部分只能以有限且定义明确的方式相互影响-这对于程序的长期稳定性和可维护性非常重要。否则,代码将趋向于变成意大利面条式代码,在该代码中,对代码库的任何部分进行的任何更改都可能对程序中似乎无关的不同部分造成意想不到的影响。不用说,这是每个程序员的噩梦。
  • 以TDD方式编写测试本身实际上会练习您的API,类和方法,并且可以作为非常有效的测试来检测您的设计是否有意义-如果针对该接口编写测试并感到尴尬或困难,则在开发过程中您会获得有价值的早期反馈仍然很容易塑造API。换句话说,这可以防止您过早发布API。
  • TDD强制执行的开发模式可帮助您专注于要完成的具体任务,并使您始终如一,从而最大程度地减少了解决其他问题的机会,从而增加了不必要的额外功能和复杂性等
  • 单元测试的快速反馈使您能够大胆地重构代码,使您能够在代码的整个生命周期中不断适应和发展设计,从而有效地防止了代码熵。

我记得有一条经验法则,直到您需要/直到有第二个地方使用该代码时,它才会泛化。使用单元测试,总是有第二个地方使用代码-即单元测试。那么,这个理由足以概括吗?

如果您可以证明您的软件能够准确地完成应做的工作-并以足够快,可重复,便宜和确定性的方式证明它可以满足客户的需求-而无需因单元测试而造成“额外”的泛化或接缝,那就去做吧(并让我们知道您的做法,因为我敢肯定这个论坛上的很多人都会和我一样感兴趣:-)

顺便说一句,我认为“泛化”是指引入接口(抽象类)和多态性(而不是单个具体的类)之类的东西-如果没有,请说明。


先生,我向您致敬。
GordonM 2012年

一个简短但有趣的注释:“诊断端口”主要在那儿,因为政府已将其强制要求作为排放控制计划的一部分。因此,它具有严重的局限性。有许多可能无法通过此端口诊断的事物(即与排放控制无关的事物)。
罗伯特·哈维

4

我将向您抛出《见证的方式》,但总结一下:

如果您花费大量时间和精力使代码更复杂以测试系统的单个部分,则可能是结构错误或测试方法错误。

最简单的指南是:您要测试的是代码的公共接口,该代码将被系统的其他部分使用。

如果您的测试变得冗长而复杂,则表明使用公共接口将很困难。

如果您必须使用继承来使您的类能够被当前将用于其实例之外的任何其他实例使用,那么您的类很有可能与它的使用环境紧密相关。您能举一个例子说明这种情况是对的吗?

但是,提防单元测试教条。编写测试,使您能够检测到会导致客户端对您大喊大叫的问题


我要添加相同的内容:从外部制作一个api,测试该api。
Christopher Mahan

2

TDD和单元测试不仅对单元测试有利,而且对整个程序都有利。这样做的原因是因为它对大脑有益。

这是有关名为RobotLegs 的特定 ActionScript框架的演示。但是,如果您翻阅前10张幻灯片左右,它就会开始触及大脑的各个部分。

TDD和单元测试会迫使您以更适合大脑处理和记忆信息的方式进行行为。因此,当摆在您面前的确切任务只是进行更好的单元测试,或使代码更可单元测试时……实际上,它的工作是使代码更具可读性,从而使代码更具可维护性。这样可以使您更快地编写习惯代码,并且在需要添加/删除功能,修复错误或通常打开源文件时,可以更快地理解代码。


1

测试应用程序的最小可隔离单元

的确如此,但是如果您采取的措施过多,它并不会给您带来太多的好处,并且会花费很多。我相信,正是这一方面促使使用BDD一词成为TDD应该是所有的东西。 -最小的可隔离单位就是您想要的。

例如,我曾经调试过一个网络类,该类具有(除其他以外)2种方法:1设置IP地址,另一种设置端口号。当然,这些方法非常简单,可以轻松通过最简单的测试,但是如果您设置端口号然后设置ip地址,则它将无法正常工作-ip setter使用默认值覆盖了端口号。因此,您必须对整个类进行测试以确保行为正确,我认为TDD的概念缺失了,但BDD可以为您提供帮助。当您可以测试整个应用程序中最明智和最小的区域时(在本例中为网络类),您实际上并不需要测试每个微小的方法。

最终,测试没有万灵药,您必须做出明智的决定,将有限的测试资源应用于多少粒度和粒度。自动为您生成存根的基于工具的方法不会执行此操作,而是一种钝器方法。

因此,鉴于此,您不需要以某种方式来构造代码来实现TDD,但是您要达到的测试水平将取决于代码的结构-如果您具有将所有逻辑紧密绑定到的整体式GUI GUI结构,那么您将发现很难分离出这些片段,但是您仍然可以编写单元测试,其中“ unit”是指GUI,并且所有后端DB工作都是模拟的。这是一个极端的示例,但是它表明您仍然可以对其进行自动化测试。

对代码进行结构化以使其更易于测试较小的单元的副作用确实有助于您更好地定义应用程序,并且使您可以更轻松地替换零件。这在编码时也有帮助,因为不太可能有2个开发人员在任何给定的时间都在同一个组件上工作-不像一个单片应用程序具有相互依赖的关系,这种依赖关系会破坏其他人的工作,导致合并时间。


0

您已经对语言设计的权衡有了很好的认识。C ++中的一些核心设计决策(将虚拟函数机制与静态函数调用机制混合在一起)使TDD变得棘手。该语言并不真正支持您轻松实现所需的功能。编写几乎无法进行单元测试的C ++很容易。

我们最好从准功能思维定式编写函数而不是过程(不带参数并返回void的函数)中编写TDD C ++代码,并尽可能使用组合。由于很难替换这些成员类,因此我们专注于测试这些类以构建受信任的基础,然后在将其添加到其他对象时知道基本单元功能。

关键是准功能方法。想想看,如果您所有的C ++代码都是不访问全局变量的自由函数,那么这对单元测试很容易:)

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.