美文网首页
React测试相关

React测试相关

作者: 初漾流影 | 来源:发表于2020-10-18 18:17 被阅读0次

最近在写业务代码测试时候,对如何写规范的测试产生了兴趣,下面是一点学习心得。

React组件测试

React组件的测试,选择的测试工具一般是官方测试工具库和Enzyme。
React组件有两种存在方式:虚拟DOM对象和真是DOM节点。针对这两种形式,官方测试工具库对这两种方式,有不同测试API。

 shallow rendering: 测试虚拟DOM
 DOM rendering: 测试真实DOM

Enzyme库shallow,render和mount方法

Enzyme 是 Airbnb 开源的专为 React 服务的测试框架。

shallow方法是对官方测试工具库Shallow Rendering的封装。Shallow Rendering是将一个组件渲染成虚拟DOM,并且只渲染第一层,不渲染子组件,所以渲染速度很快。优点:渲染速度快。

render方法将组件解析为一段静态HTML字符串。

mount方法将组件完全解析成真实DOM结构。

为什么要写单元测试

保障质量,方便重构,避免需求增加后,代码腐化。

什么是好的单元测试

测试一般遵循give-when-then 的结构,这样写出的测试代码结构清晰,易于理解。
比如

// production code
const computeTotalAmount = (products) => {
  return products.reduce((total, product) => total + product.price, 0); 
}

// testing code
it('should return summed up total amount 1000 when there are three products priced 200, 300, 500', () => {
  // given - 准备数据
  const products = [
    { name: 'nike', price: 200 },
    { name: 'adidas', price: 300 },
    { name: 'lining', price: 500 },
  ]

  // when - 调用被测函数
  const result = computeTotalAmount(products)

  // then - 断言结果
  expect(result).toBe(1000)
})

好的测试的几条原则

  • 只关注输入输出,不关注内部实现
    简单来说,就是只要测试输入不变,输出也不变。这是重构的基础。因为重构的定义是在不改变软件外部可观测行为的基础上,调整软件内部的实现。
    对于去mock外部依赖,其实也是关注内部实现。因为mock失败了,测试也会挂,但其实并不是真实业务逻辑出问题了,建议少用mock。

  • 只测试一条分支
    简单来说,就是只测试一个业务场景,对应在任务拆解中一个细粒度的task,这样做的好处是轻量,写起来快,可以给测试一个很详细的描述。

  • 表达力极强

  1. 测试描述,当遵循只测试一条分支原则时,一般测试描述就能给出具体的业务上下文,这样看测试的人,通过测试就能获取一些业务的导入。
  2. 测试数据,测试数据不应该包含与本次测试无关的数据,只准备满足所测场景的最小数据。
  3. 清晰的断言,测试失败时候,能给出期望的数据与实际数据具体差异。
  • 测试不包含逻辑
    一般模式都是:准备数据->调用函数->断言。如果包含了逻辑,一是增加了阅读负担,而是测试挂了,不知道是实现挂了,还是测试本身挂了。

  • 运行速度快
    只有单元测试运行快,他才是快速获取反馈的一种手段。
    如何才能尽量让测试运行更快,有一些策略:
    1.把耗时的操作能不能挪到更高层级的测试中,比如与第三方系统集成。
    2.尽可能避免依赖。

React component测试

React component的拆分应遵循单一职责,分为以下几类:

  • 通用UI组件
  • 功能组件
  • 展示型业务组件
  • 容器型业务组件

功能型组件,指的是跟业务无关的另一类组件,它是功能型的,更像是底层支撑着业务组件运作的基础组件,比如路由组件、分页组件等。这些组件一般逻辑多一点,UI 部分少一些。
组件的测试一般遵循 shallow -> find(Component) -> 断言的三段式
那React component应该测什么,不该测什么?

  • 分支渲染逻辑必须侧
  • 事件调用和参数传递必须侧
  • 渲染出来的UI一般不放在单元测试
    因为分支渲染和事件调用,有业务价值和文档作用。添加测试能方便重构,也能做文档参考。
    单元测试一般不测纯UI,因为去获取一些DOM节点,不好加断言,写测试成本较高。UI测试一般由自动化测试监控,比如可以用backstopjs去做image compare

举个例子

  1. 业务组件-分支渲染测试
export const CommentsSection = ({ comments }) => (
  <div>
    {comments.length > 0 && (
      <h2>Comments</h2>
    )}

    {comments.map((comment) => (
      <Comment content={comment} key={comment.id} />
    )}
  </div>
)

第一个测试用例是,如果没有comment,则不渲染comment header和comment内容

import { CommentsSection } from './index'
import { Comment } from './Comment'

test('should not render a header and any comment sections when there is no comments', () => {
  const component = shallow(<CommentsSection comments={[]} />)

  const header = component.find('h2')
  const comments = component.find(Comment)

  expect(header).toHaveLength(0)
  expect(comments).toHaveLength(0)
})

第二个测试用例,如果有comment,则正确渲染comment header和comment内容

test('should render a comments section and a header when there are comments', () => {
  const contents = [
    { id: 1, author: 'test user 1', comment: 'test comment 1' },
    { id: 2, author: 'test user 2', comment: 'test comment 1' },
  ]
  const component = shallow(<CommentsSection comments={contents} />)

  const header = component.find('h2')
  const comments = component.find(Comment)

  expect(header.html()).toBe('Comments')
  expect(comments).toHaveLength(2)
})

2.业务组件-事件调用
测试场景是:当某条产品被点击时,应该将产品相关的信息发送给埋点系统进行埋点。

export const ProductItem = ({
  id,
  productName,
  introduction,
  trackPressEvent,
}) => (
  <TouchableWithoutFeedback onPress={() => trackPressEvent(id, productName)}>
    <View>
      <Title name={productName} />
      <Introduction introduction={introduction} />
    </View>
  </TouchableWithoutFeedback>
)

测试内容:模拟产品点击事件,相关函数被调用,并能传递正确的参数。

import { ProductItem } from './index'

test(`
  should send product id and name to analytics system 
  when user press the product item
`, () => {
  const trackPressEvent = jest.fn()
  const component = shallow(
    <productitem id={100832}
      introduction="iMac Pro - Power to the pro."
      trackPressEvent={trackPressEvent}></productitem>
  )

  component.find(TouchableWithoutFeedback).simulate('press')

  expect(trackPressEvent).toHaveBeenCalledWith(
    100832,
    'iMac Pro - Power to the pro.'
  )
})

可以发现,这些测试还是依赖了一些组件内部实现,比如find TouchableWithoutFeedback组件,这些无法避免。因为组件本质是渲染树,要获取点击节点,必须通过组件名,className等选择器。测试时尽量少暴露组件细节。

对纯函数可以用参数化测试

纯函数就是一个输入对应固定的输出,没有任何外部依赖。
比如

test.each([
    ['abc@hotmail.com', true, 'should return true given correct toEmail format'],
    ['abc@hotmail.com,um_agt@outlook.com', true, 'should return true given correct toEmail format'],
    ['abc,abc@hotmail.com', false, 'should return false given wrong toEmail format'],
    ['abc@hotmail.com,abc@hotmail.,um_agt@outlook.com', false, 'should return false given wrong toEmail format'],
  ])(
      'should return correct boolean value given email address',
      (expected, input, description) => {
        expect(isCorrectEmailFormat(input)).toEqual(expected)
      }
  )

参数化测试方便准备数据,测试用例在一个地方,清晰易读。

相关文章

网友评论

      本文标题:React测试相关

      本文链接:https://www.haomeiwen.com/subject/zazxmktx.html