阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 单元测试规范

单元测试规范

作者:陈川 阅读数:44031人阅读 分类: 前端综合

组件开发规范

组件化开发是前端工程化的核心实践之一。良好的组件设计能提升代码复用率、降低维护成本。以下从设计原则、目录结构、代码风格等方面阐述组件开发规范。

设计原则

  1. 单一职责原则:每个组件只负责一个特定功能。例如按钮组件只处理点击交互,不包含业务逻辑:
// 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>
}
  1. 受控与非受控:明确区分组件状态管理方式。表单组件通常需要同时支持两种模式:
// 受控组件
<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   // 验证逻辑

代码风格要求

  1. Props命名:采用小驼峰命名法,布尔类型属性需加is/has前缀:
interface Props {
  isLoading: boolean
  maxLength?: number
  onComplete: () => void
}
  1. 样式隔离:推荐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'
  }
}

测试用例设计原则

  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()
})
  1. 边界条件覆盖:包括空状态、错误处理等场景:
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()
  })
})

常用测试模式

  1. 异步操作测试
test('should load data', async () => {
  jest.spyOn(api, 'fetchData').mockResolvedValue({ data: 'mock' })
  
  render(<DataFetcher />)
  await waitFor(() => {
    expect(screen.getByText('mock')).toBeInTheDocument()
  })
})
  1. 上下文依赖测试
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'))
  }
}

性能优化要求

组件性能直接影响用户体验,需遵循以下实践:

渲染优化

  1. React.memo应用
const MemoComponent = React.memo(
  ({ data }) => <div>{data}</div>,
  (prev, next) => prev.data.id === next.data.id
)
  1. useCallback/useMemo
function Parent() {
  const [count, setCount] = useState(0)
  const increment = useCallback(() => setCount(c => c + 1), [])
  
  return <Child onClick={increment} />
}

体积控制

  1. 代码分割
const HeavyComponent = React.lazy(() => import('./HeavyComponent'))

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <HeavyComponent />
    </Suspense>
  )
}
  1. 依赖分析
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

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌