我对那些相信TDD的价值被夸大的人的回答

有一次,我与客户公司的开发人员谈论了软件。我应该理解,当对话者谈到我们作为软件开发人员的幸运时,谈话出现了错误:“我们诱使组织为看似简单的工作付钱给我们。”多少人可以编写代码并不重要,但我认为不应该像整个骗子一样谈论整个软件开发行业。

我没有专注于此,而是开始讨论。客户通常对测试新方法并改善其工作流程的想法持开放态度。但是-直到我提到通过测试进行的开发(TDD,即测试驱动的开发)。唯一的答案是以下短语:“ TDD值被夸大了”。



听到它不仅让我感到痛苦,而且使我意识到TDD是敏捷方法中的另一种方法,可能看起来像“城市传奇”。这就是让我写这篇材料的原因,在此我想向那些怀疑TDD的价值的人发出呼吁。

什么是TDD?


让我首先定义TDD。这是一种软件开发策略,在实施该策略的过程中,创建程序时,它们会完全受编写测试的指导。实际上,从这种方法的名称可以理解。这个想法是肯特·贝克(Kent Beck)在2003年撰写的《通过测试进行开发》中提出的。使用TDD涉及以下三个步骤:

  1. 编写一小部分功能失败的测试。
  2. 该功能的实现导致测试成功通过。
  3. 重构新旧代码,以使其保持结构良好且可读的状态。

此过程也称为“红色,绿色,重构”循环。

这是使用TDD的简单示例。在这里,我们要用Python编写一种方法,该方法旨在根据C勾股定理来计算变量

这是测试代码(文件test.py):

#!/usr/bin/env python

"""
test.py
"""

import unittest

from main import *

class TestDrivenDevelopment(unittest.TestCase):

        def test_pythagorean_theorem(self):
                a, b = 3, 4
                self.assertEqual(calculate_side_c(a, b), 5)

if __name__ == '__main__':
    unittest.main()

这是方法代码(main.py),到目前为止,什么都没有发生:

#!/usr/bin/env python

"""
main.py
"""

def calculate_side_c(side_a, side_b):
    return True

现在尝试运行测试。


测试失败(TDD周期的“红色”部分),

重写代码,将内容main.py转换为以下形式:

#!/usr/bin/env python

"""
main.py
"""

def calculate_side_c(side_a, side_b):
    c_squared = (side_a * side_a) + (side_b * side_b)
    c = c_squared ** 0.5
    return c 

再次运行测试。


测试进行得很好(TDD周期的“绿色”部分),

现在我们main.py重构代码

#!/usr/bin/env python

"""
main.py
"""

def calculate_side_c(side_a, side_b):
    return ((side_a ** 2) + (side_b ** 2)) ** 0.5




自从开发手册通过测试发布以来,经过重构的代码(TDD周期的“重构”部分)已经成功测试了 17年。但是,即使在今天,即2020年,对于许多人来说,TDD的好处是否值得花费时间和精力来实施这种方法,这一问题仍然没有明确的答案。结果,许多开发人员甚至没有尝试通过测试进行开发。

为了支持TDD,我问开发人员为什么他们不使用这种方法。这是我最常听到的4个答案:

  1. 我们有一个质量检查部门。编写测试是他们的工作。
  2. 创建可能非常需要moki和stub的测试可能会花费大量时间和精力。
  3. TDD与传统开发相比没有优势。
  4. TDD很慢。

让我们分析这些想法。

我们有一个质量检查部门。编写测试是他们的工作


关于我为什么有人不使用TDD的问题的这个答案总是会让我发笑。当编写代码的人对此负责时,我大力支持“ 构建,运行的原则因此,当我看到开发人员的行为好像他们唯一的任务就是编写功能代码时,这使我感到困扰。

我深信开发人员有责任确保其代码具有以下品质:

  • 正确性-满足业务需求。
  • 清晰。
  • 可测试性。
  • 可扩展性。
  • 简单。

我认为,将编写测试的任务转移给质量检查部门不属于开发人员的职责范围。质量检查部门的专家还有其他重要事项。他们不应该将工作时间主要花费在使用“黑匣子”方法测试代码上。

创建可能非常需要moki和存根的测试可能会花费大量时间和精力


我理解那些这样说的人的感受。例如,您需要实现一个采用三个不同输入值的方法。每个对象都是一个对象,并且每个对象都没有可选属性。所有这些都需要通过检查该方法的不同选项来进行测试。为此,您需要配置stub和mokas,并编写大量其他代码。所有这些仅是测试一种方法。我已经遇到过类似情况。但是,我从另一个角度看待了这一切。

当我考虑减少编写测试的工作量和时间时,我想到了SOLID编程的原理和测试金字塔。从这里

开始使用SOLID 。在这里,我想谈谈以下事实:缩写SOLID由字母S表示,即唯一责任原则(单一责任原则)。这是根据哪个方法和类只能解决一个问题的想法。它们必须起作用,以使其活动不会影响系统的其余部分。

我了解到,企业软件中的大多数方法所执行的任务比找到传递给这些方法的多个数字的总和要难得多。但是不能否认这样一个事实,即使用旨在解决项目中一个问题的方法极大地促进了测试。

让我们谈谈测试金字塔。


测试金字塔(图像取自此处

测试金字塔通常用于查找项目中不同类型的测试的正确比率。

用户界面测试(UI测试)和服务测试(服务测试)需要大量时间来实施。因此,大部分测试应由单元测试(Unit Tests)表示,因为执行甚至大量的此类测试也要花费毫秒。当然,这有助于改善反馈循环,这是DevOps的主要目标之一。

执行单元测试以测试系统的一小部分还要求与系统其余部分的集成度要低得多。这使得编写这样的测试更加容易。TDD方法论是专为单元测试而设计的。

当然,说起来容易做起来难,要编写大多数测试,甚至是单元测试,您都需要付出一些努力。但是,如果编写单元测试似乎花费了很多精力,则可以通过问几个问题来测试自己:

  • 这是模块化测试,还是应将其视为更高级别的测试或服务测试?
  • 代码是否设计合理,每个类和方法是否符合唯一责任原则(代码是否紧密耦合)?

如果事实证明编写测试需要花费大量时间和精力,那么通常这表明可以并且可能需要重构代码。

TDD与传统开发相比没有优势


具有讽刺意味的是,TDD与传统开发相比没有优势这一事实主要是由那些甚至没有尝试过以TDD风格进行工作的程序员说的。但是,除非您尝试尝试,否则您将无法理解这是好是坏。甚至Coldplay乐队都知道“如果您不尝试,就永远不会知道”。

TDD具有两个主要优势。

TDD的第一个优势非常明显。如果在编写有效代码之前总是有人为此代码编写测试,那么按照定义,程序员总是可以使用自测代码。这是马丁·福勒(Martin Fowler)所写的内容:“如果您可以在代码库上运行一系列自动测试,那么您将拥有一个自测代码,并且在成功完成测试之后,您可以确保该代码没有重大缺陷。”

换句话说,使用TDD意味着代码可以完全按照程序员的需要工作。

这是一个奇妙的事实,因为它对任何不利更改的代码稳定性都给予了极大的信心。 TDD的使用使程序员可以立即发现代码库中的某些内容是否由于重构或扩展代码而恶化。更妙的是,它可以让您知道系统中是否有错误。

自测试代码使开发团队可以真正利用持续集成和代码部署系统的优势

TDD的第二个主要优点是,这种开发方法使程序员甚至在编写工作代码之前,就可以考虑要编写什么以及如何编写代码。

在编写代码之前先仔细考虑一下代码会导致这样一个事实,即开发人员确实沉浸在业务需求的本质中,并考虑了边界情况和实施适当机制时可能遇到的困难。结果,在编写工作代码时,精力就花在了您所需要的上。另外,TDD的使用导致开发人员根据其结构和体系结构计划未来的设备系统。如果从系统工作之初就计划并考虑到这些事情,这将严重提高其扩展和扩展能力。

TDD很慢


那些说TDD速度慢的开发人员通常是使用这种方法工作了一段时间的人,然后又回到编写在代码创建后执行的测试。通常,这是谈论某人为什么不使用TDD时最常提及的原因。

我也可以理解为什么程序员这么认为。在编写代码之前先思考一下代码需要花费时间。即使是在正常情况下可能尚未编写测试的方法,也要花时间为计划要实施的所有内容创建测试。寻找有效使用存根和小怪的方法有时也不是最快,最令人愉快的任务。

程序员的生产率和效率经常根据他们的工作速度和创建成品所花费的时间来评估,这使情况更加恶化。所有这些都促使程序员团队渴望尽快工作,并渴望适应紧凑的工作计划。

论文的四项指标评估的DevOps有效性的系统:

  1. 部署频率。
  2. 更改执行时间。
  3. 服务恢复时间。
  4. 故障频率随变化而变化。

我对指标“部署频率”和“失败频率随变化”特别感兴趣。

正如我所说,在使用TDD时,基于系统仅使用TDD构建的假设,开发团队在对系统进行更改后将立即知道此更改是否不影响任何产品的性能。来自系统的各个部分。此外,使用TDD可以提高发现错误的速度。测试旨在测试非常小的代码片段,这一事实使您可以非常迅速地找出发生故障的项目位置。这导致以下事实:在已部署的系统更新版本中不太可能检测到错误,从而降低了“更改期间的失败率”的发生率。

开发团队对项目的可持续性及其正确工作具有高度的信心,这意味着开发团队可以在测试成功完成后决定是否部署项目。有没有人按需完成项目部署?

凤凰项目涵盖四种类型的工作。其中之一是意外工作。当开发人员需要将精力放在正在做的事情上并做其他事情时,就必须完成此类工作。通常是对某种错误的更正。如果项目是根据自测试代码构建的,则可以最大程度地减少错误。反过来,这导致“意外工作”量的最小化。而且,在开发人员的生活中,这种“惊喜”越少,他的感觉就越好,他的工作效率也越高。

如果开发人员可以减少对生产错误的担心,则意味着他们可以将更多的时间用于开发产品的新有用功能。如果开发人员事先考虑了代码,应用了诸如SOLID之类的有用原理并不断进行重构,则可以最大程度地减少“技术债务”的数量。当团队对代码的功能和质量充满信心时,它可以从字面上进行两次部署。所有这些都提高了工作速度。

摘要


像任何其他工具一样,就像其他开发方法一样,TDD方法论乍看起来似乎很尴尬。在程序员正确掌握此方法之前,对他们来说似乎有点慢。我能说什么 引用赫兹(Jez Humble)的话:“如果某事引起不适,请多做几次,然后您就可以处理。”

现在,我建议您练习TDD方法,直到TDD不再引起您不适为止:)

亲爱的读者!您在项目上工作时是否使用TDD方法?

Source: https://habr.com/ru/post/undefined/


All Articles