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

Dave Hyungmok Lee

< / div >
验证专家 在工程< / div >< / div >< / div >< / div >

戴夫是韩国市场上排名第一的安卓应用程序的创造者. 他擅长多种编程语言和框架.

< / div >< / div >

专业知识

反应.js< / div >< / div >

以前在

雅虎!< / div >< / div >< / div >< / div >< / div >< / div >< / div >< / div >< / div >
< / div >
< / div >
< / div >< / div >分享< / div >< / div >< / div >
\n\n\n\n

在这篇文章中, 我们将使用测试驱动开发(TDD)从用户故事到开发来开发一个反应应用程序. 同样,我们将在TDD中使用开玩笑和酶. 完成本指南后,您将能够:

\n\n
    \n
  • 根据需求创建史诗和用户故事.
  • \n
  • 根据用户描述创建测试.
  • \n
  • 使用TDD开发反应应用程序.
  • \n
  • 使用酶和开玩笑来测试反应应用程序.
  • \n
  • 在响应式设计中使用/重用CSS变量.
  • \n
  • 创建一个可重用的反应组件,根据提供的道具以不同的方式呈现和运行.
  • \n
  • 使用反应 PropTypes类型检查组件道具.
  • \n
\n\n

本文假设您具有反应的基本知识. 如果您是反应的新手,我建议您完成 官方教程 看看Toptal的2019 反应教程: 第1部分第2部分.

\n\n

我们的测试驱动反应应用概述

\n\n

我们将构建一个由一些UI组件组成的基本番茄定时器应用程序. 每个组件在相应的测试文件中都有一组单独的测试. 首先,我们可以 创建史诗和用户故事 根据我们的项目要求如下.

\n\n
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
史诗用户故事验收标准
作为一个用户,我需要使用计时器,这样我就可以管理我的时间.作为一个用户,我需要启动计时器,这样我就可以倒数我的时间.确保用户能够:
\n
\n*启动计时器
\n*看到计时器开始倒数
\n
\n即使用户多次点击开始按钮,计数时间也不应中断.\n
作为一个用户,我需要停止计时器,这样我才能在需要的时候倒数我的时间.确保用户能够:
\n
\n*停止定时器
\n*见计时器停止
\n
\n即使用户多次单击停止按钮,也不会发生任何事情.\n
作为一个用户,我需要重置计时器,这样我就可以从一开始倒计时我的时间.确保用户能够:
\n
\n*重置定时器
\n*参见定时器重置为默认值
< / div >\n\n

线框

\n\n

\"线框\"

\n\n

项目设置

\n\n

首先,我们将使用 创建反应应用 如下:

\n\n
$ NPX create-react-app react-timer\n$ CD反应计时器\n$ NPM start\n
\n\n

您将看到在URL处打开一个新的浏览器选项卡 http://localhost:3000. 你可以使用以下命令停止正在运行的反应应用 Ctrl + C.

\n\n

现在,我们要加 开玩笑 和一些依赖关系如下:

\n\n
$ npm i -D酶\n$ npm i -D react-test-renderer酶-adapter-react-16\n
\n\n

同时,我们将添加或更新一个名为 setupTests.jssrc 目录:

\n\n
从“酶”中导入{configure};\n从'酶-适配器-反应-16 '中导入适配器;\n\n配置({adapter: new adapter ()});\n
\n\n

创建反应应用 运行 setupTests.js 文件,它将执行并正确配置酶.

\n\n

配置CSS

\n\n

我们将编写变量和一个基本的CSS重置,因为我们想 CSS变量 在应用程序中全局可用. 我们将定义变量 root作用域. 定义变量的语法是使用自定义属性表示法, 每个变量都以-开头,后面跟着变量名.

\n\n

导航到 指数.css 文件并添加以下内容:

\n\n
:根{\n\t——font - family:宋体;\n}\n\nBody, div, p {\n\t保证金:0;\n\t填充:0;\n}\n
\n\n

现在,我们需要将CSS导入到应用程序中. 更新索引.Js文件如下:

\n\n
从“反应”中导入反应;\n从' react-dom '导入反应DOM;\n进口的./索引.css ';\n反应DOM.呈现(\n\t<反应.StrictMode>\n\t\t<应用程序 />\n\t\n\t文档.getElementById(“根”)\n)\n
\n\n

浅渲染测试

\n\n

正如您可能已经知道的,TDD过程看起来是这样的:

\n\n
    \n
  1. 添加测试.
  2. \n
  3. 运行所有测试,您将看到测试失败.
  4. \n
  5. 编写代码以通过测试.
  6. \n
  7. 运行所有测试.
  8. \n
  9. 重构.
  10. \n
  11. 重复.
  12. \n
\n\n

因此, 我们将为浅渲染测试添加第一个测试,然后编写代码以通过测试. 添加一个新的规范文件 应用程序.规范.js to src /组件/应用程序 目录如下:

\n\n
从“反应”中导入反应;\n从' enzyme '中导入{shallow};\n导入应用程序./应用程序”;\n\ndescribe(‘应用程序’, () => {\n\tit(‘应该 呈现一个 
’, () => {\n\t\tconst 浅容器<应用程序 />);\n\t\t期望(容器.找到(div).长度).toEqual (1);\n});\n});\n
\n\n

然后,您可以运行测试:

\n\n
$ NPM测试\n
\n\n

您将看到测试失败.

\n\n

应用程序组件

\n\n

现在,我们将继续创建应用程序组件以通过测试. 导航到 应用程序.jsx 在目录中 src /组件/应用程序 并添加如下代码:

\n\n
从“反应”中导入反应;\n\nconst 应用程序 = () => 
;\n\n导出默认应用程序;\n
\n\n

现在,再次运行测试.

\n\n
$ NPM测试\n
\n\n

第一个测试现在应该通过了.

\n\n

添加应用CSS

\n\n

我们将创建一个文件 应用程序.css 在目录中 src /组件/应用程序 给应用程序组件添加一些样式,如下所示:

\n\n
.容器{\n\t身高:100 vh;\n\t宽度:100大众;\n\t对齐项目:中心;\n\t显示:flex;\n\tjustify-content:中心;\n}\n
\n\n

现在,我们准备将CSS导入到 应用程序.jsx 文件:

\n\n
从“反应”中导入反应;\n进口的./应用程序.css ';\n\nconst 应用程序 = () => 
;\n\n导出默认应用程序;\n
\n\n

接下来,我们必须更新 指数.js 导入应用程序组件,如下所示:

\n\n
import 反应 from \"react\"\nimport 反应DOM from \"react-dom\"\nimport \"./索引.css\"\nimport 应用程序 from \"./components/应用程序/应用程序\"\nimport * as serviceWorker from \"./serviceWorker\"\n\n反应DOM.呈现(\n  <反应.StrictMode>\n    <应用程序 />\n  ,\n  文档.getElementById(\"root\")\n)\n\n//如果你想让你的应用离线工作,加载速度更快,你可以修改\n// 注销()到register(. 注意,这有一些缺陷.\n//了解service worker的更多信息:http://bit.ly / CRA-PWA\nserviceWorker.注销()\n
\n\n

添加计时器组件

\n\n

最后,应用程序将包含计时器组件,因此我们将更新 应用程序.规范.js 文件来检查应用中是否存在计时器组件. 也, 我们将在第一个测试用例之外声明容器变量,因为浅层渲染测试需要在每个测试用例之前完成.

\n\n
import 反应 from \"react\"\nimport { shallow } from \"enzyme\"\nimport 应用程序 from \"./应用程序\"\nimport 计时器 from \"../计时器/计时器\"\n\ndescribe(\"应用程序\", () => {\n  让容器\n\n  beforeEach(() => (浅容器<应用程序 />)))\n\n  it(\"应该 呈现一个 
\", () => {\n 期望(容器.find(\"div\").长度).toEqual (1)\n })\n\n it(\"应该 render the 计时器 Component\", () => {\n 期望(容器.containsMatchingElement(<计时器 />)).toEqual(真正的)\n })\n})\n
\n\n

如果你逃跑 npm测试 在这个阶段,测试将失败,因为计时器组件还不存在.

\n\n

编写定时器浅渲染测试

\n\n

现在,我们要创建一个名为 计时器.规范.js 在一个名为 计时器src /组件 目录.

\n\n

此外,我们将添加浅渲染测试 计时器.规范.js 文件:

\n\n
import 反应 from \"react\"\nimport { shallow } from \"enzyme\"\nimport 计时器 from \"./计时器\"\n\ndescribe(\"计时器\", () => {\n  让容器\n\n  beforeEach(() => (浅容器<计时器 />)))\n\n  it(\"应该 呈现一个 
\", () => {\n 期望(容器.find(\"div\").长度).toBeGreaterThanOrEqual (1)\n })\n})\n
\n\n

正如预期的那样,测试将会失败.

\n\n

创建定时器组件

\n\n

接下来,让我们创建一个名为 计时器.jsx 并根据用户故事定义相同的变量和方法:

\n\n
从' 反应 '中导入反应, {Component};\n\n类定时器扩展组件{\n  构造函数(道具){\n    超级(道具);\n    这.状态= {\n      分钟:25日\n      秒:0,\n \t      关:假\n    };\n  }\n\n  开始时间(){\n    控制台.日志(“启动计时器.');\n  }\n\n  stop计时器 () {\n    控制台.日志(“停止计时器.');\n  }\n\n reset计时器 () {\n    控制台.日志(“重置计时器.');\n  }\n\n  render = () => {\n    return 
;\n };\n}\n\n导出默认定时器;\n
\n\n

这应该能通过测试 应该 呈现一个

计时器.规范.js 文件,但测试 不应该 渲染计时器组件,因为我们还没有在应用组件中添加计时器组件.

\n\n

我们将在 应用程序.jsx 像这样的文件:

\n\n
从“反应”中导入反应;\n进口的./应用程序.css ';\n导入定时器../定时器/计时器”;\n\nconst 应用程序 = () => (\n  
\n <计时器 />\n
\n);\n\n导出默认应用程序;\n
\n\n

现在所有的测试都应该通过了.

\n\n

添加定时器CSS

\n\n

我们将添加与计时器相关的CSS变量,并为较小的设备添加媒体查询.

\n\n

更新文件 指数.css 如下:

\n\n
:根{\n\t——timer-background-color: # FFFFFF;\n——timer-border: 1px solid #000000;\n\t——timer-height: 70%;\n\t——timer-width: 70%;\n}\n\nBody, div, p {\n\t保证金:0;\n\t填充:0;\n}\n\n@media screen 和 (max-width: 1024px) {\n\t:根{\n\t\t——timer-height: 100%;\n\t\t——timer-width: 100%;\n}\n}\n
\n\n

同时,我们要创建 计时器.css 目录下的文件 组件/计时器:

\n\n
.timer-容器 {\n\tbackground - color: var(——timer-background-color);\n\t边界:var(——timer-border);\n\t高度:var(——timer-height);\n\t宽度:var(——timer-width);\n}\n
\n\n

我们必须更新 计时器.jsx 要导入 计时器.css 文件.

\n\n
import 反应, { Component } from \"react\"\nimport \"./计时器.css\"\n
\n\n

如果你现在运行反应应用,你会在浏览器上看到一个带有边框的简单屏幕.

\n\n

编写计时器Button浅渲染测试

\n\n

我们需要三个按钮: 启动、停止, 重置,因此我们将创建 计时器Button组件.

\n\n

首先,我们需要更新 计时器.规范.js 文件以检查是否存在 计时器Button 组件中的 计时器 组件:

\n\n
it(\"应该渲染计时器Button组件的实例吗\", () => {\n    期望(容器.find(\"计时器Button\").长度).toEqual (3)\n  })\n
\n\n

现在,我们加上 计时器Button.规范.js 文件在名为 计时器Buttonsrc /组件 目录,让我们像这样将测试添加到文件中:

\n\n
import 反应 from \"react\"\nimport { shallow } from \"enzyme\"\nimport 计时器Button from \"./ 计时器Button\"\n\ndescribe(\"计时器Button\", () => {\n  让容器\n\n  beforeEach(() => {\n    浅容器\n      <计时器Button\n        buttonAction ={开玩笑.fn()}\n        buttonValue={\"\"}\n      />\n    )\n  })\n\n  it(\"应该 呈现一个 
\", () => {\n 期望(容器.find(\"div\").长度).toBeGreaterThanOrEqual (1)\n })\n})\n
\n\n

现在,如果运行测试,您将看到测试失败.

\n\n

让我们创建 计时器Button.jsx 申请 计时器Button 组件:

\n\n
从“反应”中导入反应;\n从“prop-types”中导入PropTypes;\n\nconst 计时器Button = ({ buttonAction, buttonValue }) => (\n  
\n);\n\n计时器Button.propTypes = {\n buttonAction: proptype.函数.isRequired,\n buttonValue: proptype.字符串.isRequired,\n};\n\n导出默认计时器Button;\n
\n\n

如果你逃跑 npm测试 在这个阶段,测试 应该渲染计时器Button组件的实例吗 但是会失败,因为我们还没有将计时器Button组件添加到计时器组件中.

\n\n

让我们导入 计时器Button 组件并添加三个 计时器Button 中的呈现方法中的组件 计时器.jsx:

\n\n
render = () => {\n    回报(\n      
\n
\n
\n <计时器Button buttonAction ={这.开始时间} buttonValue ={'开始'} />\n <计时器Button buttonAction ={这.stop计时器} buttonValue ={‘停止’} />\n <计时器Button buttonAction ={这.reset计时器} buttonValue ={“重置”} />\n
\n
\n );\n };\n
\n\n

计时器Button CSS

\n\n

现在,该为计时器Button组件添加CSS变量了. 让我们加入变量 :根 范围: 指数.css 文件:

\n\n
:根{\n  ...\n\n  ——button-border: 3px solid #000000;\n  ——button-text-size: 2 em;\n}\n\n@media screen 和 (max-width: 1024px) {\n  :根{\n   \n    …\n\n    ——button-text-size: 4 em;\n  }\n}\n
\n\n

另外,让我们创建一个名为 计时器Button.css计时器Button 目录下的 src /组件 目录:

\n\n
.按钮容器{\n  Flex: 1 1 auto;\n  text-align:中心;\n  边距:0px 20px;\n  边界:var(——button-border);\n  字体大小:var(——button-text-size);\n}\n\n.按钮容器:{徘徊\n  光标:指针;\n}\n
\n\n

让我们来更新 计时器Button.jsx 因此,进口 计时器Button.css 文件和显示按钮值:

\n\n
从“反应”中导入反应;\n从“prop-types”中导入PropTypes;\n进口的./ 计时器Button.css ';\n\nconst 计时器Button = ({ buttonAction, buttonValue }) => (\n  
\n

{buttonValue}

\n
\n);\n\n计时器Button.propTypes = {\n buttonAction: proptype.函数.isRequired,\n buttonValue: proptype.字符串.isRequired,\n};\n\n导出默认计时器Button;\n
\n\n

此外,我们需要更新 计时器.css 为了使三个按钮水平对齐,让我们更新 计时器.css 文件以及:

\n\n
从“反应”中导入反应;\n从“prop-types”中导入PropTypes;\n进口的./ 计时器Button.css ';\n\nconst 计时器Button = ({ buttonAction, buttonValue }) => (\n  
\n

{buttonValue}

\n
\n);\n\n计时器Button.propTypes = {\n buttonAction: proptype.函数.isRequired,\n buttonValue: proptype.字符串.isRequired,\n};\n\n导出默认计时器Button;\n
\n\n

如果你现在运行反应应用,你会看到如下画面:

\n\n
\n \"计时器\"\n
\n\n

重构定时器

\n\n

我们将重构计时器,因为我们想实现这样的函数 开始时间, stop计时器, re开始时间, reset计时器. 让我们来更新 计时器.规范.js 文件:

\n\n
describe('安装定时器', () => {\n  让容器;\n\n  beforeEach(() => (容器 = mount(<计时器 />)));\n\n  it('invokes 开始时间 when the start button is clicked', () => {\n    Const spy = jest.spyOn(容器.实例(),“开始时间”);\n    容器.实例().forceUpdate ();\n    期望(间谍).toHaveBeenCalledTimes (0);\n    容器.找到('.启动时间”).第().模拟('点击');\n    期望(间谍).toHaveBeenCalledTimes (1);\n  });\n\n  it('invokes stop计时器 when the stop button is clicked', () => {\n    Const spy = jest.spyOn(容器.实例(),“stop计时器”);\n    容器.实例().forceUpdate ();\n    期望(间谍).toHaveBeenCalledTimes (0);\n    容器.找到('.stop-timer”).第().模拟('点击');\n    期望(间谍).toHaveBeenCalledTimes (1);\n  });\n\n  it('invokes reset计时器 when the reset button is clicked', () => {\n    Const spy = jest.spyOn(容器.实例(),“reset计时器”);\n    容器.实例().forceUpdate ();\n    期望(间谍).toHaveBeenCalledTimes (0);\n    容器.找到('.reset-timer”).第().模拟('点击');\n    期望(间谍).toHaveBeenCalledTimes (1);\n  });\n});\n
\n\n

如果运行测试,您将看到添加的测试失败,因为我们没有更新 计时器Button 组件没有. 让我们来更新 计时器Button 组件来添加click事件:

\n\n
const 计时器Button = ({ buttonAction, buttonValue }) => (\n  
buttonAction()}>\n

{buttonValue}

\n
\n);\n
\n\n

现在,测试应该通过了.

\n\n

中调用每个函数时的状态,我们将添加更多的测试 安装定时器 测试用例:

\n\n
it('应该 change isOn 状态 true when the start button is clicked', () => {\n    容器.实例().forceUpdate ();\n    容器.找到('.启动时间”).第().模拟('点击');\n    期望(容器.实例().状态.关).toEqual(真正的);\n  });\n\n  it('应该 change isOn 状态 false when the stop button is clicked', () => {\n    容器.实例().forceUpdate ();\n    容器.找到('.stop-timer”).第().模拟('点击');\n    期望(容器.实例().状态.关).toEqual(假);\n  });\n\n  it('应该 change isOn 状态 false when the reset button is clicked', () => {\n    容器.实例().forceUpdate ();\n    容器.找到('.stop-timer”).第().模拟('点击');\n    期望(容器.实例().状态.关).toEqual(假);\n    期望(容器.实例().状态.分钟).toEqual (25);\n    期望(容器.实例().状态.秒).toEqual (0);\n });\n
\n\n

如果您运行测试,您将看到它们失败,因为我们还没有实现每个方法. 那么让我们实现每个函数来通过测试:

\n\n
开始时间(){\n    这.设置状态({isOn: true});\n  }\n\n  stop计时器 () {\n    这.设置状态({关:假});\n  }\n\n  reset计时器 () {\n    这.stop计时器 ();\n  这.设置状态({\n      分钟:25日\n      秒:0,\n    });\n}\n
\n\n

如果运行测试,您将看到测试通过.\n现在,让我们实现剩下的函数 计时器.jsx:

\n\n
从' 反应 '中导入反应, {Component};\n进口的./计时器.css ';\n导入计时器Button../ 计时器Button 计时器Button ';\n\n类定时器扩展组件{\n  构造函数(道具){\n    超级(道具);\n    这.状态= {\n      分钟:25日\n      秒:0,\n      关:假的,\n    };\n\n    这.开始时间 = 这.开始时间.bind ();\n    这.stop计时器 =这个.stop计时器.bind ();\n    这.reset计时器 = 这.reset计时器.bind ();\n  }\n\n  开始时间(){\n    如果(这.状态.isOn === true) {\n      返回;\n    }\n    这.myInterval = setInterval(() => {\n      Const{秒,分钟}=这个.状态;\n\n      if (seconds > 0) {\n        这.setState(({ seconds }) => ({\n          秒:秒- 1;\n        }));\n      }\n      If (seconds === 0) {\n        If (minutes === 0) {\n          clearInterval(这.myInterval);\n        } else {\n          这.setState(({ minutes }) => ({\n            分钟:分钟- 1;\n            秒:59,\n          }));\n        }\n      }\n    }, 1000);\n    这.设置状态({isOn: true});\n  }\n\n  stop计时器 () {\n    clearInterval(这.myInterval);\n    这.设置状态({关:假});\n  }\n\n  reset计时器 () {\n    这.stop计时器 ();\n    这.设置状态({\n      分钟:25日\n      秒:0,\n    });\n  }\n\n  render = () => {\n    Const {minutes, seconds} = 这.状态;\n\n    回报(\n      
\n
\n {minutes}:{seconds < 10 ? ' 0${seconds} ': seconds}\n
\n
\n <计时器Button\n className=\"start-timer\"\n buttonAction ={这.开始时间}\n buttonValue ={'开始'}\n />\n <计时器Button\n className=\"stop-timer\"\n buttonAction ={这.stop计时器}\n buttonValue ={‘停止’}\n />\n <计时器Button\n className=\"reset-timer\"\n buttonAction ={这.reset计时器}\n buttonValue ={“重置”}\n />\n
\n
\n );\n };\n}\n\n导出默认定时器;\n
\n\n

您将看到所有功能都是基于我们之前准备的用户描述工作的.

\n\n

\"计时器\"

\n\n

这就是我们如何使用TDD开发一个基本的反应应用程序. 如果用户描述和验收标准更详细, 可以更精确地编写测试用例, 从而贡献更多.

\n\n

结束

\n\n

在使用TDD开发应用程序时, 不仅要将项目分解为史诗或用户故事,还要为验收标准做好准备,这一点非常重要. 在本文中, 我想向您展示如何分解项目,并为反应 TDD开发使用准备好的验收标准.

\n\n

尽管有很多与反应 TDD相关的资源, 我希望这篇文章能够帮助您了解一些使用用户故事使用反应进行TDD开发的知识. 如果您选择模仿这种方法,请参考完整的源代码 在这里.

\n","as":"div","isContentFit":true,"sharingWidget":{"url":"http://7zb.healthydairyland.com/react/tdd-react-user-stories-to-development","title":"Mastering 反应 Test-driven Development","text":null,"providers":["linkedin","推特","脸谱网"],"gaCategory":null,"domain":{"name":"developers","title":"工程","vertical":{"name":"developers","title":"开发人员","publicUrl":"http://7zb.healthydairyland.com/developers"},"publicUrl":"http://7zb.healthydairyland.com/developers/blog"},"hashtags":"反应,反应TDD,TDD"}}