< div >
编者注:本文由我们的编辑团队于2022年10月17日更新. 它已被修改,以包括最近的来源,并与我们目前的编辑标准保持一致.
全面测试用户交互是非常困难的. 在反应之前,前端视图并不适合传统的自动化测试框架.Js出现了.
随着单页面应用程序(SPA)框架的出现,我们的客户机变得越来越笨重. 因此,测试前端代码和 UI组件 变得更难完成.
在项目中实现测试驱动开发(TDD)一开始有点奇怪, 虽然它提供了许多福利:一个可预测的环境, 多个测试运行器, 测试工具嵌入到框架中, 以及持续集成支持.
十年前,我会说测试是所有问题的解决方案. 但后来 骨干 长大了,我们都换成了 前端MVC,本质上是将我们的可测试后端变成美化的数据库服务器. 将我们最复杂的代码移到浏览器中, 我们的应用程序在实践中不再可测试, 编写前端代码和 UI组件 难以测试.
反应原生地包含模型, 功能, 和组件, 所有这些——通过横向定义——都可以被认为是单位. 反应迫使我们构建组件. 所有这些都为单元测试奠定了基础. 换句话说, 反应, 就其本质而言, 适合在我们的UI/客户端中进行单元测试(一种经过验证的方法).
确保我们的模型运行良好, 或者调用函数会改变正确的值, 我们为负责的单位执行测试. 要执行反应 UI测试,我们需要:
- 编写格式良好的独立模块.
- 使用 茉莉花, 摩卡,或运行函数的其他测试.
- 使用测试运行器,比如 业力 or 肆无忌惮.
因此,我们的反应代码是单元测试的.
过去,运行前端测试是最困难的部分. 框架是完全不同的. 也, 在大多数情况下, 您将与手动刷新以运行测试的浏览器窗口进行交互. 当然,你总是可以忘记的——我知道我确实忘记了.
2012年,Vojta Jina发布了 业力跑步 (当时被称为Testacular). 与业力, UI测试成为了工具链的一个完整公民,我们的反应测试在终端或持续集成服务器上运行. 当我们修改文件时,测试将自动重新运行, 我们可以在多个浏览器中同时测试我们的代码.
我们还能期望什么呢? 好吧, 实际上 测试我们的 前端 反应代码.
UI测试需要的不仅仅是单元测试
单元测试对于基础是完美的. 这是查看算法是否执行一致的最好方法, 或者检查我们的输入验证逻辑, 或者数据转换, 或者其他孤立的操作.
但是前端代码不是关于操作数据的. 它是关于用户事件和在正确的时间呈现正确的视图. 前端是关于用户体验的.
以下是我们希望在反应测试中实现的目标:
- 测试用户事件.
- 测试对用户事件的响应.
- 确保在正确的时间渲染正确的东西.
- 在多个浏览器中运行测试.
- 在更新的文件上重新运行测试.
- 使用持续集成系统,比如 特拉维斯.
在反应之前,我还没有找到一个合适的方法来测试用户交互和视图渲染.
反应单元测试:UI组件
使用反应是保证我们能够实现所有测试目标的最简单方法. 反应迫使我们使用 可测试的模式 便于编写测试. 一些很棒的反应进一步支持测试 TestUtils
.
反应的组件遵循许多函数式编程的最佳原则, 除了它们是对象. 例如, 给定一组参数, 反应组件总是呈现相同的输出——不管它被呈现了多少次, 不管是谁渲染的, 不管我们把输出放在哪里. 因此, 我们不需要执行复杂的脚手架来测试反应组件, 或者跟踪全局变量和配置对象.
我们通过尽可能避免状态来实现这种稳定性. 在函数式编程中,我们称之为 referrential透明度.
当涉及到测试用户交互时, 反应为我们提供了绑定到函数回调的事件. 设置测试间谍并确认单击事件调用了正确的函数是很容易的. 因为反应组件会渲染自己, 我们可以触发一个click事件并检查HTML是否有更改. 这是有效的,因为反应组件只关心自己,所以点击 在这里 不会改变什么 在那里. 我们将永远不必处理事件处理程序的嵌套—只需定义良好的函数调用即可.
因为反应很神奇,我们不需要担心 文档对象模型(DOM). 反应使用所谓的 虚拟DOM 将组件呈现到JavaScript变量中. 为了测试反应组件,我们只需要对虚拟DOM的引用.
反应的内置测试工具
反应的 TestUtils
我们的切入点是测试用户交互和检查输出吗. TestUtils
让我们 渲染反应 组件通过将DOM放入变量(而不是将DOM插入页面). 例如,我们可以这样渲染一个反应组件:
var 组件 = testtils.renderIntoDocument (
);
然后,我们可以检查是否渲染了所有的子节点:
var h1 = testtils.find渲染edDOMComponentWithTag (
组件,h1的
);
我们现在可以用 getDOMNode ()
来访问原始DOM元素并测试其值. 作为一个例子,让我们检查我们的组件是否 H1
标签上写着“A title”:
期望(h1.getDOMNode ().textContent)
.toEqual(“标题”);
综合起来,完整的测试是这样的:
它("渲染h1", function () {
var 组件 = testtils.renderIntoDocument (
);
var h1 = testtils.find渲染edDOMComponentWithTag (
组件,h1的
);
期望(h1.getDOMNode ().textContent)
.toEqual(“标题”);
});
的 find渲染edDOMComponentWithTag
正如你所预料的那样:它会通过孩子们传递, 找到我们要找的组件, 然后返回. 返回值的行为就像反应组件一样.
TestUtils
让我们也触发用户事件. 对于点击事件,我们可以这样写:
Var节点=组件
.find渲染edDOMComponentWithTag (按钮)
.getDOMNode ();
TestUtils.模拟.单击(节点);
代码模拟点击, 并触发任何潜在的听众, 哪些应该是改变输出的组件方法, 国家, 或两个. 如果有必要,侦听器可以调用父组件上的函数.
所有情况都很容易测试:已更改的状态是in 组件.状态
. 我们可以使用普通DOM函数访问输出,也可以使用间谍函数调用.
为什么不开玩笑呢??
反应的 官方文档 建议 开玩笑 作为测试运行器和反应 UI测试框架. 开玩笑构建在茉莉花之上,保留了茉莉花的语法和优点.
编者注:自从本文最初发布以来,开玩笑已经有了很大的改进. 你可以阅读我们最近的教程, 使用酶和开玩笑反应单元测试,然后自己决定杰斯特现在是否能胜任这项任务.
开玩笑将模拟除我们正在测试的组件之外的所有组件. 从理论上讲,这很棒,但我觉得很烦人. 任何我们还没有实现的东西,或者来自代码库的不同部分的东西,都只是 未定义的
. 虽然这可能在某些情况下有效,但它可能会导致悄悄失败的bug. 例如,在一个案例中,我在测试点击事件时遇到了麻烦. 不管我怎么尝试,它就是不给它的监听器打电话. 后来,我意识到开玩笑在没有通知我的情况下悄悄地“嘲笑”了这个功能.
在开玩笑的早期版本中,一个更严重的缺陷是它缺乏监视模式. 我更喜欢在工作时在后台运行测试. 观察模式使我能够自动测试更改. 我不需要记得运行我的测试套件.
开玩笑不支持在多个浏览器中运行反应测试. 这在今天已经不像以前那么成问题了, 但在罕见的情况下,它仍然是一个重要的功能 heisenbug 比如,在Chrome的某个特定版本中崭露头角.
反应 UI测试:一个集成的例子
从理论上讲,我们已经演示了一个好的前端反应测试应该如何工作. 让我们用一个简单的例子来实践我们的理论.
使用用反应和 d3.js,我们来看看生成随机数的不同方法. 我们将使用业力作为测试运行器,摩卡作为UI测试框架,Webpack作为模块加载器. 代码和演示可以在我的 反应测试站点.
设置
我们的源文件将放在 /src
目录,我们将在其中存储测试 /src/__测试__
. 我们可以在里面有几个目录 src
,每个主要组件都有一个,每个组件都有自己的测试文件. 以这种方式捆绑源代码和测试文件,可以更容易地在其他项目中重用反应组件.
目录结构就绪后,让我们安装依赖项:
karma karma-cli karma-mocha karma-webpack 预计
如果有任何安装失败,请尝试重新运行该部分的安装. 有时在重新运行时可以避免NPM故障.
我们的 包.json
当我们完成后,文件应该看起来像这样:
{
“名称”:“react以及ing-example”,
"description": "一个用反应JS研究测试选项的示例项目",
"脚本":{
"test": "业力开始"
},
// ...
“主页”:“http://github.com/Swizec/react-testing-example”,
" devDependencies ": {
:“babel-core ^ 5.2.17",
:“babel-loader ^ 5.0.0",
"d3": "^3.5.5",
“期望”:“^ 1.6.0",
:“jsx-loader ^ 0.13.2",
“业力”:“^ 0.12.31",
:“karma-chrome-launcher ^ 0.1.10",
:“karma-cli 0.0.4",
:“karma-mocha ^ 0.1.10",
:“karma-sourcemap-loader ^ 0.3.4",
:“karma-webpack ^ 1.5.1",
“摩卡”:“^ 2.2.4",
“反应”:“^ 0.13.3",
:“react-hot-loader ^ 1.2.7",
:“react-tools ^ 0.13.3",
:“webpack ^ 1.9.4",
:“webpack-dev-server ^ 1.8.2"
}
}
经过一些配置后,您将能够使用其中任何一个运行测试 npm测试
or 业力开始
:
< / div >
配置
确保Webpack知道如何找到我们的代码, 卡玛知道怎么做测试, 将以下两行JavaScript添加到 ./测试.webpack.js
文件:
Var 上下文 = require.上下文('./src', true, /以及.jsx?$/);
上下文.键().forEach(上下文);
这段代码告诉Webpack考虑任何带有 以及
后缀成为测试套件的一部分.
配置业力需要做更多的工作:
/ /业力.相依.js
Var webpack = require('webpack');
模块.Exports = function (配置) {
配置.集({
浏览器:“铬”,
singleRun:没错,
框架:“摩卡”,
文件:[
的测试.webpack.js'
],
预处理器:{
的测试.webpack.js”(“webpack”):
},
记者:“点”,
webpack: {
模块:{
加载器:(
{测试:/ \.jsx?$/, exclude: /node_模块s/, loader: 'babel-loader'}
]
},
看:真
},
webpackServer: {
noInfo:真
}
});
};
这些行大多来自默认的业力配置. 我们使用 吃嫩叶的动物
指示测试应该在Chrome中运行. 框架
指定我们正在使用的测试框架,和 singleRun
默认情况下用于使测试只运行一次. 你可以让卡玛在后台运行 因果报应,不许一分跑
. 一切都很简单.
因为Webpack处理我们代码的依赖树,所以我们不需要在 文件
数组. 我们只需要 测试.webpack.js
,然后需要所有必要的文件.
我们使用 webpack
设置告诉Webpack要做什么. 在正常情况下,这部分会变成a webpack.配置.js
文件.
我们还告诉Webpack使用 babel-loader
查看我们的javascript. 这给了我们所有奇特的特性 ECMAScript2015 和 反应的JSX.
与 webpackServer
配置时,我们告诉Webpack不要打印任何调试信息. 它只会破坏我们的测试输出.
一个反应组件和一个测试
有了一个运行的测试套件,剩下的就很简单了. 我们必须创建一个组件来接受随机坐标数组并创建一个
元素和一堆点.
以下是反应 UI测试的最佳实践.e.标准的TDD实践——我们将先编写测试,然后编写实际的反应组件. 让我们从一个香草测试文件开始 src / __测试__ /
:
/ / 散点图以及.jsx
var 反应 = require(' 反应 /插件'),
testtils = 反应.插件.TestUtils,
Expect = require(' Expect '),
散点图 = require('../散点图.jsx”);
Var d3 = require('d3');
description ('散点图', function () {
Var normal = d3.随机.正常(1,1),
mockData = d3.范围(5).Map (function () {
返回{x: normal(), y: normal()};
});
});
首先,我们需要反应 TestUtils
, d3.js, 预计
库,以及我们正在测试的代码. 然后我们创建一个新的测试套件 描述
,并创建一些随机数据.
对于我们的第一个测试,我们来确定一下 散点图
渲染标题. 我们的测试在 描述
布洛克:
/ / 散点图以及.jsx
它("渲染h1", function () {
var scatterplot = testtils.renderIntoDocument (
<散点图 />
);
var h1 = testtils.find渲染edDOMComponentWithTag (
散点图,“h1”
);
期望(h1.getDOMNode ().textContent).toEqual("这是一个随机的散点图");
});
大多数测试将遵循以下模式:
- 渲染.
- 查找特定节点.
- 检查内容.
正如我们之前所展示的, renderIntoDocument
渲染我们的组件, find渲染edDOMComponentWithTag
找到我们要测试的特定部件,然后 getDOMNode
给了我们原始的DOM访问.
如果没有组件来呈现标题标记,我们的测试就会失败. 为了让它通过,让我们编写这样一个组件:
var 反应 = require(' 反应 /插件');
Var d3 = require('d3');
var 散点图 = 反应.createClass ({
渲染:函数(){
回报(
This is a 随机 scatterplot
);
}
});
模块.exports = 散点图;
就是这样. 的 散点图
组件呈现一个 与一个
标记包含预期的文本,我们的测试将通过. 是的,它不仅仅是简单的HTML,但是请再耐心听我说一会儿.
一个更有趣的测试
我想向你展示一个测试,以确保所有的数据点显示在图表上:
/ / 散点图以及.jsx
它("为每个数据点渲染一个圆",function () {
var scatterplot = testtils.renderIntoDocument (
<散点图 data={mockData} />
);
var circles = testtils.scry渲染edDOMComponentsWithTag (
散点图,“圆”
);
期望(圈.长度).toEqual (5);
});
和之前一样:渲染,查找节点,检查结果. 这里有趣的部分是绘制那些DOM节点. 现在加入d3.Js魔术到我们的组件:
/ /散点图.jsx
组件WillMount:函数(){
这.yScale = d3.规模.线性();
这.xScale = d3.规模.线性();
这.update_d3(这.道具);
},
组件WillReceiveProps:函数(newProps) {
这.update_d3 (newProps);
},
Update_d3: function (道具) {
这.yScale
.域([d3.min(道具.数据,函数(d){返回d.y; }),
d3.max(道具.数据,函数(d){返回d.y; })])
.范围([道具.point_r、数量(道具.height-道具.point_r)]);
这.xScale
.域([d3.min(道具.数据,函数(d){返回d.x; }),
d3.max(道具.数据,函数(d){返回d.x; })])
.范围([道具.point_r、数量(道具.width-道具.point_r)]);
},
//...
你可以在我的 反应测试报告.
我们使用 组件WillMount
设置空的d3比例 X
和 Y
域, 组件WillReceiveProps
确保它们在发生变化时得到更新. 然后 update_d3
确保设置 域
和 范围
对于两个尺度.
我们将使用这两个尺度在数据集中的随机值和图片上的位置之间进行转换. 中返回的数字 [0,1
范围,它太小而不能视为像素.
然后我们将这些点添加到组件的渲染方法中:
/ /散点图.jsx
渲染:函数(){
回报(
This is a 随机 scatterplot
);
}
这个代码经过 这.道具.data
数组并添加
元素对应每个数据点.
不要再为你的UI测试绝望了,有了反应组件测试.< / div >