Hey,你好,我是 JimmyLv 吕靖,这是你开始 前端 TDD 练习的第 20 天,昨天你已经对 ShoppingCart 项目中的会用到的组件进行了拆分,并且基于 E2E 层面的测试已经开始实现需求了,有可能你已经遇到了不少问题。等一等,今天我想告诉你的是,组件级别的单元测试驱动开发才是最核心、最重点的 TDD 实践。
既然单元测试应该由开发者,在开发软件的同时编写对应的单元测试。它应该是内建的,而不是后补的:即在编写实现的同时完成单元测试,而不是写完代码再一次性补足。测试先行,这正是 TDD(测试驱动开发)的做法。使用 TDD 开发方法是得到可靠单元测试的唯一途径。
测试很难补,其实补出来的测试几乎不可能完整覆盖我们对重构和质量的要求。TDD 和单元测试是全有或全无:不做 TDD,难以得到好的单元测试;TDD 是获得可靠的单元测试的的唯一途径。除此之外别无捷径,想抛开 TDD 而获得一个好的单元测试是迷思,难以成功,接下来你就将练习如何从组件级别使用正确的单元测试,驱动出合理的组件接口设计。
Testing Library 会帮助你测试单个组件的行为,通过测试驱动开发所有的组件 props,单元测试的重点也就在于用户如何与该组件进行交互,最终使得整个 E2E 测试通过。
import React from 'react'
import { render } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import ProductList from './ProductList'
import fakeProducts from '../fixtures/product'
describe('<ProductList /> Component', () => {
test('should show empty content when no products', () => {
const comp = render(<ProductList products={[]} />)
expect(comp.queryByText('购物车为空')).toBeTruthy()
})
test('should increase product when click "+" button', () => {
// given
const handleProductChange = jest.fn()
const comp = render(<ProductList products={fakeProducts} handleProductChange={handleProductChange} />)
// when
expect(comp.queryByTestId('product-count')).toHaveTextContent('2')
userEvent.click(comp.queryByTestId('increase-count'))
// then
expect(comp.queryByTestId('product-count')).toHaveTextContent('3')
expect(handleProductChange).toBeCalledWith({
code: 'ITEM001',
count: 3,
price: 100,
})
})
test('should decrease product when click "-" button and can not decrease when count is 1', () => {
// given
const handleProductChange = jest.fn()
const comp = render(<ProductList products={fakeProducts} handleProductChange={handleProductChange} />)
// when
expect(comp.queryByTestId('product-count')).toHaveTextContent('2')
userEvent.click(comp.queryByTestId('decrease-count'))
// then
expect(comp.queryByTestId('product-count')).toHaveTextContent('1')
expect(handleProductChange).toBeCalledWith({
code: 'ITEM001',
count: 3,
price: 100,
})
userEvent.click(comp.queryByTestId('decrease-count'))
expect(handleProductChange).not.toBeCalled()
})
})
通常来说,在单元测试中我们希望将重点放在作为独立单元进行测试的组件上,并避免间接断言其子组件的行为。此外,对于包含许多子组件的组件,整个 render 树会变得非常之大,而反复 render 所有的子组件可能会减慢单元测试的速度。
为了维持金字塔形状,一个健康、快速、可维护的测试组合应该是这样的:写许多小而快的单元测试。适当写一些更粗粒度的测试,写很少高层次的端到端测试。注意不要让你的测试变成冰淇淋那样子,这对维护来说将是一个噩梦,并且跑一遍也需要太多时间。(via 测试金字塔实战 – ThoughtWorks 洞见)