你好!
最近我在一次与测试相关的PR讨论中提到了这个问题,@sebmarkbage指出这最好作为一个单独的问题来处理,所以就在这里分享一下。
我们发现,对于测试库来说,RTL(最终一致性)风格的测试并不奏效,我们需要不同的工具。
当测试等待一致的结果时,我们可能会无意中在中间进行20次不一致的重渲染,而我们的测试无法捕捉到这种情况。
虽然在UI中通常没问题,但在库中发生这种情况...每一次我们执行的意外重渲染都会影响成千上万的应用程序,而不一致的渲染会导致大量的用户空间错误。
因此,我们大多数较新的测试都使用组件,并且每次React渲染发生时,我们都会获取快照 - 根据测试的不同,有时我们会获取DOM的快照,有时我们会获取钩子返回值的快照,有时是其他东西。
尤其是在我们的Suspense钩子测试中,有证据证明跟踪哪些组件积极参与特定渲染是非常有价值的。目前跟踪这些组件有点棘手,但我们尽力而为。(请记住,我们在测试一个钩子库,所以每个测试都会定义一堆组件,然后我们测试它们之间的交互 - 我们可以为每个组件添加类似useTrackRender()的东西。)
然后,我们的测试以异步迭代器类似的模式消耗所有这些快照,这样我们就可以对每个渲染做出假设。
以下是一些示例:
- https://github.com/apollographql/apollo-client/blob/25ff067b36bd4ec3833a2d71f74c8aed75e2944f/src/react/hooks/tests/useQuery.test.tsx#L5187-L5216
- https://github.com/apollographql/apollo-client/blob/6b39e3f19ba0944b38c8b2822945dc7ca847731c/src/react/hooks/tests/useSuspenseQuery.test.tsx#L361-L415
- https://github.com/apollographql/apollo-client/blob/6b39e3f19ba0944b38c8b2822945dc7ca847731c/src/react/hooks/tests/useLoadableQuery.test.tsx#L307-L356
再次澄清:我们知道我们离“测试实现细节”非常接近,我永远不会建议任何人像那样测试实际应用程序。但是对于库来说,这种测试方式是一种极好的工具 - 我们可以接受这个缺点,即有些测试需要根据我们正在测试的React版本来测试不同的“正确”行为。
由于您目前正在添加更多API来访问内部信息(如堆栈跟踪),以便在测试期间进行测试,我想问是否是时候请求额外的性能分析工具了。
目前,除了React Devtools之外,任何工具都几乎不可能获取Profiler记录的信息(涉及太多内部信息,而且除此之外,还有许多不必要的进程间通信,这些在测试场景中并不必要) - 但对我们来说,获得这些数据对于这种类型的测试至关重要。
我们能否得到类似于 react-dom/test
renderWithProfilerRecording
(甚至更好的 renderToProfilerIterator
) API?
获取诸如以下信息将非常强大:
- 发生了多少次重渲染
- 导致哪些组件发生重渲染
- 哪些组件参与了哪些重渲染
这将有助于确保最佳的库体验。
6条答案
按热度按时间dxpyg8gm1#
我很想亲自看看这个。实际上这里有几个非常相关的PR:
但是Joe对那个的评论有些令人沮丧:
我们之前作为一个团队讨论过这个问题,我们非常担心长期支持这个的成本。虽然意图只是为了非关键性(对应用程序正确性)的事情,比如性能监控,但不可避免的是应用程序或库最终会依赖这些钩子来实现其核心行为。这会让我们放慢速度,因为这会让许多内部重构变成破坏性的更改,延迟这些重构和它们将解锁的改进。
我们认为正确的第一步是我们在实验室帖子中讨论过的过渡跟踪API。在我们的待办事项列表中。
x33g5p2x2#
是的,这绝对不是什么应该阻碍React前进的事情。如果事情发生了变化,它们就会发生变化。这只会让问题浮出水面。
这个想法更多的是为了确保,如果发布了一个新的React版本,并且我们的其中一个API意外地开始看到额外的渲染,我们可以更快地捕获并修复它,而不是拖到后面。
这当然只针对一个“测试”用例——这也是我为什么建议像
react-dom/test
这样的入口点的原因。ebdffaop3#
你是否探索过将你的测试用例 Package 在
React.Profiler
中,并在渲染回调上进行Assert?I explored that originaly in MUI。这个想法是将每个测试用例都 Package 在React.Profiler
中,并跟踪更改。它看起来很有前途,但我没有以一种有用的方式完成它(p90,提交之间的差异,随时间变化的测量等)。如果你想通过不在这些测试中使用
act
来获得更准确的实际世界结果,你可以查看一下。过渡追踪可以帮助我们获得更详细的见解。尽管如此,也许我们可以只是公开更新器跟踪 React Devtools 已经访问过的("为什么这次渲染了?" 在 Profiler 面板中)。
zzzyeukh4#
你是否探索过将测试 Package 在React.Profiler中并在渲染回调上进行Assert?
我们确实这样做了——我们将测试 Package 在
React.Profiler
中,并且在每个onRender
回调中,我们创建DOM的快照以及额外的测试特定值(钩子返回值等)。我们并没有立即进行Assert,而是将这些值保存到一边,以便在其他时候对它们进行Assert。
如果你想通过不在这些测试中使用act来获得更准确的实际世界结果,你可以尝试一下。
现在我很好奇——不是建议使用act来获得更实际的世界结果吗?
尽管如此,也许我们可以只公开更新器跟踪React Devtools已经访问过的数据。
是的,请让我们从小事做起——完美是“一点也不”的敌人,目前DevTools中有大量的数据是我们无法从测试中直接访问的。获取这些数据本身就已经非常棒了,我们现在不需要更多的东西!
wwtsj6pe5#
我们确实会这样做 - 我们将测试 Package 在React.Profiler中,并在每个onRender回调中,我们创建DOM的快照以及额外的测试特定值(钩子返回值等)。
我们并不会立即进行Assert,而是将这些值保存到一边,以便在其他时候对它们进行Assert。
所以你实际上不感兴趣于性能分析,而是希望进行更细粒度的Assert。我认为你只是想在中间步骤上进行测试,而不是使用性能分析工具。
现在我很好奇 - 建议使用act来获得更真实的结果吗?
act()
在退出之前刷新所有计划好的内容。这不一定是你在实际应用程序中看到的结果,例如React接收到mousemove
事件时,它可能会将状态更新批量提交为单个提交。如果你将两者都 Package 在act
中,你总是会得到两个提交。是的,请开始小步前进 - 完美是“任何事情”的敌人,目前有大量的数据可以供DevTools访问,而我们无法从测试中直接访问这些数据。获得这些数据本身就已经很不可思议了,我们现在不需要更多的东西!
除了这些目前存在漏洞和错误。过渡跟踪仍在进行中,因此我们不应该仅仅暴露更新器跟踪,而是有了一些东西。在React的规模上发布未完成的东西是有代价的。如果它不起作用,需要花费大量工作来移除它,阻碍生态系统的发展。这在内部仓库(如Meta)中有效,因为你有直接访问权限,但无法扩展到具有未知优先级和动机的生态系统。
ar5n3qh56#
act() 在退出之前清除了所有计划好的内容。这不一定是你在现实世界应用程序中会看到的东西,例如 React 接收到鼠标移动事件时,它可能会将状态更新批量提交为单个提交。如果你将两者都用 act 包裹,你总是会得到两个提交。
这是
act
上绝对有用的信息,我从未在其他地方看到过这样的沟通——谢谢你提供这个信息。所以你实际上并不对性能分析感兴趣,而是希望进行更细粒度的Assert。我认为你只是想在不使用 act 的情况下测试,以便在中间步骤上进行Assert,而不是性能分析工具。
我们确实对一些“类似性能分析器”的东西很感兴趣,比如在两次成功渲染之间实际提交了哪些组件,以及渲染完成提交的具体时刻(testing-library 的
waitFor
总是存在“跳过”渲染的风险)。也许我们不需要一个全新的 API,尽管如此——在给予更多思考后,在 onRender 回调中获取一些额外的信息可能更适合这个功能。
不过目前这些都有漏洞和错误。过渡跟踪仍在进行中,所以我们不应该仅仅暴露 updater 跟踪,而是应该有什么东西。在 React 的规模上发布未完成的东西是有代价的。如果它不起作用,需要大量的工作来移除它,阻碍生态系统的发展。这在内部仓库(如 Meta)中有效,因为你有直接访问权限,但无法扩展到具有未知优先级和动机的生态系统。
从 TypeScript 类型来看,似乎过去某个时候有很多额外的信息(
interactions
),但又被移除了——也许对于像Profiler
这样的事情来说,实际上并没有那么糟糕?当然,我理解如果你们需要更多的时间来稳定这个功能——但如果团队能从长远的Angular 考虑这个功能请求,我会非常高兴。