Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React单元测试最佳实践与前端TDD #230

Open
EthanLin-TWer opened this issue Jan 14, 2024 · 10 comments
Open

React单元测试最佳实践与前端TDD #230

EthanLin-TWer opened this issue Jan 14, 2024 · 10 comments

Comments

@EthanLin-TWer
Copy link
Owner

EthanLin-TWer commented Jan 14, 2024

本Github issue主要是为了方便留言。最新文章内容请移步我的博客(需要能够同时访问Github和jsDelivr,不便之处抱歉了…)

https://ethan.thoughtworkers.me/#/post/2023-12-10-react-unit-testing-best-practices-v2

@WanQuanXie
Copy link

地址 404 了诶

@WanQuanXie
Copy link

修好啦。其实是build的时候不知道为啥把所有静态资源都删了😂重新build了一下就可以了。

😘

@EthanLin-TWer EthanLin-TWer changed the title React系列(二):单元测试最佳实践 React系列(二):单元测试最佳实践与前端TDD Jan 17, 2024
@EthanLin-TWer EthanLin-TWer changed the title React系列(二):单元测试最佳实践与前端TDD React单元测试最佳实践与前端TDD Jan 17, 2024
@EthanLin-TWer
Copy link
Owner Author

最新内容上线了朋友们 @JimmyLv @WanQuanXie 快来围观!哈哈哈,都4202年了,还有人在写React吗

@dukeluo
Copy link

dukeluo commented Jan 18, 2024

@EthanLin-TWer 谢谢分享,干货很多!本文提倡从最上层的 React 组件入手,除了 mock 三方依赖之外,通过贯穿整个应用的单元测试策略来进行测试。有时候某些 ui-components, Domain Logic 足够复杂,我想本文测试策略是不是还不够覆盖到每个细节🤔(所以在你的示例代码中有对 ui-components 层的额外单元测试 ),我有个疑问,我们应该如何平衡「贯穿整个应用」和「额外为某一层增加测试」之间的关系,让我们整个系统的单元测试是充分的而又不是重叠的?我们应该对哪些层额外添加测试,哪些层放在 routes 层一并测试?

另外,在对 ui-components 层的单元测试中,有调用放在 routes 层级下的 tester import { findCounter } from '../../../routes/__tests__/component-testers/counter.tester',是不是有一种打破分层的感觉 👀

@EthanLin-TWer
Copy link
Owner Author

EthanLin-TWer commented Jan 18, 2024

另外,在对 ui-components 层的单元测试中,有调用放在 routes 层级下的 tester import { findCounter } from '../../../routes/__tests__/component-testers/counter.tester',是不是有一种打破分层的感觉 👀

@dukeluo 先回复个简单的。点个赞,代码看得真细!是的,这里我有点偷懒了,其实我在想整个routes/__tests__/下的api-mocks/business-testers/component-testers/fixtures/都挪到一个公共的地方去(比如test/根目录下)会更好一些,这样就可以解除这个不恰当的依赖关系。

只能在代码demo里改了,博客代码和截图重新弄effort太高了哈哈哈🚬

@EthanLin-TWer
Copy link
Owner Author

EthanLin-TWer commented Jan 18, 2024

我们应该如何平衡「贯穿整个应用」和「额外为某一层增加测试」之间的关系,让我们整个系统的单元测试是充分的而又不是重叠的?我们应该对哪些层额外添加测试,哪些层放在 routes 层一并测试?

@dukeluo 把“额外为某一层增加测试”这个问题拆解来看,我想最主要的是关于React Hooks的:什么时候使用这种page tests直接测,什么时候把一些逻辑放到React Hooks里头做单测?这也是我下一篇React Hooks最佳实践与面向对象目前还卡住的地方。

为什么单提React Hooks呢?因为其他的层级似乎已经有相对成熟的答案了:

  • utils:比如时间、格式转换这种的。因为这种单测没难度,我相信一直以来项目上的小伙伴也都会针对这些utils做更细致的测试覆盖。
  • ui-components:这个我设想应该放的是每个项目自己(要么基于现有库封装、要么自己从新写)搞的一套通用UI组件。那么这些组件自然也需要对它的逻辑功能做更全面的覆盖,该补就补。比如,我的业务只提到可以选时间,但是作为一个完整的Date(Range)Picker组件会有更多或一致性或功能性的要求,比如不能允许end date选得比start date还早啦,之类不一定在AC里体现的要求,这些就需要额外增加测试去覆盖。这层也没疑问。
  • redux/数据管理:这部分我的实践经验就是,如果reducer里有重逻辑或者分支那自然推荐测一测。如果没有,也不建议测试,因为单测action有没有发对、单个reducer有没有搞对其实没啥意思,有点在帮忙测redux的感觉;其次是这些测试是特定于redux这个“技术实现”的,一旦换工具(在以往的项目上真实发生)这些测试都得rework。性价比不高。通过page tests拉到一起测起来就可以了。
  • 还有什么组件?欢迎探讨。

所以说我现在直觉本质问题还是有些业务逻辑是写单独的React Hooks单测还是一样通过page tests覆盖,是吧?这其实是个性价比问题。我现在还没有成熟的想法或指导原则,再想想。不过目前做法就是page tests能测的话就它测问题不大,React hooks想增加单测也可以,只要够稳定,否则还要平衡改动成本。测试之间有一些重叠也可以接受,因为page tests更像发现问题的测试,而React hooks是定位问题的测试,目的不同,可以共存。

@dukeluo
Copy link

dukeluo commented Jan 18, 2024

所以说我现在直觉本质问题还是有些业务逻辑是写单独的React Hooks单测还是一样通过page tests覆盖,是吧?

@EthanLin-TWer 是的, React Hooks 以及一些 Domain Object,期待你的下一篇 :)

@JosephYao
Copy link

非常好的文章,感谢分享 👍 文中观点我都非常认同,而且你通过示例代码来说明的过程也很清晰,学习到了。
我自己也写前端,没你那么专业,只是想做到全栈开发而已。我用的是VueJS 2,因为项目中会写UI端到端测试的缘故,所以前端的ut最近写的不多,只有遇到复杂的ui交互逻辑才会写。当然写的时候,和你推荐的方法一样,从页面组件入手,只会mock掉api,同样也能做到tdd的效果。

提一个小的建议哈。那个 hotels list 下面 should call search endpoint with correct parameters: city id, check dates in yyyy-MM-dd, and no. of occupancies 的测试,我觉得有个地方可以改进一下。就是把BeforeEach中的 api mock 数据写到测试中去,并且加上具体的属性值,以便和 expect中验证的数据形成对应关系。大致代码修改如下:

it('should render available hotels once loaded with correct information:' +
    'hotel name, address, stars, user rating, number of user ratings and lowest price', () => {
    hotelListPageDSL.mockGetHotelListOnce([
        createHotel(hotelMocks[0], { name: '杭州栖湖轻奢酒店', ... }),
        createHotel(hotelMocks[0], { name: '杭州中山西子湖酒店', ... })
      ])

    renderHotelList(
      <HotelList />,
      '/hotels/list?city=HZ&checkinDate=2024-01-20&checkoutDate=2024-01-28&noOfOccupancies=2'
    )

    expect(getHotelList()).toEqual([
      ['杭州栖湖轻奢酒店', '西湖湖滨商圈', '★★★★', '用户评分:4.2', '930条点评', '¥198起'],
      ['杭州中山西子湖酒店', '西湖湖滨商圈', '★★★★★', '用户评分:4.7', '317条点评', '¥498起'],
    ])
  })

正如你文中提到的,测试要表达力强。我认为测试中体现准备数据(这里就是 api 返回的hotel数据)和验证数据之间的关系是非常重要的。原来的测试给人一种感觉,就是我知道要验证什么结果,但是我看不出来”为什么“是这样的结果。
其实,你在下个测试里面就给出了准备数据的环节 hotelListPageDSL.mockGetHotelListOnce(createHotel(hotelMocks[9], { name: '杭州华辰国际饭店', noOfUserRatings: 96 })])。我想或许你在上个测试中把准备数据放到BeforeEach有你的考量,愿闻其详 😄

最后,我尤其认同文中提到构筑测试代码 DSL的做法。我的伙伴 @leeonky 给端到端测试开发了一些用于准备数据和验证数据的DSL 的开源库。如果你有兴趣看看给点反馈的话,非常欢迎哈。链接这里就不贴了,不然太喧宾夺主,可以私聊 😄

@EthanLin-TWer
Copy link
Owner Author

EthanLin-TWer commented Jan 29, 2024

我想或许你在上个测试中把准备数据放到BeforeEach有你的考量,愿闻其详 😄

正如你所说,测试中体现准备数据和验证数据之间的关系是非常重要的。在第一个测试中我没有把完整的测试数据摆出来,不是非常刻意的选择,不过潜意识里我是觉得全部数据放上来,测试数据的准备部分就变长了,可能会触发linter工具的自动换行,从而让测试的given部分变得冗长、失去重点。所以trade off的方法是,我给这个测试数据起了个名exampleTwoHotels,以表示这是基准的测试数据,所有测试都会用它,所以读者可以不必太关注这里面的值。以此在“为什么是这个断言数据”的表达性和“given数据”的表达性中做个取舍。

最后,我尤其认同文中提到构筑测试代码 DSL的做法。我的伙伴 @leeonky 给端到端测试开发了一些用于准备数据和验证数据的DSL 的开源库。如果你有兴趣看看给点反馈的话,非常欢迎哈。链接这里就不贴了,不然太喧宾夺主,可以私聊 😄

👍这里就是个大家交流的地方,不用客气可以直接贴链接嘛,哈哈。

最后感谢阅读以及花时间给我写一些反馈! @JosephYao

@JosephYao
Copy link

多谢你的回复哈。我们有两个核心的开源库,一个涉及到数据准备 https://github.com/leeonky/jfactory , 另一个则是用来做验证的 https://github.com/leeonky/DAL-java。目的就是让given和then的能够通过dsl写的更加简单,突出重点,忽略测试中那些不必要的细节。而这些对单元测试和端到端测试都是适用的。

不过,这两个库都是java写的,暂时没有其他语言的版本,对于前端js开发来说,只能算是个参考了 😄 。围绕这两个核心库还有一些周边的辅助库,比较多,先就不列举了。

期待你的反馈哈,也同样期待你后续的文章 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants