单元测试规范
组件开发规范
组件化开发是前端工程化的核心实践之一。良好的组件设计能提升代码复用率、降低维护成本。以下从设计原则、目录结构、代码风格等方面阐述组件开发规范。
设计原则
- 单一职责原则:每个组件只负责一个特定功能。例如按钮组件只处理点击交互,不包含业务逻辑:
// Bad: 混合业务逻辑
function OrderButton() {
const handleClick = () => {
fetch('/api/order').then(...)
}
return <button onClick={handleClick}>Submit</button>
}
// Good: 纯粹UI组件
function Button({ onClick, children }) {
return <button onClick={onClick}>{children}</button>
}
- 受控与非受控:明确区分组件状态管理方式。表单组件通常需要同时支持两种模式:
// 受控组件
<input value={value} onChange={handleChange} />
// 非受控组件
<input defaultValue={initialValue} ref={inputRef} />
目录结构规范
推荐按功能划分的模块化结构:
components/
Button/
index.tsx // 主组件
style.module.css // 样式
types.ts // 类型定义
__tests__/ // 测试目录
Button.test.tsx
Form/
Input/
index.tsx
validate.ts // 验证逻辑
代码风格要求
- Props命名:采用小驼峰命名法,布尔类型属性需加
is
/has
前缀:
interface Props {
isLoading: boolean
maxLength?: number
onComplete: () => void
}
- 样式隔离:推荐CSS Modules或Styled Components:
/* Button.module.css */
.primary {
background: var(--color-primary);
}
import styles from './Button.module.css'
function Button({ primary }) {
return (
<button className={primary ? styles.primary : ''}>
{children}
</button>
)
}
单元测试规范
单元测试是保障组件质量的关键环节。测试覆盖率应至少包含核心功能路径。
测试框架配置
使用Jest + Testing Library组合:
// jest.config.js
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['@testing-library/jest-dom'],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1'
}
}
测试用例设计原则
- 行为导向测试:从用户视角而非实现细节出发:
// Bad: 测试内部state
test('should update state', () => {
render(<Counter />)
expect(screen.getByTestId('count').textContent).toBe('0')
})
// Good: 测试可见行为
test('should increment count on click', async () => {
render(<Counter />)
await userEvent.click(screen.getByRole('button'))
expect(screen.getByText('1')).toBeInTheDocument()
})
- 边界条件覆盖:包括空状态、错误处理等场景:
describe('List component', () => {
it('should render empty state', () => {
render(<List items={[]} />)
expect(screen.getByText('No items found')).toBeVisible()
})
it('should handle null items', () => {
render(<List items={null} />)
expect(screen.getByText('Loading...')).toBeVisible()
})
})
常用测试模式
- 异步操作测试:
test('should load data', async () => {
jest.spyOn(api, 'fetchData').mockResolvedValue({ data: 'mock' })
render(<DataFetcher />)
await waitFor(() => {
expect(screen.getByText('mock')).toBeInTheDocument()
})
})
- 上下文依赖测试:
test('should use theme context', () => {
const theme = { color: 'red' }
render(
<ThemeProvider value={theme}>
<ThemedButton />
</ThemeProvider>
)
expect(screen.getByRole('button')).toHaveStyle({ color: 'red' })
})
测试覆盖率标准
在package.json
中配置最小阈值:
{
"jest": {
"coverageThreshold": {
"global": {
"branches": 80,
"functions": 85,
"lines": 90,
"statements": 90
}
}
}
}
组件文档规范
完善的文档能显著降低组件使用成本。推荐使用Storybook或Docz等工具。
基础文档结构
## Button
### Props
| 属性名 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| size | 'sm'\|'md'\|'lg' | 'md' | 按钮尺寸 |
### 代码示例
```jsx
<Button size="lg" onClick={handleClick}>
提交
</Button>
交互演示
在Storybook中定义可视化用例:
// Button.stories.tsx
export const Primary: Story = {
args: {
primary: true,
label: 'Button',
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement)
await userEvent.click(canvas.getByRole('button'))
}
}
性能优化要求
组件性能直接影响用户体验,需遵循以下实践:
渲染优化
- React.memo应用:
const MemoComponent = React.memo(
({ data }) => <div>{data}</div>,
(prev, next) => prev.data.id === next.data.id
)
- useCallback/useMemo:
function Parent() {
const [count, setCount] = useState(0)
const increment = useCallback(() => setCount(c => c + 1), [])
return <Child onClick={increment} />
}
体积控制
- 代码分割:
const HeavyComponent = React.lazy(() => import('./HeavyComponent'))
function App() {
return (
<Suspense fallback={<Spinner />}>
<HeavyComponent />
</Suspense>
)
}
- 依赖分析:
npx source-map-explorer build/static/js/main.*.js
错误处理机制
健壮的组件需要完善的错误边界和异常处理。
React错误边界
class ErrorBoundary extends React.Component {
state = { hasError: false }
static getDerivedStateFromError() {
return { hasError: true }
}
componentDidCatch(error, info) {
logErrorToService(error, info.componentStack)
}
render() {
return this.state.hasError
? <FallbackUI />
: this.props.children
}
}
异步错误捕获
function AsyncComponent() {
const [state, setState] = useState()
useEffect(() => {
fetchData()
.then(setState)
.catch(error => {
showToast(error.message)
setState(null)
})
}, [])
return <div>{state?.data}</div>
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn