6

测试 UI 逻辑,请使用“正门优先原则”!

 2 years ago
source link: https://www.continuousdelivery20.com/blog/tott-testing-ui-logic/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
测试 UI 逻辑,请使用“正门优先原则”!

乔梁 | 2021-04-09

针对 UI 逻辑的自动化单元测试,当然是使用“正门优先原则” ,即:尽可能使用 Public 的真实实现代码。

一个对象( Object )会有几种接口。例如,它会有一个为大多数客户提供服务的 Public 接口,可能还有一个 private 接口,只少数亲密用户使用。另外,很多对象可能还会有一个“出口(outgoing interface)”,由它们自己所依赖的其它对象的接口所需要的部分组成的(如下图所示)。 我们测试中所使用的接口类型,对测试的健壮性是有影响的。那些使用“后门操作”来设置夹具,或验证预期结果的测试,有可能会导致过度耦合,这些测试也需要更频繁的维护。过度使用行为验证( Behavior Verification )和模拟对象( Mock Objects ) 会导致过度具象的软件( Overspecified Software )和更加脆弱的测试,这就可能阻碍开发人员进行代码重构的意愿度。 如果所有类型的测试选项都有相同的有效性时,我们应该使用往返测试( round trip tests )来对被测系统( SUT )进行测试。因此,我们要通过 Public 接口去测试它,并使用状态验证( State Verification )的方式以确认其行为是否正确。 如果这还不足以准确描述预期的行为,还可以让这个测试进行跨层测试,并使用行为验证( Behavior Verification )来验证 SUT 对其所依赖的组件( DOCs)的调用。如果我们不得不用更快的测试替身对象( Test Double)来替代一个速度很慢或者当前不可用的被依赖对象,那么, Fake Object 是更好的选择,因为它在测试中只封装了更少的假设条件,即唯一的假设条件就是:被测系统的确需要那个被 Fake Object 所替代的真实组件。

让我们看一个例子吧。你在某个电商网站上买鞋“ gShoe ”,如下图所示。

然而,点击了购买按钮 “ Buy ” 以后,什么也没有发生!!!通过查看 HTML 代码,你发现了问题所在:

<button disabled=”true” click=”$handleBuyClick(data)”>Buy</button>

由于按钮“ Buy ”已经被 “ disabled ”了,所以用户无法买鞋。 我们的确写了针对 handleBuyClick的单元测试(如下所示)。而且,虽然这个单元测试的确可以成功通过,但用户界面还是遇到了这个缺陷:

it('submits purchase request', () => {
  controller = new PurchasePage();
  // Call the method that handles the "Buy" button click
  controller.handleBuyClick(data);
  expect(service).toHaveBeenCalledWith(expectedData);
});

在上面的例子中,测试并没有发现这个缺陷,因为它没有使用 UI 上的元素,而是直接调用了购买按钮“ Buy ”上面的 handler为了有效,UI 逻辑测试应该尽可能像浏览器那样以方式与界面上的组件交互,这样就可以测试最终用户的真正行为。针对UI组件编写对它的测试时,应该真实模拟用户交互,而不是直接调用界面元素的 handler (例如, 将商品添加到购物车,或者单击“购买”按钮,或者验证某个元素在页面上是否可见)。这样才会让测试更有综合性( more comprehensive )。

对“ Buy ”按钮的测试应该通过与 HTML 元素交互来检查完整的 UI 组件, 这样就能捕获这个问题了,如下所示:

it('submits purchase request', () => {
  // Renders the page with the “Buy” button and its associated code.
  render(PurchasePage);
  // Tries to click the button, fails the test, and catches the bug!
  buttonWithText('Buy').dispatchEvent(new Event(‘click’));
  expect(service).toHaveBeenCalledWith(expectedData);
});

为什么测试应该以这种方式写呢? 与端到端( End-to-End )测试不同,针对单个 UI组件的测试并不需要依赖后台服务,或者依赖完整渲染的 App。相反,这些测试应该运行于同一个自包含的容器中( same self-contained environment),并且它的运行时间也应该与直接测试其下面的event handler相差无几。所以,可以把 UI 看做了一个 public 的API,而把下面的逻辑看做是它的一个具体实现。这就是“使用正门优先(Use the Front Door First)” 原则,这样会得到更好的覆盖率。


原文作者: Carlos Israel Ortiz García

原文链接:Testing on the Toilet: Testing UI Logic? Follow the User!

发表时间: Oct 26, 2020


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK