/ svg >作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.< / div >< / div >
Swizec Teller是一名作家,他的作品支持了Uber的数万名工程师, 甲骨文, 苹果和其他知名公司.
< / div >< / div >< / div >编者注:本文由我们的编辑团队于2022年10月17日更新. 它已被修改,以包括最近的来源,并与我们目前的编辑标准保持一致.
全面测试用户交互是非常困难的. 在反应之前,前端视图并不适合传统的自动化测试框架.js came along.
随着单页面应用程序(SPA)框架的出现,我们的客户机变得越来越笨重. 因此,测试前端代码和 UI components 变得更难完成. 在项目中实现测试驱动开发(TDD)一开始有点奇怪, 虽然它提供了许多福利:一个可预测的环境, multiple test runners, 测试工具嵌入到框架中, 以及持续集成支持.
十年前,我会说测试是所有问题的解决方案. But then Backbone 长大了,我们都换成了 front-end MVC,本质上是将我们的可测试后端变成美化的数据库服务器. 将我们最复杂的代码移到浏览器中, 我们的应用程序在实践中不再可测试, making front-end code 和 UI components difficult to test.
反应原生地包含模型, functions, 和 components, 所有这些——通过横向定义——都可以被认为是单位. 反应迫使我们构建组件. 所有这些都为单元测试奠定了基础. In other words, 反应, by its very nature, 适合在我们的UI/客户端中进行单元测试(一种经过验证的方法).
确保我们的模型运行良好, 或者调用函数会改变正确的值, 我们为负责的单位执行测试. 要执行反应 UI测试,我们需要:
因此,我们的反应代码是单元测试的.
过去,运行前端测试是最困难的部分. Frameworks were disparate. 也, in most cases, 您将与手动刷新以运行测试的浏览器窗口进行交互. 当然,你总是可以忘记的——我知道我确实忘记了.
2012年,Vojta Jina发布了 业力 runner (当时被称为Testacular). With 业力, UI测试成为了工具链的一个完整公民,我们的反应测试在终端或持续集成服务器上运行. 当我们修改文件时,测试将自动重新运行, 我们可以在多个浏览器中同时测试我们的代码.
What more could we wish for? Well, to actually 测试我们的 front-end 反应 code.
单元测试对于基础是完美的. 这是查看算法是否执行一致的最好方法, 或者检查我们的输入验证逻辑, or data transformations, 或者其他孤立的操作.
但是前端代码不是关于操作数据的. 它是关于用户事件和在正确的时间呈现正确的视图. 前端是关于用户体验的.
以下是我们希望在反应测试中实现的目标:
在反应之前,我还没有找到一个合适的方法来测试用户交互和视图渲染.
使用反应是保证我们能够实现所有测试目标的最简单方法. 反应迫使我们使用 testable patterns 便于编写测试. 一些很棒的反应进一步支持测试 TestUtils
.
反应的组件遵循许多函数式编程的最佳原则, except they’re objects. For instance, 给定一组参数, 反应组件总是呈现相同的输出——不管它被呈现了多少次, no matter who renders it, 不管我们把输出放在哪里. Consequently, 我们不需要执行复杂的脚手架来测试反应组件, 或者跟踪全局变量和配置对象.
我们通过尽可能避免状态来实现这种稳定性. 在函数式编程中,我们称之为 referrential transparency.
当涉及到测试用户交互时, 反应为我们提供了绑定到函数回调的事件. 设置测试间谍并确认单击事件调用了正确的函数是很容易的. 因为反应组件会渲染自己, 我们可以触发一个click事件并检查HTML是否有更改. 这是有效的,因为反应组件只关心自己,所以点击 在这里 won’t change things 在那里. 我们将永远不必处理事件处理程序的嵌套—只需定义良好的函数调用即可.
因为反应很神奇,我们不需要担心 Document Object Model (DOM). 反应 uses the so-called virtual DOM 将组件呈现到JavaScript变量中. 为了测试反应组件,我们只需要对虚拟DOM的引用.
反应的 TestUtils
我们的切入点是测试用户交互和检查输出吗. TestUtils
让我们 render a 反应 组件通过将DOM放入变量(而不是将DOM插入页面). 例如,我们可以这样渲染一个反应组件:
var component = TestUtils.renderIntoDocument(
);
然后,我们可以检查是否渲染了所有的子节点:
var h1 = TestUtils.find渲染edDOMComponentWithTag (
component, 'h1'
);
We can now use getDOMNode()
来访问原始DOM元素并测试其值. 作为一个例子,让我们检查我们的组件是否 H1
tag says “A title”:
预计(h1.getDOMNode().textContent)
.toEqual("A title");
综合起来,完整的测试是这样的:
它("渲染h1", function () {
var component = TestUtils.renderIntoDocument(
);
var h1 = TestUtils.find渲染edDOMComponentWithTag (
component, 'h1'
);
预计(h1.getDOMNode().textContent)
.toEqual("A title");
});
的 find渲染edDOMComponentWithTag
正如你所预料的那样:它会通过孩子们传递, 找到我们要找的组件, 和 returns it. 返回值的行为就像反应组件一样.
TestUtils
让我们也触发用户事件. 对于点击事件,我们可以这样写:
var node = component
.find渲染edDOMComponentWithTag (按钮)
.getDOMNode();
TestUtils.模拟.click(node);
的 code simulates a click, 并触发任何潜在的听众, 哪些应该是改变输出的组件方法, the 状态, 或两个. 如果有必要,侦听器可以调用父组件上的函数.
所有情况都很容易测试:已更改的状态是in component.状态
. 我们可以使用普通DOM函数访问输出,也可以使用间谍函数调用.
反应的 official documentation recommends 开玩笑 作为测试运行器和反应 UI测试框架. 开玩笑构建在茉莉花之上,保留了茉莉花的语法和优点.
编者注:自从本文最初发布以来,开玩笑已经有了很大的改进. 你可以阅读我们最近的教程, 使用酶和开玩笑反应单元测试,然后自己决定杰斯特现在是否能胜任这项任务.
开玩笑将模拟除我们正在测试的组件之外的所有组件. 从理论上讲,这很棒,但我觉得很烦人. 任何我们还没有实现的东西,或者来自代码库的不同部分的东西,都只是 undefined
. 虽然这可能在某些情况下有效,但它可能会导致悄悄失败的bug. 例如,在一个案例中,我在测试点击事件时遇到了麻烦. 不管我怎么尝试,它就是不给它的监听器打电话. 后来,我意识到开玩笑在没有通知我的情况下悄悄地“嘲笑”了这个功能.
在开玩笑的早期版本中,一个更严重的缺陷是它缺乏监视模式. 我更喜欢在工作时在后台运行测试. 观察模式使我能够自动测试更改. 我不需要记得运行我的测试套件.
开玩笑不支持在多个浏览器中运行反应测试. 这在今天已经不像以前那么成问题了, 但在罕见的情况下,它仍然是一个重要的功能 heisenbug 比如,在Chrome的某个特定版本中崭露头角.
从理论上讲,我们已经演示了一个好的前端反应测试应该如何工作. 让我们用一个简单的例子来实践我们的理论.
使用用反应和 d3.js,我们来看看生成随机数的不同方法. 我们将使用业力作为测试运行器,摩卡作为UI测试框架,Webpack作为模块加载器. 代码和演示可以在我的 反应 test site.
我们的源文件将放在
目录,我们将在其中存储测试
. 我们可以在里面有几个目录 src
,每个主要组件都有一个,每个组件都有自己的测试文件. 以这种方式捆绑源代码和测试文件,可以更容易地在其他项目中重用反应组件.
目录结构就绪后,让我们安装依赖项:
karma karma-cli karma-mocha karma-webpack 预计
如果有任何安装失败,请尝试重新运行该部分的安装. 有时在重新运行时可以避免NPM故障.
我们的 包.json
当我们完成后,文件应该看起来像这样:
{
“名称”:“react以及ing-example”,
"description": "一个用反应JS研究测试选项的示例项目",
"scripts": {
"test": "karma start"
},
// ...
"homepage": "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",
"karma": "^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",
"mocha": "^2.2.4",
"react": "^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 karma start
:
确保Webpack知道如何找到我们的代码, 卡玛知道怎么做测试, 将以下两行JavaScript添加到 ./测试.webpack.js
文件:
var 上下文 = require.上下文('./src', true, /以及\.jsx?$/);
上下文.键().forEach(上下文);
这段代码告诉Webpack考虑任何带有 以及
后缀成为测试套件的一部分.
配置业力需要做更多的工作:
/ /业力.相依.js
Var webpack = require('webpack');
模块.Exports = function (配置) {
配置.集({
吃嫩叶的动物: ['Chrome'],
singleRun: true,
frameworks: ['mocha'],
文件:[
的测试.webpack.js'
],
preprocessors: {
的测试.webpack.js': ['webpack']
},
reporters: ['dots'],
webpack: {
模块: {
loaders: [
{test: /\.jsx?$/, exclude: /node_模块s/, loader: 'babel-loader'}
]
},
watch: true
},
webpackServer: {
noInfo: true
}
});
};
这些行大多来自默认的业力配置. 我们使用 吃嫩叶的动物
指示测试应该在Chrome中运行. frameworks
指定我们正在使用的测试框架,和 singleRun
默认情况下用于使测试只运行一次. 你可以让卡玛在后台运行 karma start --no-single-run
. All simple enough.
因为Webpack处理我们代码的依赖树,所以我们不需要在 文件
数组. We only need 测试.webpack.js
,然后需要所有必要的文件.
我们使用 the webpack
设置告诉Webpack要做什么. 在正常情况下,这部分会变成a webpack.配置.js
文件.
我们还告诉Webpack使用 babel-loader
for our JavaScripts. 这给了我们所有奇特的特性 ECMAScript2015 和 反应的 JSX.
与 webpackServer
配置时,我们告诉Webpack不要打印任何调试信息. 它只会破坏我们的测试输出.
有了一个运行的测试套件,剩下的就很简单了. 我们必须创建一个组件来接受随机坐标数组并创建一个 元素和一堆点.
以下是反应 UI测试的最佳实践.e.标准的TDD实践——我们将先编写测试,然后编写实际的反应组件. 让我们从一个香草测试文件开始 src/__测试__/
:
// ScatterPlot以及.jsx
var 反应 = require(' 反应 /插件'),
TestUtils = 反应.插件.TestUtils,
预计 = require('预计'),
ScatterPlot = require('../ScatterPlot.jsx”);
var d3 = require('d3');
description ('ScatterPlot', function () {
var normal = d3.随机.normal(1, 1),
mockData = d3.范围(5).map(function () {
返回{x: normal(), y: normal()};
});
});
首先,我们需要反应 TestUtils
, d3.js, 预计
库,以及我们正在测试的代码. 然后我们创建一个新的测试套件 describe
,并创建一些随机数据.
对于我们的第一个测试,我们来确定一下 ScatterPlot
renders a title. 我们的 test goes inside the describe
布洛克:
// ScatterPlot以及.jsx
它("渲染h1", function () {
var scatterplot = TestUtils.renderIntoDocument(
);
var h1 = TestUtils.find渲染edDOMComponentWithTag (
scatterplot, 'h1'
);
预计(h1.getDOMNode().textContent).toEqual("这是一个随机的散点图");
});
大多数测试将遵循以下模式:
正如我们之前所展示的, renderIntoDocument
renders our component, find渲染edDOMComponentWithTag
找到我们要测试的特定部件,然后 getDOMNode
gives us raw DOM access.
如果没有组件来呈现标题标记,我们的测试就会失败. 为了让它通过,让我们编写这样一个组件:
var 反应 = require(' 反应 /插件');
var d3 = require('d3');
var ScatterPlot = 反应.createClass({
render: function () {
return (
This is a 随机 scatterplot
);
}
});
模块.exports = ScatterPlot;
That’s it. 的 我想向你展示一个测试,以确保所有的数据点显示在图表上: 和之前一样:渲染,查找节点,检查结果. 这里有趣的部分是绘制那些DOM节点. Let’s now add some d3.js magic to our component: 你可以在我的 反应 test repo. 我们使用 我们将使用这两个尺度在数据集中的随机值和图片上的位置之间进行转换. 中返回的数字 然后我们将这些点添加到组件的渲染方法中: This code goes through the 了解更多关于反应和d3结合的知识.Js来制作数据可视化组件, 考虑订阅我书中的一个免费章节, 反应+d3.js. 在本教程中,你学到了: 我再次邀请你来看看我的 反应 test example repo,在这里你可以查看更多反应 UI测试. 组件测试类似于单元测试, 但在更高的水平上表现, 至少部分集成了单元. 在某些情况下,组件测试发生在整个应用程序的上下文中. 当使用最佳实践进行设计时, 反应组件是模块化和可重复的, 让它们以一种与单元测试相似的方式进行测试, 不需要应用程序的其余部分. Only a test framework (e.g.(如茉莉花)和一个测试员(如茉莉花).g.(如业力))来测试反应组件. 单元测试是在每个功能的基础上,尽可能在最精细和最隔离的级别上进行测试. 单元测试显示给定一组输入的函数是否返回正确的输出.ScatterPlot
component renders a 标记包含预期的文本,我们的测试将通过. 是的,它不仅仅是简单的HTML,但是请再耐心听我说一会儿.
A More Interesting Test
// ScatterPlot以及.jsx
它("为每个数据点渲染一个圆",function () {
var scatterplot = TestUtils.renderIntoDocument(
// ScatterPlot.jsx
componentWillMount:函数(){
这.yScale = d3.规模.linear();
这.xScale = d3.规模.linear();
这.update_d3(这.道具);
},
componentWillReceiveProps:函数(newProps) {
这.update_d3(newProps);
},
Update_d3: function (道具) {
这.yScale
.域([d3.min(道具.数据,函数(d){返回d.y; }),
d3.max(道具.数据,函数(d){返回d.y; })])
.范围([道具.point_r, Number(道具.height-道具.point_r)]);
这.xScale
.域([d3.min(道具.数据,函数(d){返回d.x; }),
d3.max(道具.数据,函数(d){返回d.x; })])
.范围([道具.point_r, Number(道具.width-道具.point_r)]);
},
//...
componentWillMount
设置空的d3比例 X
和 Y
域s, 和 componentWillReceiveProps
确保它们在发生变化时得到更新. 然后 update_d3
makes sure to set the 域
和 范围
f或两个 规模s.[0,1
范围,它太小而不能视为像素.// ScatterPlot.jsx
render: function () {
return (
This is a 随机 scatterplot
这.道具.data
数组 和 adds a
element for each datapoint.自动化反应组件测试:比听起来容易
关于总博客的进一步阅读:
Underst和ing the basics
What is component testing?
如何测试反应组件?
单元测试是什么意思?
标签
< / div >< / div >< / div >< / div >