断言被认为有害

断言是单元测试中的首选检查机制。 但是,当应用于测试接口(特别是GUI)时,我认为它们是有毒的。 值得庆幸的是,还有一个很有前途的选择。

ĴUnit was a huge success, being the single most used library in all of Java. And JUnit brought with it the famous Assert.assert... statement. This mechanism is designed to only check one thing at a time in isolation. And when testing a single unit, this is the most sensible approach: we want to ignore as much volatile context as possible. And we want to focus on ideally checking only a single aspect of only the unit under test. This creates maximally durable tests. If a test depends only on a single aspect of the code, then it only needs to change if that aspect changes. Assertions are a natural and intuitive mechanism to achieve that. Being “inside of the software during the test, where practically all of the internals are exposed in one way or another, all else wouldn’t have made sense.

由于它的成功,JUnit被认为是测试自动化的最新技术,这是正确的。 这样,它的机制也被应用于非单元测试,即它们被应用于接口测试(例如GUI测试)。 从直觉上讲,这是有道理的。 因为随着各个功能朝接口堆叠,接口变得非常不稳定。 仅测试系统的各个方面似乎可以解决此问题。

除非不是。 尽管仍然可行,但已经很难在单位级别实现该等级的分离。 在不可避免集成的接口级别,这是绝对不可能的。 实践证明了这一点。 著名的测试金字塔形状的原因之一是,该级别的测试往往会经常断裂,并且需要大量维护工作。

A practical example

Unit test

想象一下,您想测试代码的单个方面–计算单个用户曾经购买的商品数量。 在单元级别,您只需要一个用户对象以及一些关联的项目或交易。 根据系统的复杂性,您可以根据需要创建或模拟这些对象。 然后,您可以仅测试计算项目的代码。

GUI test

但是,在GUI级别上,首先需要使用现有用户登录系统。 然后,您需要导航到显示相关信息的特定页面。 因此,即使您仅创建一个断言要检查项目数量,您的代码仍然取决于工作的持久层,预定义状态(例如,用户现有和正确的项目数量),用户登录的能力以及导航。 此测试隔离得如何?

In an integrated test, it is basically impossible to ignore context. Involuntarily, we always depend on numerous aspects that have nothing to do with what we want to test. We suffer from the multiplication of effects. This is the reason for the famous test-pyramid. However, if we cannot ignore context, maybe we should embrace it instead?

Embrace Context

想象一下,我们可以以某种方式减轻效果的多重性。 然后,我们可以检查系统的完整状态,而不是各个方面。 我们可以一次检查所有内容!

因此,由于接口易碎,我们现在希望包含更多上下文,从而使我们的测试更加脆弱? 因为现在测试不再依赖于单个方面,而是一次依赖于所有内容? 谁想要那个? 好吧...每个想要​​知道界面是否更改的人。 如果您考虑过这一点,那么同样的问题也适用于版本控制。 版本控制系统是一个系统,每次您进行更改时,任何东西在任何文件, 你必须手动地批准该更改。 多么努力! 真是浪费时间! 除了那个不使用一个是一个非常糟糕的主意。

Automated test

因为人们无时无刻不在改变事情。 并且他们无意改变了系统的行为。 这就是为什么我们首先进行回归测试的原因。 但是有时候我们真的通缉改变行为。 然后,您必须更新回归测试。 实际上,回归测试非常类似于版本控制。

秉持软件一直在变化的思想,断言只是检测单个此类变化的一种手段。 因此,写断言就像将变更列入黑名单。 替代方法是一次检查所有内容,然后永久忽略单个更改-有效地将它们列入白名单。

Whitelisting vs blacklisting

When creating a firewall configuration, which approach would you rather choose? Blacklisting (i.e. “closing”) individual ports or whitelisting (i.e. “opening”) individual ports? Likewise with testing ... do you want to detect a change and later recognise that it isn’t problematic, or would you rather ignore all changes except the ones for which you manually created checks? Google introduced whitelist testing, because they didn’t want to miss the dancing pony on the screen again. Whitelisting means to err on the side of caution.

Pixel-diff tools

Of course I am not the first one to come up with that idea. In his book Working Effectively with Legacy Code, Michael Feathers called this approach characterization testing, others call it Golden Master testing. Today, there are two possibilities: pixel-based and text-based comparison. Because pixel-based comparison (often called visual regression testing) is easy to implement, there are many tools. For text-based comparison, there are essentially two specific testing tools: ApprovalTests and TextTest. But both pixel-based and text-based approaches suffer from the multiplication of effects.

Multiplication of Effects

在GUI级别上,很多事情是相互依赖的,因为隔离实际上是不可能的。 假设您天真地将自动化测试编写为一系列操作。 然后,如果有人更改了导航或登录屏幕,则此更改很可能会影响每个测试。 这样,测试的隐式或显式依赖性可能导致单个更改的效果成倍增加。

How can we contain that multiplication of effects? One possibility is to create an additional layer of abstraction, as is being done by single page objects or object maps. But, in order to later reap the fruits in the form of reduced efforts if the anticipated change happens, this requires manual effort in advance. According to YAGNI, implementing that abstraction “just in case is actually a bad thing to do.

我们还有什么其他可能性来遏制效应的繁衍呢? 在编程中进行重构时,我们碰巧处于相同的情况。 一种方法可能在数十个甚至数百个地方被调用。 因此,当重命名单个方法时(请仅在内部未公开的API中执行此操作),我们还需要更改调用该方法的每个位置。 在某些情况下,我们可以从抽象语法树中得出这些位置。 对于其他情况(属性文件,文档等),我们必须依靠基于文本的搜索和替换。 如果我们忘记或忽略了某些内容,通常仅在某些情况下会显示这种情况-通常是在执行软件时。 但是对于测试,这是不同的。 因为按照定义,测试已经在执行该软件。 因此,我们可以看到发生变化的所有地方(即测试失败)。 现在,我们只需要一种“大量应用相似的更改”的机制。

有两种不同类型的更改:布局差异和流量差异。

Differences in Layout

例如,如果现在将登录按钮称为“登录”,具有不同的内部名称,XPath或xy坐标,则这是布局上的差异。 布局差异相对容易用对象图解决。

但是,令人惊讶的是,如果我们有更多的上下文,布局上的差异也相对容易解决。 如果我们知道整个拼图而不是单个拼图,我们可以创建一对一的作业。 这使得对象识别非常可靠。

One-on-one assignments

想象一下,我们有一个表单,其中添加了一些元素。 并且我们想要识别“接受按钮以提交表单”。 如果按钮的所有内容都发生了变化,我们仍然可以根据其余按钮的一对一分配来识别它没用过UI组件。

批量应用这些更改也很容易。 我们可以应用所有类似的更改。 例如。 将“接受”保存的更改的所有实例组合为一个更改,该更改仅需审核一次。

有了如此强大的机制,冗余突然不再成为问题。 因此,我们可以突然收集UI组件的许多属性,甚至可以识别它们更多强大的。

Redundant UI information

这样我们就可以收集XPath,名称,标签和像素坐标。 如果某些值发生变化,我们仍然有剩余的值来标识元素。 大量应用使它仍然易于维护。

Differences in Flow

有时,软件的用例或内部流程会发生变化。 这些更改可能很小(例如,如果需要执行其他步骤-填写验证码或重置密码)。 有时,这些都是重大更改–工作流完全更改。 在后一种情况下,重写测试可能更容易。 但是这种情况很少发生。 通常,我们只需要稍微调整一下测试即可。

流程差异无法通过对象映射解决。 相反,我们需要其他形式的抽象:将循环流提取为“函数或过程”并进行重用。 这可以通过页面对象来实现,但是需要人工和正确的抽象。

相反,我提出了另一种方法:被动更新。 那是什么意思 传统上,我们必须主动识别测试中所有特定情况的发生并手动更新它们。 因此,如果需要调整登录过程,则必须找到测试登录的所有实例。然后,我们需要相应地手动更改它们。 这是活动更新。

被动更新是为了指定我们需要更新的情况以及有关如何更新的规则。 因此,我们没有找到所有登录尝试,而是指定了这种情况:登录页面中充满了凭据,并且显示了验证码。 现在,我们添加了一条有关如何更新在这种情况下发现自己的测试脚本的规则-填充验证码。 我们通过删除或插入单个动作或其组合来做到这一点。 然后,在执行测试后,将被动应用该更新。 这意味着我们实质上是在反过来提取程序。

这种方法具有多种优点:

  1. 通过保留效果的乘积,此方法需要更少的努力更新您的测试。通过创建详细规则,它可以更细微的在更新中。它仅影响指定情况下的测试执行期间–无需分析静态测试脚本并解释情况是在运行时应用于该测试还是手动调试它。可以定义适用于各种情况的一般规则。 因此,我们可以有一条规则说:只要测试发现自己带有模式对话框且只有一个选项(例如“确定”),请单击该选项并继续测试。 这使我们的测试很多更强大应对不可预见的变化。

能够解决效应的多重性,使我们能够接受测试的整个环境,而不是试图忽略它。 这种方法有望使测试自动化和结果检查更加强大和强大。

rediff

我们已经为Java Swing实现了该方法。 现在,我们想创建一个开源工具来促进广泛采用。 我们非常感谢您的任何支持-给我们反馈,支持我们或传播信息。

We have a Kickstarter campaign, that allows you to fund us. Back us to vote which technology gets implemented next. Or get an unbeatable price for premium. Or simply be the cool guy who can claim: I backed them in 2017.

谢谢!

from: https://dev.to//roesslerj/assertions-considered-harmful-6pf

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值