测试 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