作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
鲍里斯·约达诺夫的头像

鲍里斯Yordanov

Boris主要使用普通JavaScript和最流行的JavaScript框架(如Angular), 反应, 和流星.

以前在

哥本哈根大学
分享

Toptal设计系统的新版本最近发布了,它要求我们对系统中的几乎每个组件进行更改 毕加索,我们的内部组件库. 我们的团队面临着一个挑战:我们如何确保回归不会发生?

毫不奇怪,简短的答案是测试. 有很多测试.

我们不会回顾测试的理论方面,也不会讨论不同类型的测试, 其效用, 或者解释为什么应该首先测试代码. 我们的博客 其他人已经覆盖了 这些话题. 相反,我们将只关注测试的实际方面.

继续阅读,了解Toptal的开发人员如何编写测试. 我们的存储库是公共的,所以我们使用真实的示例. 没有任何抽象或简化.

测试金字塔

我们本身并没有定义一个测试金字塔,但如果我们定义了,它看起来会是这样的:

测试金字塔图

Toptal的测试金字塔说明了我们所强调的测试.

单元测试

单元测试编写简单,易于运行. 如果您几乎没有时间编写测试,那么它们应该是您的首选.

然而,它们并不完美. 不管你选择哪个测试库(在我们的例子中是Jest和反应测试库[RTL]), 它不会有一个真正的DOM,它不允许你在不同的浏览器中检查功能, 但它将允许您剥离复杂性并测试库的简单构建块.

单元测试不仅通过测试代码的行为来增加价值,还通过检查代码的整体可测试性来增加价值. 如果您不能轻松地编写单元测试,那么您很有可能编写了糟糕的代码.

视觉回归测试

即使你有100%的单元测试覆盖率, 这并不意味着这些组件在不同设备和浏览器上看起来都很好.

手工测试尤其难以发现视觉退化. 例如,如果一个按钮的标签移动了1px,则a 质量工程师 甚至通知? 值得庆幸的是,有许多解决方案可以解决这个可见性有限的问题. 您可以选择企业级的一体化解决方案,例如 LambdaTest or Mabl. 你可以加入插件,比如 珀西,以及DIY解决方案,从喜欢 洛基 or 故事书 (这是我们在毕加索之前使用的). 它们都有缺点:有些过于昂贵,而另一些则有陡峭的学习曲线或需要太多的维护.

Happo 拯救! 它是珀西的直接竞争对手, 但是便宜多了, 支持更多浏览器, 而且更容易使用. 另一大卖点? 它支持Cypress集成, 这一点很重要,因为我们想摆脱使用故事书进行视觉测试. 我们发现自己处于这样的情况,我们必须创建故事,这样我们才能确保视觉测试覆盖率, 不是因为我们需要记录那个用例. 这污染了我们的文档,使它们更难以理解. 我们想要将可视化测试与可视化文档隔离开来.

集成测试

即使两个组件有单元测试和可视测试,也不能保证它们能一起工作. 例如, 我们发现了一个错误,工具提示在下拉项中使用时不会打开,但在单独使用时却可以正常工作.

为了保证组件的良好集成,我们使用了Cypress的实验 组件测试功能. 起初,我们不满意 表现不佳,但我们可以用 自定义webpack配置. 结果? 我们能够使用Cypress出色的API来编写性能测试,以确保我们的组件能够很好地协同工作.

运用测试金字塔

这一切在现实生活中是什么样子? 让我们测试一下 手风琴 组件!

您的第一反应可能是打开编辑器并开始编写代码. 我的建议? 花一些时间了解组件的所有特性,并写下您想要涵盖的测试用例.

毕加索组件库演示GIF

测试什么?

下面是我们的测试应该涵盖的情况的分类:

  • -手风琴可以展开和折叠, 其默认状态可配置, 这个功能可以被禁用
  • 风格 手风琴可以有边界变化
  • 内容 -它们可以与图书馆的其他单元集成
  • 定制 -组件可以有它的样式覆盖,可以有自定义展开图标
  • 回调 -每次状态改变时,都可以调用回调

毕加索组件库演示GIF -手风琴组件

如何测试?

既然我们知道了要测试的内容,那么让我们考虑一下如何进行测试. 我们在测试金字塔中有三个选项. 我们希望以金字塔各部分之间最小的重叠实现最大的覆盖. 测试每个测试用例的最佳方法是什么?

  • -单元测试可以帮助我们评估状态是否相应改变, 但是我们还需要视觉测试来确保组件在每个状态下都被正确渲染
  • 风格 -视觉测试应足以发现不同变体的回归
  • 内容 -视觉测试和集成测试的结合是最好的选择, 因为accordion可以与许多其他组件结合使用
  • 定制 -我们可以使用单元测试来验证是否正确应用了类名, 但我们需要一个视觉测试,以确保组件和自定义样式协同工作
  • 回调 单元测试是确保调用正确回调的理想选择

手风琴测试金字塔

单元测试

可以找到完整的单元测试套件 在这里. 我们已经讨论了所有的状态变化 定制和回调:

  it('toggles', async () => {
    const handleChange = jest.fn()

    const {getByText, getbytestd} = render手风琴({
      onChange: handleChange,
      expandIcon: 
    })

    fireEvent.点击(getByTestId (accordion-summary))
    await waitFor(() => expect(getByText(DETAILS_TEXT)).toBeVisible ())

    fireEvent.点击(getByTestId(“触发”))
    await waitFor(() => expect(getByText(DETAILS_TEXT)).不.toBeVisible ())

    fireEvent.点击(getByText (SUMMARY_TEXT))
    await waitFor(() => expect(getByText(DETAILS_TEXT)).toBeVisible ())

    期望(handleChange).toHaveBeenCalledTimes (3)
  })

视觉回归测试

视觉测试位于 这柏树描述块. 截图可以在 Happo的仪表板.

您可以看到记录了所有不同的组件状态、变体和自定义. 每次打开公关, CI比较了屏幕截图 Happo已经存储到你的分支中了:

  it('renders', () => {
    山(
      
        
      
    )
    cy.(身体的).happoScreenshot ()
  })
  it('renders disabled', () => {
    山(
      
        
        } />
      
    )
    cy.(身体的).happoScreenshot ()
  })
  it('renders border variants', () => {
    山(
      
        
        
        
      
    )
    cy.(身体的).happoScreenshot ()
  })

集成测试

我们编写了一个“坏路径”测试 这柏树描述块 断言手风琴仍然可以正常工作,并且用户可以与自定义组件进行交互. 我们也 添加了可视化断言 为了增加自信:

describe('手风琴 with custom summary', () => {
  it('closes and opens', () => {
    山(<手风琴CustomSummary />)
    toggle手风琴 ()
    get手风琴内容 ().('不应该.be.可见”)

    cy.get(“[data-testid = accordion-custom-summary]”).happoScreenshot ()

    toggle手风琴 ()
    get手风琴内容 ().(“应该.可见”)

    cy.get(“[data-testid = accordion-custom-summary]”).happoScreenshot ()
  })
  // …
})

持续集成

毕加索几乎完全依赖 GitHub的行为 对于QA. 此外,我们还添加了 Git钩子 用于阶段性文件的代码质量检查. 我们最近从Jenkins迁移到GHA,所以我们的设置仍处于MVP阶段.

工作流按顺序在远程分支中的每个更改上运行, 集成和可视化测试是最后一个阶段,因为它们的运行成本最高(在性能和货币成本方面)。. 除非所有测试都成功完成,否则无法合并拉取请求.

以下是GitHub的行为每次都要经历的阶段:

  1. 依赖安装
  2. 版本控制 -验证提交的格式和PR标题是否匹配 传统的 提交
  3. 线头 ESlint确保高质量的代码
  4. 打印稿编译 —检查是否有输入错误
  5. 包编译 -如果包不能被构建, then they won’t be released successfully; our Cypress tests also expect compiled code
  6. 单元测试
  7. 集成和视觉测试

可以找到完整的工作流程 在这里. 目前,完成所有阶段只需不到12分钟.

可测试性

像大多数组件库一样, 毕加索有一个根组件,它必须封装所有其他组件,并可用于设置全局规则. 这使得编写测试变得更加困难,原因有两个——测试结果不一致, depending on the props used in the wrapper; and extra boilerplate:

从“@testing-library/react”中导入{render}

describe('Form', () => {
  it('renders', () => {
    Const {container} = render(
      <毕加索 loadFavicon={false} environment='test'>
        
) 期望(容器).toMatchSnapshot () }) })

我们通过创建一个 Testing毕加索 这是全球测试规则的先决条件. 但是必须为每个测试用例声明它是很烦人的. 这就是我们创建a的原因 自定义渲染功能 它将传递的组件封装在Testing毕加索中,并返回RTL渲染函数中可用的所有内容.

我们的测试现在更容易阅读和直接编写:

从“@toptal/picasso/test-utils”中导入{render}

describe('Form', () => {
  it('renders', () => {
    Const {container} = render()

    期望(容器).toMatchSnapshot ()
  })
})

结论

这里描述的设置远非完美, 但对于那些敢于创建组件库的人来说,这是一个很好的起点. 我读过很多关于测试金字塔的书,但在实践中应用它们并不总是那么容易. 因此,我邀请你去探索 我们的代码库 从我们的错误和成功中学习.

组件库是独特的,因为它们服务于两种受众:与UI交互的最终用户和使用您的代码构建自己的应用程序的开发人员. 在健壮的测试框架中投入时间将使每个人受益. 在可测试性改进上投入时间将使您作为维护人员和使用(和测试)您的库的工程师受益.

我们没有讨论代码覆盖、端到端测试、版本和发布策略等问题. 关于这些主题的简短建议是:经常发布, 实践适当的语义版本控制, 你的流程是否透明, 为那些依赖你的图书馆的工程师设定期望. 我们可能会在以后的文章中更详细地讨论这些主题.

了解基本知识

  • 谁负责组件测试?

    组件库有两个用户组:使用您的库构建UI的工程师和与之交互的最终用户. 用户关心好的UI/UX, 而开发人员需要能够在他们的应用程序和组件之间编写集成测试. 作为一个维护者,你必须兼顾双方.

  • 什么是视觉回归测试?

    更改一行代码可能会意外地影响到您没有预料到的功能(回归)。. 有时, 虽然, the regression is only visual; the UI still works, 但它看起来不一样(视觉回归). 单元测试或端到端测试无法捕获错误,但视觉回归测试应该可以.

  • 为什么视觉回归测试很重要?

    在不同版本之间保持UI的一致性对于用户体验来说非常重要. 大多数自动化测试不与UI交互,它们以编程方式操作它. 他们不会注意到按钮是否改变了颜色,或者图片是否溢出了页面. 用户会注意到,视觉回归测试也会注意到.

就这一主题咨询作者或专家.
预约电话
鲍里斯·约达诺夫的头像
鲍里斯Yordanov

位于 春湖,新泽西州,美国

成员自 2017年7月15日

作者简介

Boris主要使用普通JavaScript和最流行的JavaScript框架(如Angular), 反应, 和流星.

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

以前在

哥本哈根大学

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

订阅意味着同意我们的 隐私政策

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

订阅意味着同意我们的 隐私政策

Toptal开发者

加入总冠军® 社区.