测试驱动开发 测试前移_测试驱动开发的最佳实践

测试驱动开发 测试前移

在我以前的测试驱动开发(TDD)和突变测试系列中 ,我演示了在构建解决方案时依靠示例的好处。 这就引出一个问题:“依靠例子”是什么意思?

在该系列中,我描述了构建解决方案以确定白天还是晚上时的期望之一。 我提供了一个示例,其中列出了我认为属于白天类别的特定时段。 我创建了一个名为dayHourDateTime变量,并给它指定了2019年8月8日,7小时0分钟0秒的特定值。

我的逻辑(或推理方式)是:“当通知系统时间是2019年8月8日上午7点时,我希望系统将执行必要的计算并返回值Daylight 。”

有了这样一个特定的示例,创建单元测试( Given7amReturnDaylight )非常容易。 然后,我运行测试并观察我的单元测试失败,这为我提供了修复此早期失败的机会。

迭代就是解决方案

TDD(以及代理,敏捷性)的一个非常重要的方面是,除非您进行迭代,否则不可能获得可接受的解决方案。 TDD是基于无休止的迭代过程的专业学科。 重要的是要注意,它要求每次迭代必须以微故障开始。 这种微故障只有一个目的:立即征求反馈。 即时的反馈确保我们能够Swift缩小寻求解决方案与获得解决方案之间的差距。

冲洗,重复,直到我们完全消除差距并提供完全符合期望的解决方案为止(但请记住,期望也必须是微观期望)。

为什么微?

这种方法通常感觉很雄心勃勃。 在TDD(和敏捷)中,最好选择一个微小的,几乎是微不足道的挑战,然后通过先失败然后进行迭代直到我们解决这个微不足道的挑战来进行TDD歌舞。 习惯了更丰富,更精通的工程和解决问题的人往往会觉得这样的练习低于他们的能力水平。

敏捷哲学的基石之一是将问题空间缩小到多个最小的可能表面积。 正如罗伯特·C·马丁(Robert C. Martin)所说:

“敏捷是关于小型编程团队做小事情的小问题的一个小主意”

但是,如何制造一系列令人印象深刻的行人,微小的,几乎微不足道的微型胜利,才能使我们达到大规模的解决方案呢?

在这里,复杂而精巧的系统思维开始发挥作用。 在构建系统时,总是存在以可怕的“整体”结束的风险。 整体是基于紧密耦合原理构建的系统。 整体的任何部分都高度依赖于同一整体的许多其他部分。 这种布置使得整料非常脆弱,不可靠并且难以操作,维护,故障排除和固定。

避免此陷阱的唯一方法是最小化或更好地完全消除耦合。 与其投入大量的精力来构建将要组装到系统中的精致零件,不如花些谦虚的步伐来构建微型零件。 这些微型零件本身仅具有很小的能力,并且由于这种布置,将不依赖于其他组件。 这将最小化甚至消除任何耦合。

构建有用的,精致的系统所需的最终结果是由一组通用的,完全独立的组件组成。 每个组件越通用,结果系统将越强大,更具弹性和灵活性。 同样,拥有通用组件的集合使它们能够通过重新配置这些组件而重新用于构建全新的系统。

考虑由乐高积木制成的玩具城堡。 如果我们从城堡中挑出几乎所有街区并单独进行检查,那么我们将无法在该街区找到任何东西来指定它是用于建造城堡的乐高积木。 积木本身具有足够的通用性,因此适合建造其他设备,例如玩具车,玩具飞机,玩具船等。这就是拥有通用组件的力量。

TDD是提供通用,独立和自主组件的可靠实践,可以安全地组装大型复杂系统。 与敏捷一样,TDD专注于微活动。 而且由于敏捷基于称为“整个团队”的基本原则,因此在说明业务示例时,此处说明的谦虚方法也很重要。 如果用于构建组件的示例不适当,则将很难满足期望。 因此,期望必须谦虚,这使得得到的例子同样谦虚。

例如,如果整个团队的成员(请求者)为开发人员提供了期望,并且示例显示为:

“在处理订单时,请确保对忠实客户的订单或超过一定货币价值的订单或两者都给予适当的折扣。”

开发人员应认识到此示例过于宏大。 这不是一个卑鄙的期望。 如果可以的话,它还不够细微。 开发人员在编写示例时应始终努力引导请求者更加具体和微观。 矛盾的是,示例越具体,所得到的解决方案就越通用。

一个更好,更有效的期望和示例将是:

“订单金额大于$ 100.00的折扣为$ 18.00。”

要么:

“已经下三笔订单的客户所下的订单大于$ 100.00的折扣为$ 25.00。”

这样的微型示例可以轻松地将其转变为自动化的微型期望(阅读:单元测试)。 这样的期望会使我们失败,然后我们会反复努力,直到我们交付解决方案为止。这个解决方案是一个强大的通用组件,知道如何根据整个团队提供的微观示例来计算折扣。

编写质量单元测试

仅仅编写单元测试而不关心它们的质量是一个愚蠢的事情。 粗暴地编写单元测试将导致code肿,紧密耦合的代码。 这样的代码易碎,难以推理,而且通常几乎无法修复。

我们需要为编写质量单元测试制定一些基本规则。 这些基本规则将帮助我们在构建健壮,可靠的解决方案方面Swift取得进展。 最简单的方法是以首字母缩略词FIRST的形式引入助记符,它表示单元测试必须是:

  • F =快速
  • =独立
  • R =可重复
  • S =自验证
  • T =彻底

快速

由于单元测试描述了一个微型示例,因此应该期望从实现的代码中进行非常简单的处理。 这意味着每个单元测试都应该非常快地运行。

独立

由于单元测试描述了一个微型示例,因此应该描述一个非常简单的过程,该过程不依赖于任何其他单元测试。

可重复的

由于单元测试不依赖于任何其他单元测试,因此它必须是完全可重复的。 这意味着每次运行某个单元测试时,它都会产生与上一次运行相同的结果。 单元测试的运行次数和运行顺序都不会影响预期的输出。

自我验证

运行单元测试时,测试结果应立即可见。 不应期望开发人员接触其他信息源,以了解其单元测试是失败还是通过。

彻底

单元测试应描述微型示例中定义的所有期望。

结构良好的单元测试

单元测试是代码。 与任何其他代码一样,单元测试也需要结构良好。 提供草率的,混乱的单元测试是不可接受的。 适用于管理干净的实现代码的规则的所有原理均以相同的力应用于单元测试。

一种久经考验且久经考验的方法,用于编写可靠的质量代码,是基于称为SOLID的纯净代码原则。 该首字母缩写词可以帮助我们记住五个非常重要的原则:

  • S =单一责任原则
  • O =开闭原理
  • L = Liskov替代原理
  • I =接口隔离原理
  • D =依赖反转原理

单一责任原则

每个组件必须仅负责执行一个操作。 这个模因说明了这一原理

Sign illustrating single-responsibility principle

抽化粪池的操作必须与注入游泳池分开进行。

应用于单元测试时,该原理可确保每个单元测试都可以验证一个(也只有一个)预期。 从技术角度来看,这意味着每个单元测试必须有一个且只有一个Assert语句。

开闭原则

该原则指出,组件应为扩展打开,但应为任何修改关闭。

Open-closed principle

应用于单元测试时,该原理确保我们不会在该单元测试中对现有的单元测试进行任何更改。 相反,我们必须编写一个全新的单元测试来实现更改。

里斯科夫替代原则

该原理为确定哪种抽​​象级别适合该解决方案提供了指导。

Liskov substitution principle

应用于单元测试时,该原理指导我们避免与依赖于底层计算环境(例如数据库,磁盘,网络等)的依赖项紧密耦合。

接口隔离原理

这个原则提醒我们不要夸大API。 当子系统需要协作来完成任务时,它们应该通过接口进行通信。 但是,这些接口一定不要过分膨胀。 如果需要一项新功能,请不要将其添加到已定义的接口中; 而是设计一个全新的界面。

Interface segregation principle

应用于单元测试时,消除界面上的膨胀可帮助我们制定更具体的单元测试,从而产生更多通用的组件。

依赖倒置原则

该原则指出,我们应该控制我们的依赖关系,而不是控制我们的依赖关系。 如果需要使用另一个组件的服务,而不是负责实例化我们正在构建的组件中的该组件,则必须将其注入到我们的组件中。

Dependency inversion principle

应用于单元测试时,该原理有助于将意图与实现分开。 我们必须努力仅注入那些已经足够抽象的依赖项。 该方法对于确保单元测试不与集成测试混在一起很重要。

测试测试

最后,即使我们设法制作出符合FIRST原则的结构良好的单元测试,也不能保证我们提供了可靠的解决方案。 TDD最佳实践在构建组件/服务时依赖于事件的正确顺序; 我们总是且总是希望提供对我们期望的描述(在微型示例中提供)。 只有在单元测试中描述了这些期望之后,我们才能继续编写实现代码。 但是,在编写实现代码时,经常会发生两种不良的副作用:

  1. 已实现的代码使单元测试能够通过,但是使用不必要的复杂逻辑以复杂的方式编写它们
  2. 在编写单元测试之后,已实施的代码会被标记

在第一种情况下,即使所有单元测试均通过,突变测试仍会发现某些突变体仍然存活。 正如我在“ 变异测试”示例中所解释的那样:从脆弱的TDD演变而来 ,这是非常不希望的情况,因为这意味着解决方案不必要地复杂,因此无法维护。

在第二种情况下,保证所有单元测试都可以通过,但是代码库中很大一部分可能由未在任何地方描述的已实现代码组成。 这意味着我们正在处理神秘的代码。 在最佳情况下,我们可以将该神秘代码视为沉木,然后安全地将其删除。 但是很可能,删除此未描述的,已实现的代码将导致严重的损坏。 而且这种破损表明我们的解决方案设计不良。

结论

TDD最佳实践源于久经考验的方法(称为极限编程) (简称XP)。 XP的基石之一是基于三个C

  1. 卡:一张小卡简要说明意图(例如,“查看客户请求”)。
  2. 对话:这张卡成为对话的门票。 整个团队聚在一起讨论“审查客户需求”。 那是什么意思? 我们是否有足够的信息/知识以这种增量方式交付“查看客户请求”功能? 如果没有,我们如何进一步分割这张卡?
  3. 具体的确认示例:包括插入的所有特定值(例如,具体名称,数字值,特定日期,以及与用例有关的其他信息),以及所有期望作为处理输出的值。

从此类微型示例开始,我们编写了单元测试。 我们观察单元测试失败,然后使它们通过。 同时,我们遵守并尊重最佳软件工程实践: FIRST原则, SOLID原则和突变测试准则(即杀死所有幸存的突变体)。

这样可以确保我们的组件和服务以内置的可靠质量交付。质量的衡量标准是什么? 简单—变更的成本 。 如果交付的代码更改成本很高,则质量很差。 高质量的代码结构如此之好,以至于更改既简单又便宜,并且不会招致任何更改管理风险。

翻译自: https://opensource.com/article/19/10/test-driven-development-best-practices

测试驱动开发 测试前移

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值