< / svg >作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.< / div >< / div >
Swizec出纳员< / svg >
< / div >

Swizec出纳员

< / div >
< / svg >自由发展顾问< / div >< / div >< / div >< / div >

Swizec出纳员是一名作家,他的作品支持了Uber的数万名工程师, 甲骨文, 苹果和其他知名公司.

< / div >< / div >

专业知识

反应.js质量保证工程前端< / div >< / div >< / div >< / div >< / div >< / div >< / div >< / div >< / div >
< / svg >< / div >
< / svg >< / div >
< / svg >< / div >< / div >分享< / div >< / div >< / div >
< div >

编者注:本文由我们的编辑团队于2022年10月17日更新. 它已被修改,以包括最近的来源,并与我们目前的编辑标准保持一致.

全面测试用户交互是非常困难的. 在反应之前,前端视图并不适合传统的自动化测试框架.Js出现了.

随着单页面应用程序(SPA)框架的出现,我们的客户机变得越来越笨重. 因此,测试前端代码和 UI组件 变得更难完成. 在项目中实现测试驱动开发(TDD)一开始有点奇怪, 虽然它提供了许多福利:一个可预测的环境, 多个测试运行器, 测试工具嵌入到框架中, 以及持续集成支持.

十年前,我会说测试是所有问题的解决方案. 但后来 骨干 长大了,我们都换成了 前端MVC,本质上是将我们的可测试后端变成美化的数据库服务器. 将我们最复杂的代码移到浏览器中, 我们的应用程序在实践中不再可测试, 编写前端代码和 UI组件 难以测试.

反应原生地包含模型, 功能, 和组件, 所有这些——通过横向定义——都可以被认为是单位. 反应迫使我们构建组件. 所有这些都为单元测试奠定了基础. 换句话说, 反应, 就其本质而言, 适合在我们的UI/客户端中进行单元测试(一种经过验证的方法).

确保我们的模型运行良好, 或者调用函数会改变正确的值, 我们为负责的单位执行测试. 要执行反应 UI测试,我们需要:

因此,我们的反应代码是单元测试的.

过去,运行前端测试是最困难的部分. 框架是完全不同的. 也, 在大多数情况下, 您将与手动刷新以运行测试的浏览器窗口进行交互. 当然,你总是可以忘记的——我知道我确实忘记了.

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("这是一个随机的散点图");
});

大多数测试将遵循以下模式:

  1. 渲染.
  2. 查找特定节点.
  3. 检查内容.

正如我们之前所展示的, 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比例 XY 域, 组件WillReceiveProps 确保它们在发生变化时得到更新. 然后 update_d3 确保设置 范围 对于两个尺度.

我们将使用这两个尺度在数据集中的随机值和图片上的位置之间进行转换. 中返回的数字 [0,1 范围,它太小而不能视为像素.

然后我们将这些点添加到组件的渲染方法中:

/ /散点图.jsx
渲染:函数(){
    回报(
        

This is a 随机 scatterplot

{这个.道具.data.Map (function (pos, i) { Var key = "circle-"+i; 回报( ); }.bind ())};
); }

这个代码经过 这.道具.data 数组并添加 元素对应每个数据点.

不要再为你的UI测试绝望了,有了反应组件测试.< / div >
推特 < / div >

了解更多关于反应和d3结合的知识.Js来制作数据可视化组件, 考虑订阅我书中的一个免费章节, 反应+ d3.js.

自动化反应组件测试:比听起来容易

在本教程中,你学到了:

  1. 反应迫使我们模块化和封装.
  2. 模块化/封装组件促进了反应 UI测试的自动化.
  3. 对于前端来说,单元测试是不够的.
  4. 业力是一个很棒的测试者.
  5. 杰斯特在成为一名优秀的测试员的道路上走了很长一段路.

我再次邀请你来看看我的 反应测试样例,在这里你可以查看更多反应 UI测试.

< / div >< / div >< / div >< / div >

关于总博客的进一步阅读:

< / div >

了解基本知识

  • 什么是组件测试?

    组件测试类似于单元测试, 但在更高的水平上表现, 至少部分集成了单元. 在某些情况下,组件测试发生在整个应用程序的上下文中.

    < / div >< / div >
  • 如何测试反应组件?

    当使用最佳实践进行设计时, 反应组件是模块化和可重复的, 让它们以一种与单元测试相似的方式进行测试, 不需要应用程序的其余部分. 只有一个测试框架(例如.g.(如茉莉花)和一个测试员(如茉莉花).g.(如业力))来测试反应组件.

    < / div >< / div >
  • 单元测试是什么意思?

    单元测试是在每个功能的基础上,尽可能在最精细和最隔离的级别上进行测试. 单元测试显示给定一组输入的函数是否返回正确的输出.

    < / div >< / div >
< / div >< / div >

标签

< / div >< / div >< / div >< / div >
聘请Toptal这方面的专家.< / div >现在雇佣< / div >< / div >
< / div >
< / div >
< / div >< / div >< / div >< / div >< / div >
< / div >< / div >
< / div >

世界级的文章,每周发一次.

< / div >

输入您的电子邮件,即表示您同意我们的 隐私政策.

< / div >< / div >< / div >< / div >< div >
< / div >

世界级的文章,每周发一次.

< / div >

输入您的电子邮件,即表示您同意我们的 隐私政策.

< / div >< / div >< / div >< / div >< / div >< / div >< / div >

Toptal开发者

< / div >< / div >

加入总冠军® 社区.

聘请开发人员 or 申请成为发展商< / div >< / div >< / div >