我们行业中有一个常见的谚语:“你永远无法预测用户拿到你的产品后会如何使用它。” 如果你曾经观看过利益相关者第一次使用网站或 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 就为我们提供了非常强大的测试。
高级乱搞
如果在使用默认配置后,你的需求没有得到满足,那么有很多方法可以自定义运行方式。例如,你可能想要一次只关注页面上的特定组件,而不是始终测试整个页面。
虽然我们无法对所有种类的小精灵进行范围限定,但我们可以将 toucher
、clicker
和 formFiller
限定在页面的特定区域。具体来说,我们要求小精灵检查它尝试定位的元素的父级。如果该元素在我们定义的范围内,小精灵将继续执行操作。否则,小精灵将重新尝试查找要与之交互的元素。我们还可以告诉小精灵在放弃操作之前,我们希望它们尝试执行操作的次数,可以使用 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 从一开始就做对了基本原理。与任何类型的测试库或框架一样,我们只有在人们使用它的时候才能改进它!他们正在积极寻找贡献者,所以如果你对这个库的功能有什么愿望,请 告诉他们!
使用 Gremlin.js 的 Focus 组件在 Chrome 54 中出现问题,不允许我向下滚动页面。还有其他人发现此问题在其他浏览器中存在吗?
它实际上就像预期的那样聚焦元素,这会触发浏览器将该元素带入视图。如果你已经在演示中看到了你想要看到的内容,你可以关闭结果选项卡。
大家好,
原谅我的菜鸟行为,但 Gremlin 如何断言它们破坏了某些东西(比如页面的布局或小部件)?除了观察 FPS 之外。
您好!不用道歉。:)
除了 FPS,Mogwai 还负责捕获所有 JavaScript 错误。在这个库中,`console.error` 和 `window.onerror` 被覆盖,并捕获任何特定于代码的错误(例如 `'undefined is not a function'` 或 `'could not find x of undefined'` 这样的错误)。
这是由一个非常特殊的 Mogwai 处理的,名叫 Gizmo。如果你感兴趣,可以查看 Gizmo 的源代码并尝试了解内部工作原理!希望这能让你更清楚地了解。 https://github.com/marmelab/gremlins.js/blob/master/src/mogwais/gizmo.js
这真是太棒了!感谢大家提出这个问题!我迫不及待地想把它添加到我的库中!
干杯!
J
Ps. 是否可以将“createHorde()”方法别名为“addWater()”?
【免责声明:我只是粗略地浏览了这篇文章。】是否可以设置它来记录每个随机群的种子,这样如果某个特定群导致失败,你可以在修复问题时多次重播该特定群?
您好 Nathan!这也是我一直很好奇的事情。它比仅仅获取当前种子要复杂一些。
如果你有一个群,你可以通过 `horde._randomizer` 访问它的随机器数据。这是使用另一个名为 Chance.js 的库生成的,该库负责所有随机化计算。从深入研究这个对象来看,至少对我来说,它似乎没有存储它们自己如何生成数据的特定种子。也许我遗漏了什么!
我已经开发软件应用程序超过十年了,从未听说过猴子测试。它与实际描述的混沌测试有什么区别?
您好 Josh!根据我的经验,混沌测试和猴子测试(以及模糊测试)往往可以互换使用,尽管它们在各自的方式上略有不同。混沌测试更多地涉及故意随机地引发故障,而猴子测试更多地是关于执行随机操作,这些操作可能会(但不一定)导致故障。它们都有一些随机元素,并且都基于测试你正在构建的任何东西的弹性,但它们背后的意图不同。
是否可以使用只有在捕获到错误时才保存种子的随机马?这样每次都使用随机场景,但能够在应用修复后重新运行失败的场景?
感谢你介绍这个库。非常有用。
在你的自定义 Gremlins 部分,我只想提请注意 Event.initEvent() 已 弃用,并且在我的 Firefox FF45.3 中不起作用,因此需要替换以下代码
由于它已弃用,请不再使用 initEvent()。而是使用特定的事件构造函数,例如 Event()。
谢谢!很棒的内容。
看看通过 gremlins.js 进行猴子测试发现错误的案例研究,并附上示例,将会很有趣。