使用 Gremlins.js 进行猴子测试入门

Avatar of Alicia Sedlock
Alicia Sedlock

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 $200 的免费信用额度!

我们行业中有一个常见的谚语:“你永远无法预测用户拿到你的产品后会如何使用它。” 如果你曾经观看过利益相关者第一次使用网站或 Web 应用程序,你可能对此深有体会。我数不清有多少次我看到用户似乎忘记了如何在移动设备上使用网站,或者试图以一种让你觉得“但现实生活中没人会这样做!”的方式使用网站。

问题是,你永远无法真正知道用户在特定时刻会做什么。他们可能处于一种焦躁的心态,试图快速完成某件事,并且他们的点击或输入方式可能不像冷静、专注的用户那样。这恰好符合我们经常遇到的情况:为最佳情况设计和开发,而不是考虑边缘情况或“如果事情没有按照这个顺序完美地发生会怎么样?” 作为开发人员,我们倾向于在构建事物时认为一切都将被理解,用户是理性的,并且应该知道快速点击可能会导致奇怪的事情发生。它甚至会影响那些在没有完全关注应用程序的情况下可能意外点击或点击的用户——你曾在走路、说话并试图回复推文或电子邮件时意外点击了移动设备多少次?

构建工具来帮助我们测试不可预测的方面并非完全是新的。2012 年,Netflix 开源了他们的内部服务 Chaos Monkey,该服务“终止运行在生产环境中的虚拟机实例和容器。” 简单来说,它是一项在随机情况下拆卸服务器的服务,以确保整个系统在发生故障时不会突然崩溃。我们的开发社区也提醒我们,不仅要为“快乐路径”设计,还要考虑如何像测试服务器架构那样,在我们的界面中检测到不可预测的故障点?

如果一百只猴子在打字机上可以写出莎士比亚的作品,那么一只猴子应该可以发现我们界面中的错误和问题。

引入猴子

猴子测试是一种测试方法,它生成随机的用户输入——点击、滑动、输入——其唯一目的是查找应用程序中的问题或完全破坏应用程序。与单元测试和验收测试不同,在单元测试和验收测试中,你编写的是按特定顺序或特定条件发生的测试用例,最终会在你的界面测试中产生偏差。开发人员对猴子测试的执行方式控制较少,并且由于每次运行都是随机的,因此你永远不会只测试一种场景,而是测试无限种交互组合。

虽然这种类型的测试适用于大多数技术栈,但为 Web 构建的测试还没有完全成熟。例如,Android SDK 具有一个 UI Exerciser Monkey,它处理大多数界面级和系统级事件。随着 Web 开发人员开始更注重性能和压力测试,这些想法中的一些终于以 Gremlins.js 的形式进入 Web 世界,Gremlins.js 是一个由 Marmelab 团队编写的基于 JavaScript 的猴子测试库。

猴子、小精灵和其他有趣的生物

Gremlins.js 运行的控制程度由你决定。它在初始设置方面的耗时非常短。

有三种方法可以开始使用 Gremlins.js。

独立库

将库集成到你的网站中最简单的方法是将库直接包含到你的网站中。这会将 gremlins 放入你项目的全局命名空间中,这意味着你可以在项目的任何地方访问它。

<script src="path/to/gremlins.min.js"></script>

<script type="javascript">
  // You can also run this in any file now!
  gremlins.createHorde().unleash();
</script>

Require.js 模块

如果你在项目中使用 Require.js,你可以以非全局、按需的方式导入 Gremlins.js。

require.config({
  paths: {
    gremlins: 'scripts/libraries/gremlins.min'
  }
});

require(['gremlins'], function(gremlins) {
  gremlins.createHorde().unleash();
});

书签

如果你更喜欢以即时的方式进行猴子测试,甚至有一个书签,它允许你一键测试你所在的页面。你可以从 安装说明 中获取书签。

乱搞

如果你选择直接包含库或通过 Require 导入库,现在你就可以开始在自己的项目中玩 Gremlins 了!在我们的安装示例中,我们调用 gremlins.createHorde().unleash()——那么它在做什么呢?

gremlins           // Yo, Gremlins
  .createHorde()   // Create me a default horde
  .unleash();      // Then immediately release them

查看 Pen Gremlins.js 开箱即用 由 Alicia Sedlock (@aliciasedlock) 在 CodePen 上创建。

默认的 horde 包含所有五种可用的随机生成的 user interaction 类型,也称为“小精灵物种”,包括

  • formFillerGremlin 用数据填充输入框,点击复选框/单选按钮,并与其他标准表单元素交互
  • clickerGremlin 点击可见文档中的任何位置
  • toucherGremlin 点击可见文档中的任何位置
  • scrollerGremlin 滚动视窗
  • typerGremlin 在模拟键盘上触发随机输入

触发后,小精灵会在屏幕上留下一个视觉指示,以表示执行的操作。它们还会留下一个操作日志,可以在开发者控制台中找到,以及与该物种相关的任何其他数据。它们看起来类似于下面的示例。

gremlin formFiller input 5 in <input type=​"number" name=​"age">​
gremlin formFiller input [email protected] in <input type=​"email" name=​"email">​
gremlin clicker    click at 1219 301
gremlin scroller   scroll to 100 25

默认情况下,小精灵将在 10 毫秒的间隔内随机触发,总共触发 1000 次。

除了喜欢惹麻烦的“坏”小精灵之外,我们还可以使用有帮助的小精灵,称为魔怪。它们不会像小精灵那样干扰我们的应用程序,而是主要报告我们的应用程序的运行状况,例如记录当前帧速率。如果帧速率低于 10,魔怪将抛出错误。

mogwai  fps  12.67
mogwai  fps  23.56
err > mogwai  fps  7.54 < err
mogwai  fps  15.76

小精灵和魔怪日志的组合提供了测试过程中发生的所有事情的清晰图片。

无需进行任何定制,Gremlins.js 就为我们提供了非常强大的测试。

高级乱搞

如果在使用默认配置后,你的需求没有得到满足,那么有很多方法可以自定义运行方式。例如,你可能想要一次只关注页面上的特定组件,而不是始终测试整个页面。

虽然我们无法对所有种类的小精灵进行范围限定,但我们可以将 toucherclickerformFiller 限定在页面的特定区域。具体来说,我们要求小精灵检查它尝试定位的元素的父级。如果该元素在我们定义的范围内,小精灵将继续执行操作。否则,小精灵将重新尝试查找要与之交互的元素。我们还可以告诉小精灵在放弃操作之前,我们希望它们尝试执行操作的次数,可以使用 maxNbTries 来指定。

gremlins.species.clicker().canClick(function(element) {
    return $(element).parents('.my-component').length;
    /**
       Is the element this gremlin attempted to click
       within our wanted scope? Let it proceed by
       returning true! Otherwise, tell it to try again
       by returning false.
    **/
}).maxNbTries(5); // Our gremlin will tolerate 5 false returns for canClick before it gives up and moves on

// Similarly, we can control the scope of formFiller and toucher.

gremlins.species.formFiller.canFillElement(/** do stuff here **/);
gremlins.species.toucher.canTouch(/** do stuff here **/);

查看 Pen Gremlins.js 开箱即用 由 Alicia Sedlock (@aliciasedlock) 在 CodePen 上创建。

自定义小精灵

如果你觉得现有的 Gremlin 种类无法满足你的需求,别担心!你可以自己编写自定义 Gremlin 来执行你期望用户执行的任何其他操作。例如,你想检查用户在体验的任何部分随机提交表单会发生什么情况?你可以希望 `clicker` 随机点击提交按钮,或者你可以创建一个自定义提交 Gremlin 来提高成功率,以及控制它的执行方式。

这需要你对如何在 JavaScript 中创建和自定义 DOM 事件有一定的了解,所以让我们一起了解一下创建提交 Gremlin 所涉及的步骤。

gremlins.createHorde() // first, create our horde 
  .allGremlins()      // and enable all gremlins
  .gremlin(function() {

    // Now let's define our submission gremlin
    var targetElement, availableForms;

    availableForms = document.querySelectorAll('form'); // Let's get all available form elements on the page
    targetElement = availableForms[Math.floor(Math.random()*availableForms.length)]; // Then let's grab a random element from those results

    // Now, we create a dummy submission event
    var evt = document.createEvent('HTMLEvents');  // Create an event container
    evt.initEvent('submit');  // Define our event as a submit event
    targetElement.dispatchEvent(evt);  // Finally, dispatch the submit event onto our randomly selected form

    // We also want to make sure to log this event like others gremlins do!      
    console.log('gremlin submit ', targetElement);
  })
  .unleash();

查看 Pen 使用 Gremlins.js 的自定义 Gremlin by Alicia Sedlock (@aliciasedlock) on CodePen.

如果你想了解更多关于创建自定义事件的指导,请查看 Mozilla 开发者网络关于创建事件的文档,并且一定要查看 Gremlins.js 如何创建它的事件的源代码(clicker Gremlin 是一个很好的起点)。

种子群

每次运行这种测试时都让一个全新的群执行,这将有助于在许多不同的情况下对你的 UI 进行压力测试。但是,如果你释放了一个群,结果出现了错误,你如何才能真正知道你是否修复了导致这些错误的问题呢?

对于想要多次运行同一个群的情况,你可以选择为群播种。这将使你每次执行测试时都能得到完全相同的群。

var horde = gremlins.createHorde();
horde.seed(1234);
horde.unleash();

你可能需要权衡在长期解决方案中使用种子群和随机群的频率。虽然种子群允许重新测试,但猴子测试的很大一部分好处在于它的随机性,而在始终使用相同的种子情况下,随机性就变得毫无意义。

结论

网络社区经常谈论不要对用户做假设。他们可能连接速度慢,没有使用最新的设备,或者由于某种原因需要他们的使用环境是可访问的。我们也不能保证五岁的 Jaime 不会拿起父母的手机,开始疯狂地点击,最终导致他们在使用任何东西时出现重大问题。或者来自市场部的 Kassidy,在第一次接触产品后,会兴奋地疯狂地点击或轻触。任何用户操作的顺序、速度或重复性都无法预测。作为开发者,我们有责任确保我们的服务不会仅仅因为期望用户只执行“正常路径”操作而意外中断。

客户端猴子测试的选择很少,但 Gremlins.js 从一开始就做对了基本原理。与任何类型的测试库或框架一样,我们只有在人们使用它的时候才能改进它!他们正在积极寻找贡献者,所以如果你对这个库的功能有什么愿望,请 告诉他们