如何编写出不可测试的代码

发布于:

翻译

这份指南列举了一些可以帮助你写出不可测试代码的原理或方法。或者,避免这些技术点可以帮助你写出可测试的代码。

  • 创建你自己的依赖

不要传入对象实例,在方法中间执行 new 操作来实例化对象。这是一个邪恶的手段,因为无论何时,你在代码块中实例化一个对象然后使用它,任何想要测试这块代码的人都被强制使用这个你实例化的具体对象。他们不能注入一个假的或者其他 mock 对象来简化这个行为或 对你做了什么做出断言。

  • 重型构造器

创建做很多工作的构造方法,在构造函数中执行的工作越多,在测试夹具中创建对象就越困难。另外,如果你的构造器能够构建出其他比较难构建的对象,那就更好了!你应该期望每个构造方法的依赖传递性都是巨大的,巨大到难以测试。

  • 依赖于特定的类

和特定的类做绑定 - 在任何可能的地方都避免使用接口。(接口可以让别人替换你使用的特定类为他们自己创建的类,他们的类可以实现接口或抽象类里的约定,想要写出好测试的家伙可能会这样做来测试你的代码 - 不要让这些家伙得逞!)

  • 条件障碍

写冗长的 if 分支和 switch 声明,并总是对此感觉良好。这样做可以增加测试时需要覆盖的可能执行路径。这个条件圈越复杂,测试越困难!当有人建议使用多态替代条件时,嘲笑他们对测试的体贴。是条件分支即深又宽:如果你不能保持至少5个条件深度,你就是在喂养可测试代码给那些 TDD 狂热者。

  • 依赖大型上下文对象

传递巨大的上下文对象(或难以构造的小型对象),这将降低方法的清晰度。myMethod(Context ctx) 不如 myMethod(User user, Label lable) 清晰。为了进行测试,需要创建,填充和传递上下文对象。

  • 使用静态

静态,静态无处不在!它们在可测试性方面创造了巨大的危机。他们不能被模拟,同时是一个可以任意获取到的方法, OO (面向对象) 狂热者会说静态方法是其中一个参数应该拥有该方法的标志。但你是3v1L!

  • 使用更多的静态

静态是一个非常强大的工具,可以使被感染 TDD 的工程师们屈服。静态方法不能被子类重写(有时编写一个子类重写方法是一个测试技术点)。当你使用静态方法时,它们不能被 mock 库模拟(掣肘亲测试工程师的另一个好办法)。

  • 使用全局标志

为什么要明确调用一个方法? 就像 L Ron Hubbard 那样,使用 “mind over matter” (精神高于物质) 格言来在你的一段代码中设置一个标记,以便在你的应用程序的完全不同的部分产生影响(当你在不同的线程中同时这样做时,它会更有趣!)。测试人员会疯狂地试图弄清楚为什么突然之间有一个条件是 true,突然又变为 false

  • 到处使用单实例

为什么要显而易见的将依赖传递进来而不是使用单实例来隐藏这个依赖?建立一个需要单实例的测试时很难的,当所有测试都依赖于彼此的状态时,TDD 工程师们将会被刺痛到怀疑人生。

  • 要防守 - 他们为了获取你的代码出击!

对传入方法、构造器和中间方法的参数状态采取防卫断言。如果有人能传入一个 null,那就说明你已经放松警惕。你看,有一些测试怪胎在那想要实例化你的对象,或者在测试中调用你的方法并传入 null!要积极防止这种情况发生: 用铁拳捍卫你的代码!(同时铭记:如果获取到了你的代码,那他们就不是偏执狂了)

  • 在任何可能的地方使用基本类型

每次需要一个值时,与其使用 曲奇饼对象,不如将基本类型传入后再做你需要的所有解析。基本类型需要人们解析和传递才能得到想要的数据,这使工作变得更艰难 – 而对象是可以伪造的(喘口气)同时也可以为空,并且可以封装状态(谁才会想要这样做?)。

  • 查找你想要找的任何东西

通过随意的查找你可以确保你的对象处于霸主地位,它知道其他任何东西在哪。这样可以让测试开发困难一些,因为他必须模仿环境,以便你的代码能够掌握它所需要的东西。不要害怕你需要获取多少对象,查找的越多,测试越难伪造这些对象。如果你是 InvoiceTaxCalculator 类,随意做这样的调用 invoiceTaxCalculator.getUser().getDbManager().getCaRateTables().getSalesTaxRate()。当那些测试维尼告诉你关于 “依赖注入“、”得墨忒耳定律” 或者不要查找别的东西时,请捂住你的耳朵。

  • 使用静态初始化

加载类时,做尽可能多地工作。当想要写测试的疯子们发现加载你的类会导致如网络或文件访问这些令人讨厌的东西时会变得抓狂。

  • 将外部系统依赖和逻辑代码直接挂钩(耦合)

如果你的产品依赖于外部系统如数据库、文件系统或网络,请确保你的业务逻辑编码时引用了尽可能多的这些底层系统实现细节。这样可以阻止其他人不按照你的本意使用你的代码,(如运行2毫秒的小测试而不是5分钟)。

  • 混合对象生命周期

长生命周期的对象引用短生命周期的对象。这使其他人感到困惑,因为长寿命对象在它不再有效或活着之后仍然引用它,这样做特别阴险,不仅是糟糕的设计,而且是邪恶的且难以测试。

  • 副作用是个好帮手

你最好的选择是在您的方法中执行大量副作用生成操作,对于设置器来说尤其如此。副作用越不明显越好,特殊和看似不合理的副作用对单元测试特别有用。使用未初始化或初始化为无效状态的成员字段可以再为副作用锦上添花,一旦你实现这一点,请确保在设置器方法调用时访问这些未经初始化的成员,来产生段错误(SEGV)或空指针(NPE)。为什么要这么做?干净的、可读的代码然后才是可以测试的代码,这就是为什么!副作用方法是专门写给那些智力弱者的,他们认为方法名程应该表明这个方法该干什么。

  • 创建实用工具类 Utility 和函数/方法

例如:你有一个要传递的 URL 为内容的 String(遵守尽可能使用基本类型的原则),创建另一个含有静态方法的类比如 isValidUrl(String url),不要让面向对象规则告诉你要将这个方法修改为一个 URL 对象。如果你的静态工具方法能够同时调用外部服务那就更好了!

  • 创建 Manager 和 Controller

到处使用这些 ManagerController 来干预其他对象的职责,不要理睬想将这些职责被移除到其他独立的对象的想法。看看这个你完全不知道要干什么的 SomeObjectManager 类。

  • 在对象中做复杂的创建工作

每当有人建议你使用工厂来实例化对象时,你要知道你比他们更聪明。你一定比他们更聪明,因为你的类可以有多个职责而且有上千行代码。

  • 对 if 和 switch 分支亮绿灯

不要对嵌套的 if 分支感到肮脏,这样写 “可读性更高”。面向对象思想的牛仔们想要用多态来串联对象,对这些面向对象的家伙说不。当你嵌套条件分支时,你唯一需要做的是从顶到底阅读代码。就像一个伟大的小说,一个简单的线性散文代码。使用 OO-overbard 范例,就像是一个恐怖的 choose-your-own-adventure 孩子的书,你需要经常在多个类、模式戏法和太多的复杂的概念中翻转。就只使用 if 就对了。

  • Utils, Utils, Utils!

代码异味?没门 - 代码香水!按你的期望将尽可能多的 utilhelper 类乱扔,这些东西是很有帮助的,而且当你把它们粘在某个地方,其他人也可以使用。这就是代码复用,同时对每个人都好,对吧?当心,面向对象规则会说这些方法逻辑数序某些对象,是这些对象的职责。算了吧,你要务实地破坏他们的意愿。毕竟你有一个可以推送的产品!

  • 每当你需要逃避某些事情时,请使用“重构”

“重构”是一个测试驱动、面向对象的家伙们喜欢用的词。因此,如果你想做一些有意义的事情,涉及新的功能,没有测试,只要告诉他们你是“重构”。这可以每次骗到他们。他们认为你需要在重构之前对所有内容进行测试,并且在这之前永远不应该添加新的功能。无视他们的喧哗,按照自己的方式做事吧!

  • final 方法

总是使用 final 类和方法。他们不能为了测试而重载。(-; 不过不要担心没将变量或值对象(除了 setter)写为 final- 让你的对象状态可以被任何人任何东西改写,保证状态毫无意义,这样做可以让事情变得太容易了。

  • 指定特定的类型

尽可能多的使用 instanceof,这可以使伪造对象变得头疼,这样可以告诉他们你掌控着被允许的对象。

参考

How to Write 3v1L, Untestable Code

Top 10 things which make your code hard to test