单元测试是一个很好的学科,可以导致生产虫密度降低40%-80%。单元测试还有其他几个重要的好处:

但有些事情比其他事情更容易进行单元测试。具体来说,单元测试非常有用pure functions:给定相同输入的函数,始终返回相同的输出,并且没有副作用。

通常,UI组件不属于易于单元测试的那类事物,这使得更难坚持TDD的规则:首先编写测试。

首先编写测试对于实现我列出的一些好处是必要的:架构改进,更好的开发人员体验设计以及在开发应用程序时更快的反馈。培训自己使用TDD需要纪律和实践。许多开发人员在编写测试之前更喜欢修补,但如果你不先编写测试,就会剥夺自己很多单元测试的最佳功能。

不过,值得练习和训练。带有单元测试的TDD可以训练您编写UI组件,这些组件更简单,更易于维护,并且更易于与其他组件组合和重用。

我的测试学科最近的一项创新是开发RITEway单元测试框架,这是一个微小的包装Tape这有助于您编写更简单,更易于维护的测试。

无论您使用什么框架,以下提示将帮助您编写更好,更可测试,更易读,更可组合的React组件:

纯组件是一个组件,在给定相同的道具的情况下,它总是呈现相同的UI,并且没有副作用。例如,

这些组件通常很容易测试。你需要一种方法来选择组件(在这种情况下,我们选择的是greeting className),你需要知道预期的输出。要编写纯组件测试,我使用render-component from RITEway.

要开始使用,请安装RITEway:

在内部,RITEway使用react-dom/server renderToStaticMarkup()并将输出包装在一个Cheerio对象易于选择。如果您不使用RITEway,您可以手动执行所有操作以创建自己的函数,以将React组件呈现为可以使用Cheerio查询的静态标记。

一旦你有一个渲染函数从你的标记生成一个Cheerio对象,你可以编写如下的组件测试:

但那不是很有趣。如果您需要测试有状态组件或具有副作用的组件,该怎么办?这就是TDD对React组件非常有趣的地方,因为这个问题的答案与另一个重要问题的答案相同:“我怎样才能使我的React组件更易于维护和调试?”

答案:从您的演示文稿组件中隔离您的状态和副作用。您可以通过将状态和副作用管理封装在容器组件中,然后通过props将状态传递到纯组件中来实现。

但是钩子API是不是因为我们可以拥有扁平的组件层次结构而忘记所有组件嵌套的东西?嗯,不太好。将代码保存在三个不同的存储桶中仍然是一个好主意,并使这些存储桶彼此隔离:

根据我的经验,如果您将显示/ UI问题与程序逻辑和副作用分开,它会让您的生活更轻松。对于我来说,这个经验法则始终适用于我曾经使用的每种语言和每个框架,包括React with hooks。

让我们通过构建一个点击计数器来演示有状态的组件。首先,我们将构建UI组件。它应该显示类似“Clicks:13”的内容,告诉您单击按钮的次数。按钮只会说“点击”。

显示组件的单元测试非常简单。我们真的只需要测试按钮是否被渲染(我们不关心标签所说的内容 - 根据用户的语言环境设置,它可能会说不同语言中的不同内容)。我们do想确保显示正确的点击次数。让我们编写两个测试:一个用于按钮显示,另一个用于正确重新插入的点击次数。

当使用TDD时,我经常使用两个不同的断言来确保我已经编写了组件,以便从props中提取适当的值。可以编写测试,以便您可以对函数中的值进行硬编码。为了防范这种情况,您可以编写两个测试,每个测试测试不同的值。

在这种情况下,我们将创建一个名为的组件<ClickCounter>,该组件将具有点击计数的道具,称为clicks。要使用它,只需渲染组件并设置clicks支持您希望它显示的点击次数。

让我们看看一对单元测试,它们可以确保我们从道具中提取点击次数。让我们创建一个新文件,click-counter/click-counter-component.test.js:

我喜欢创建一些小工厂函数,以便更容易编写测试。在这种情况下,createCounter将需要多次点击才能注入,并使用该点击次数返回呈现的组件:

通过编写测试,是时候创建我们的ClickCounter显示组件。我用我的测试文件在同一个文件夹中放置了我的名字,click-counter-component.js。首先,让我们编写一个组件片段并观察我们的测试失败:

如果我们保存并运行我们的测试,我们将得到一个TypeError,目前触发NodeUnhandledPromiseRejectionWarning- 最终,Node将停止使用额外段落的恼人警告DeprecationWarning and just throw an UnhandledPromiseRejectionError相反。我们得到了TypeError因为我们的选择回归null,我们正试图跑.trim()在上面。让我们通过渲染预期的选择器来解决这个问题:

大。现在我们应该有一个通过测试,一个失败的测试:

要修复它,将计数作为道具,并使用JSX中的实时道具值:

现在我们的整个测试套件正在通过:

是时候测试按钮了。首先,添加测试并观察它失败(TDD样式):

这会导致测试失败:

现在我们将实现点击按钮:

测试通过:

现在我们只需要实现状态逻辑并挂钩事件处理程序。

我要向您展示的方法对于点击计数器可能有点过分,但大多数应用程序远比点击计数器复杂。状态通常保存到数据库或在组件之间共享。 React社区中流行的副词是从本地组件状态开始,然后根据需要将其提升到父组件或全局应用程序状态。

事实证明,如果使用纯函数启动本地组件状态管理,则以后可以更轻松地管理该过程。由于这个和其他原因(如React生命周期混乱,状态一致性,避免常见错误),我喜欢使用纯reducer函数实现我的状态管理。对于本地组件状态,您可以导入它们并应用useReducer React hook.

如果您需要解除由Redux等州经理管理的状态,那么在您开始之前就已经有了一半:单元测试等等。

首先,我将为状态缩减器创建一个新的测试文件。我将它放在同一个文件夹中,但使用不同的文件。我叫这个click-counter/click-counter-reducer.test.js:

我总是从断言开始,以确保reducer将产生有效的初始状态。如果您以后决定使用Redux,它将调用每个reducer没有状态,以便为商店生成初始状态。这也使得在您需要单元测试或初始化组件状态时,可以非常轻松地创建有效的初始状态。

当然,我们需要创建一个相应的reducer文件。我在叫它click-counter/click-counter-reducer.js:

我首先只是导出一个空的reducer和action creator。要了解有关动作创建者和选择者等重要角色的更多信息,请阅读“更好的Redux架构的10个技巧”。我们现在不打算深入研究React / Redux架构模式,但是理解这个主题对于理解我们在这里做的事情还有很长的路要走,即使你不打算使用Redux库。

首先,我们将观察测试失败:

现在让我们进行测试通过:

初始值测试现在将通过,但是是时候添加更有意义的测试了:

观察测试失败(两者都返回0什么时候应该回来1 and 4, 分别)。然后实现修复。

请注意,我正在使用click()action creator作为reducer的公共API。在我看来,您应该将reducer视为您的应用程序不直接与之交互的东西。相反,它使用动作创建器和选择器作为reducer的公共API。

我也没有为动作创建者和选择者编写单独的单元测试。我总是和减速机一起测试它们。测试reducer是测试动作创建者和选择器,反之亦然。如果您遵循此经验法则,您将需要更少的测试,但仍然可以获得与单独测试时相同的测试和案例覆盖率。

现在所有单元测试都将通过:

再多一步:将我们的行为连接到我们的组件。我们可以用容器组件来做到这一点。我会打电话给你index.js并将其与其他文件共存。它应该看起来像这样:

而已。该组件唯一的工作是连接我们的状态管理并将状态作为道具传递给我们经过单元测试的纯组件。要测试它,请在浏览器中加载应用程序,然后单击单击按钮。

到目前为止,我们还没有在浏览器中查看组件或完成任何类型的样式。为了澄清我们正在计算的内容,我将添加一个标签和一些空间ClickCounter零件。我也会联系上onClick功能。现在代码看起来像这样:

所有单元测试仍然通过。

那么容器组件的测试怎么样?我没有对容器组件进行单元测试。相反,我使用功能测试,它在浏览器中运行并模拟用户与实际UI的交互,端到端运行。您需要在应用程序中进行两种测试(单元和功能),并且对容器组件进行单元测试(主要是连接/接线组件,如上面连接减速器的那些组件)对于我的口味的功能测试来说太多余了,并不是特别容易进行适当的单元测试。通常,您必须模拟各种容器组件依赖关系才能使它们工作。

与此同时,我们对所有不依赖于副作用的重要单元进行了单元测试:我们正在测试是否呈现了正确的数据以及正确管理状态。您还应该在浏览器中加载组件,并亲自查看按钮是否正常工作以及UI是否响应。

为React实现功能/ e2e测试与为任何其他框架实现它们相同。我不会在这里讨论它们,但请查看TestCafe, TestCafe Studio and Cypress.io没有Selenium舞蹈的e2e测试。

单元测试反应组件