如果您要对软件开发商店进行民意测验,并询问他们是否进行了单元测试,您会得到各种各样的回答。有些人会衷心地说他们是,有些人会不好意思地说,他们完全打算在明年解决这个问题,而且他们一直在研究这个问题。在中间,你会得到很多回应,相当于,“这很复杂。
在我作为顾问的旅行中,我亲眼目睹了这样做的原因。在过去的十年中,自动化测试的采用率急剧增加,这种采用率的增加意味着许多商店都在冒险。当然,这意味着许多在代码库中拥有大量遗留代码和笨拙结构的商店正在冒险,这导致了有趣而复杂的结果。
“这很复杂” 通常涉及 “我们尝试过,但不适合我们” 和 “我们在可能的时候做,但开关还没有翻转” 的变体。所有这些变体的根源在于一个在谈论你的团队时很难承认的事实-“我们很难在这方面做得很好。
如果这描述了你或你认识的人,请振作起来。“TDD简介” 和 “NUnit 101” 指南使它看起来非常简单。但是这些学习来源通常会向您展示如何为 “内存计算器” 之类的东西编写单元测试,旨在简化领域和代码,以便您了解单元测试的机制。但是,在这样做的过程中,他们描绘了一幅欺骗性的画面,说明用测试覆盖你的代码应该是多么容易。
如果你多年来一直在编写代码,没有想到在单元级别进行测试,那么很可能你熟悉的、舒适的编码实践被证明是错误的朋友。换句话说,当您尝试采用自动化测试时,您的代码库可能充满了使您的生活极其困难的东西。以下是我看到的一些最常见的。
繁忙的构造函数
当你编写单元测试时,有一个非常简单和简约的模式。你实例化一个对象,为你想要测试的条件安排它,做你正在测试的事情,然后验证结果是你所期望的。一件事是,忙碌的建筑商威胁要把你绊倒。
如果构造函数执行许多行代码,这意味着许多行代码可能会失败。你把错误的论点传递给它吗?是你传递给它的对象之一里面的东西没有正确设置?构造函数实例化正在爆炸的东西吗?它期望一个全局变量有一个值,它不?
这些问题中的任何一个都会导致单元测试爆炸并需要调试。这不仅令人沮丧,而且当你试图找出测试的细节时,这完全令人困惑。你忙碌的构造函数正在测试头痛。
全局状态
说到测试问题,全球状态是测试问题的巨大来源。全局变量 (可以突变的公共静态变量) 是最明显的例子,也是我在上一节中提到的,但也有其他形式。单例设计模式和服务定位器模式基本上是全局变量存储库,封装状态的静态方法也具有相同的效果。
从可测试性的角度来看,全局状态的主要问题是它创建了对您来说不明显的隐藏依赖关系。如果您要为具有无参数构造函数的名为 “CustomerOrder” 的东西编写测试,则可能需要实例化它,然后在向其添加行项之后断言它具有单个行项。
想象一下你的惊喜,如果,当你实例化它,你得到一个异常告诉你,你有一个坏的连接字符串。哦,好吧,那是因为order类引用了一个数据库单例,作为其初始化的一部分,使用应用程序配置文件中定义并存储在全局变量中的内容来访问并连接到数据库。哎呀。祝你好运,为单元测试设置所有这些。
延迟加载
我看到的与难以测试的代码库相关的另一种模式是延迟加载的亲和力。我理解这种模式的吸引力,作为一个可以欣赏一个好的抽象的人。您可以获得两个世界中最好的: 在绝对必要之前不会产生性能下降,并且不会给代码的客户端带来实现细节的负担。
但另一方面是一个问题。向客户隐藏这些细节也意味着向试图测试代码的人隐藏它们 — 对那些 “这将需要一毫秒或5分钟” 的人来说非常重要。延迟加载通常是为需要大量时间的操作保留的,而需要大量时间的操作通常会这样做,因为它们会执行与数据库对话、访问文件或调用web服务等操作。试图测试你的代码的人现在面临着这样一个难题: “当我从我的单元测试中运行这段代码时,它要么表现正常,要么它会尝试与某个地方的数据库进行对话,我不确定是哪一个。
这种类型的东西在单元测试中表现不佳。因此,如果你试图测试使用延迟加载结构的代码,那么你很有可能会把头撞到你的桌子上。
外部访问现场
我要提到的最后一个困难来源是我认为的对应用程序内存空间之外的东西的 “原位” 访问。这可能意味着从文件读取,与数据库交谈,从驱动程序获取输入等。在具有良好可测试性的应用程序中,这些类型的活动被本地化到应用程序边缘的特定位置,以最大程度地减少对它们的依赖。
然而,在难以测试的代码库中,它们似乎只是在需要的地方发生。需要知道invoiceparer类中的配置设置是什么?好吧,只是从配置文件中读取它。
虽然这看起来可能是无害的,但你已经谋杀了所讨论方法的可测试性。在你把它放进去之前,测试一下逻辑是没有问题的。但现在,你的单元测试套件 (和服务器上的一个) 依赖于磁盘上某个特定位置存在的一些文件,以便有任何机会通过。现在,您将得到一个测试,该测试始终失败或偶尔失败,这两种情况都会造成挫败感,并导致删除单元测试和放弃工作。
让它容易对自己
开始单元测试很难。显然,这意味着要找出一项新技能,但是很少有人意识到这往往意味着开始对您的代码进行不同的推理。你的盘子里已经有很多了,所以当你让自己的生活变得艰难时,理解这一点很重要。而且,如果你在做我提到的事情,你会让自己的生活变得艰难。
这并不意味着你必须改变所有的实践或在你的代码库中进行大规模的重新工作。面对真正的交付压力,这是不合理的。这只是意味着你应该选择你的战斗,特别是在开始的时候。测试实际上是可测试的东西,你会节省自己相当大的胃灼热。
想要构建具有高性能控件的桌面、移动或web应用程序?下载终极免费试用版今天或联系我们看看它能为你做什么。