关于 test-coverage
test-coverage 是用于查找并修复 Flows 应用测试覆盖率缺口的技能,设有 80% 行覆盖率门槛。它指导开发者配置覆盖率、编写测试、补齐低覆盖部分并改进代码可测试性。
主要功能
- 0180% 行覆盖率门槛
- 02检查 Vitest 与 Jest 覆盖率配置
- 03检查生产代码的覆盖范围
- 04指导补充测试与修复缺口
- 05审查并重构可测试性
适用场景
- 01使 Flows 应用达到覆盖率门槛
- 02查找缺少测试的生产文件
- 03为未覆盖分支和错误路径补测试
- 04重构依赖以改善可测试性
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ cognitedata/ builder-skills --skill test-coverage安装后在 Claude Code 中输入/test-coverage确认已加载。
详细介绍
测试覆盖率修复
修复 $ARGUMENTS(如果未提供参数,则修复整个应用)的测试覆盖率。此技能通过查找并修复覆盖率缺口,确保达到 Flows 应用获批所要求的 80% 行覆盖率硬性门槛。请按顺序完成每个步骤。
步骤 1 — 验证测试框架和覆盖率工具
检查项目是否配置了可正常工作的测试框架和覆盖率工具:
# Check for vitest or jest in package.json
grep -E "(vitest|jest)" package.json
# Check for coverage configuration
cat vitest.config.ts 2>/dev/null || cat vitest.config.js 2>/dev/null || cat jest.config.ts 2>/dev/null || cat jest.config.js 2>/dev/null
验证以下事项:
- 已安装并配置测试框架(Vitest 或 Jest)
- 配置文件中包含
coverage部分(例如,在 vitest.config.ts 中使用coverage: { provider: 'v8', ... }) - 已配置覆盖率报告器(至少包括
text和lcov或json-summary)
如果未配置覆盖率工具,请立即修复:
- 安装覆盖率提供程序:
pnpm add -D @vitest/coverage-v8
- 在
vitest.config.ts中添加覆盖率配置。读取现有配置文件,然后在test内添加coverage部分:
// vitest.config.ts — minimum coverage configuration to add
test: {
coverage: {
provider: 'v8',
reporter: ['text', 'text-summary', 'lcov'],
include: ['src/**/*.{ts,tsx}'],
exclude: [
'src/**/*.test.{ts,tsx}',
'src/**/*.spec.{ts,tsx}',
'src/**/vite-env.d.ts',
'src/main.tsx',
],
},
}
写入更新后的配置文件。如果完全不存在 vitest.config.ts,请创建一个包含完整 defineConfig 包装器的配置文件。
步骤 2 — 验证覆盖率范围
80% 的阈值适用于 src/ 下的所有 .ts 和 .tsx 文件,仅排除:
- 测试文件(
*.test.ts、*.test.tsx、*.spec.ts、*.spec.tsx) - 类型声明文件(
vite-env.d.ts) - 入口文件(
main.tsx)
应用不得将页面、组件、钩子或其他生产代码排除在覆盖率统计之外。
# Check what files are excluded from coverage in the config
grep -A 20 "exclude" vitest.config.ts 2>/dev/null || grep -A 20 "exclude" vitest.config.js 2>/dev/null
# Check for coveragePathIgnorePatterns in jest config
grep -A 10 "coveragePathIgnorePatterns\|collectCoverageFrom" jest.config.ts 2>/dev/null
如果配置排除了生产文件,请立即修复:
移除所有会将生产代码排除在覆盖率统计之外的排除项。只能排除测试文件、类型声明文件和入口文件。将 exclude 数组改写为仅包含:
exclude: [
'src/**/*.test.{ts,tsx}',
'src/**/*.spec.{ts,tsx}',
'src/**/vite-env.d.ts',
'src/main.tsx',
],
具体而言,请移除以下排除项:
src/pages/、src/components/或src/hooks/— 不允许- 特定功能文件 — 不允许,除非它们是生成的代码
src/**/*.tsx(所有组件)— 不允许,这会隐藏应用中的大部分代码
写入修正后的配置文件。
步骤 3 — 运行测试并收集覆盖率
# Try common coverage commands based on project setup
npx vitest run --coverage 2>/dev/null || npx jest --coverage 2>/dev/null || npm test -- --coverage 2>/dev/null
记录覆盖率摘要:
- 语句: X%
- 分支: X%
- 函数: X%
- 行: X%
硬性门槛: 总体行覆盖率必须至少达到 80%。低于此阈值的应用会被列为必须修复。
如果测试无法运行,请立即修复:
常见修复方法:
- 缺少导入: 阅读失败的测试文件,添加缺少的导入语句,并写入修复后的文件。
- 模拟对象损坏: 阅读测试以了解模拟的内容。修复模拟对象,使其符合被模拟模块当前的 API。
- 快照过时: 运行
npx vitest run --update更新快照,然后检查差异以确保正确。 - 缺少依赖: 对尚未安装的测试工具运行
pnpm add -D <missing-package>。 - 配置错误: 阅读配置文件,修复语法或选项错误,并写入修正后的文件。
每次修复后重新运行测试,直到所有测试通过。然后记录覆盖率摘要。
步骤 4 — 查找并编写缺失的测试文件
对于 src/ 下每个非平凡的 .ts/.tsx 文件,检查是否存在对应的测试文件:
# List all production files and check for test counterparts
for file in $(find src -name "*.ts" -o -name "*.tsx" | grep -v ".test." | grep -v ".spec." | grep -v "node_modules" | grep -v "vite-env" | sort); do
base="${file%.*}"
ext="${file##*.}"
dir=$(dirname "$file")
filename=$(basename "$base")
# Check for test file in same directory or __tests__ directory
test_exists="false"
for pattern in "${base}.test.${ext}" "${base}.spec.${ext}" "${base}.test.ts" "${base}.spec.ts" "${dir}/__tests__/${filename}.test.${ext}" "${dir}/__tests__/${filename}.spec.${ext}"; do
if [ -f "$pattern" ]; then
test_exists="true"
break
fi
done
if [ "$test_exists" = "false" ]; then
echo "NO TEST: $file"
fi
done
对每个没有测试的文件进行分类:
- 服务、钩子、工具函数、上下文、ViewModel 钩子 — 立即编写测试文件(见下文)
- 纯展示组件,不含逻辑 — 标记为 N/A(无需测试)
- 汇总导出文件(只包含重新导出的
index.ts)— 标记为 N/A - 仅包含类型的文件(
.d.ts,或仅导出类型/接口的文件)— 标记为 N/A
为每个缺少测试的文件创建全面的测试文件。 如果生产代码支持,请使用上下文注入来模拟依赖项。如果生产代码使用硬编码导入,请将其注明为可测试性问题,但仍使用 vi.mock 编写测试,并附上理由注释。对每个文件遵循以下流程:
- 阅读源文件,了解其导出内容、依赖项和逻辑。
- 创建
.test.ts或.test.tsx文件,放在与源文件相同的目录中。 - **编写测试,覆盖:**正常路径、错误路径、空状态和边缘情况。
针对不同文件类型使用合适的测试模式:
针对钩子:
- 使用
@testing-library/react中的renderHook进行测试 - 使用必要的 Provider 包装(QueryClientProvider、自定义上下文 Provider 等)
- 测试初始状态、加载状态、成功状态和错误状态
- 示例结构:
import { renderHook, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
// import the hook
const createWrapper = () => {
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
return ({ children }: { children: React.ReactNode }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);
};
describe('useMyHook', () => {
it('returns data on success', async () => {
const { result } = renderHook(() => useMyHook(), { wrapper: createWrapper() });
await waitFor(() => expect(result.current.data).toBeDefined());
});
it('handles errors', async () => {
// set up error condition
const { result } = renderHook(() => useMyHook(), { wrapper: createWrapper() });
await waitFor(() => expect(result.current.error).toBeDefined());
});
});
针对服务/工具函数:
- 直接调用函数进行测试
- 必要时模拟 CDF SDK 响应
- 测试返回值、副作用和抛出的错误
- 示例结构:
import { describe, it, expect, vi } from 'vitest';
// import the service/util functions
describe('myService', () => {
it('returns expected result for valid input', () => {
const result = myFunction(validInput);
expect(result).toEqual(expectedOutput);
});
it('throws on invalid input', () => {
expect(() => myFunction(invalidInput)).toThrow();
});
});
针对包含逻辑的组件:
- 使用
@testing-library/react中的render进行测试 - 验证加载、错误和数据状态
- 测试会触发状态变化的用户交互
- 示例结构:
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
// import the component and providers
describe('MyComponent', () => {
it('shows loading state initially', () => {
render(<MyComponent />, { wrapper: createWrapper() });
expect(screen.getByText(/loading/i)).toBeInTheDocument();
});
it('renders data after loading', async () => {
render(<MyComponent />, { wrapper: createWrapper() });
await waitFor(() => {
expect(screen.getByText('expected content')).toBeInTheDocument();
});
});
});
依赖项模拟指南:
- 尽可能使用上下文注入(而不是
vi.mock)——通过钩子的上下文提供测试依赖项。 - 如果生产代码使用硬编码导入,导致无法进行上下文注入,请使用
vi.mock,并附上理由注释,说明原因(例如:// vi.mock required: useDataSource uses direct import, not context injection)。 - 确保模拟对象具备类型安全性——不要使用
as unknown as T类型断言。定义符合接口要求的模拟对象。
编写完每个测试文件后,运行 npx vitest run <test-file> 验证测试通过。
步骤 5 — 修复覆盖率较低的文件
如果覆盖率工具提供了各文件的指标,请列出行覆盖率低于 80% 的文件:
# Parse lcov or text output for per-file coverage
cat coverage/coverage-summary.json 2>/dev/null | node -e "
const data = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));
Object.entries(data).forEach(([file, metrics]) => {
if (file === 'total') return;
const pct = metrics.lines?.pct ?? 0;
if (pct < 80) console.log(pct.toFixed(1) + '% — ' + file);
});
" 2>/dev/null
对于每个覆盖率低于 80% 的文件,先从覆盖率报告中读取未覆盖的代码行,然后添加测试用例,覆盖这些特定代码路径:
- 读取覆盖率报告中的未覆盖代码行。 检查
coverage/目录中的详细文件报告(lcov 或 html),确认哪些行未被覆盖。 - 阅读源文件,了解这些未覆盖代码行的作用。
- 添加测试用例,覆盖这些特定代码路径:
- **未覆盖的错误路径:**添加可触发错误条件的测试(网络故障、无效输入、空值、权限错误)。通过提供错误输入或模拟依赖项抛出错误来触发错误。
- **未覆盖的分支:**为每个条件分支添加测试。如果
if/else只测试了true分支,就编写测试来触发false分支。 - **未覆盖的函数:**为每个缺少覆盖率的导出函数添加测试。验证返回值和副作用。
- **未覆盖的 catch 块:**模拟上游调用拒绝或抛出错误,并验证 catch 块的行为。
- 添加测试后再次运行覆盖率,验证该文件现在是否达到 80%:
npx vitest run --coverage <test-file>
重复上述步骤,直到每个文件的行覆盖率至少达到 80%,或已覆盖所有可行的路径。
第 6 步 — 修复可测试性模式
评估并修复代码库中的可测试性问题:
# Check for dependency injection via context
grep -rn --include="*.ts" --include="*.tsx" "useContext\|createContext" src/hooks/ src/contexts/
# Check for vi.mock usage (red flag for testability)
grep -rn --include="*.ts" --include="*.tsx" "vi\.mock" src/
# Check for unsafe casts in tests
grep -rn --include="*.ts" --include="*.tsx" "as unknown as" src/ | grep -E "\.test\.|\.spec\."
# Check for interface-based services
grep -rn --include="*.ts" --include="*.tsx" -E "implements\s+\w+" src/
对于发现的每个可测试性问题,重构生产代码以支持更好的测试模式。然后更新相应的测试,使用改进后的模式。
| 问题 | 修复方法 |
|---|---|
| Hook 直接导入依赖,而不是使用上下文 | 添加带有默认依赖项的上下文类型。使用 createContext 创建上下文,并提供使用真实实现的默认值。在 Hook 中,使用 useContext 获取依赖项。这样,测试就可以通过上下文提供程序传入模拟依赖项,而无需使用 vi.mock。 |
| 服务没有接口 | 提取描述服务公共 API 的 TypeScript 接口。让类或对象实现该接口。测试应针对接口进行模拟,而不是针对具体实现。 |
| 页面组件将数据获取与渲染混在一起 | 将数据逻辑提取到 use*ViewModel Hook 中。页面组件调用 ViewModel Hook,并根据其返回值进行渲染。使用 renderHook 单独测试 ViewModel Hook,并通过模拟 ViewModel 测试页面组件。 |
测试对可通过上下文注入依赖的模块使用 vi.mock | 将生产代码重构为使用上下文注入(如上所述)后,更新测试,通过上下文提供程序传入模拟依赖项。移除 vi.mock 调用。添加注释说明此模式。 |
测试使用 as unknown as T 类型断言来创建模拟对象 | 定义符合所需接口的正确模拟类型或对象。用类型正确的模拟对象替换类型断言。如果接口较大,可创建一个辅助函数,返回仅包含所用方法且类型正确的部分模拟对象。 |
对于每个重构的文件:
- 阅读源文件及其测试文件。
- 重构生产代码(添加上下文、提取接口、提取 ViewModel Hook)。
- 更新测试,使其使用改进后的模式。
- 运行
npx vitest run <test-file>,验证测试仍然通过。
第 7 步 — 报告剩余差距
重新运行完整测试套件并启用覆盖率,以获取最终数据:
npx vitest run --coverage 2>/dev/null || npx jest --coverage 2>/dev/null
总结已完成的工作和剩余事项:
### Test Coverage Summary (After Fixes)
| Metric | Before | After | Gate |
|--------|--------|-------|------|
| Lines | X% | Y% | ≥80% required |
| Branches | X% | Y% | — |
| Functions | X% | Y% | — |
| Statements | X% | Y% | — |
### Coverage verdict: PASS / FAIL
### What was fixed
- [ ] Coverage tooling configured/corrected
- [ ] Exclusions cleaned up (removed N production file exclusions)
- [ ] N failing tests fixed
- [ ] N new test files written (list them)
- [ ] N existing test files expanded for coverage
- [ ] N files refactored for testability
### Remaining gaps (needs human review)
Only list issues that could not be auto-fixed:
- Complex business logic where correct test assertions require domain knowledge
- Integration tests that need real API credentials or environment setup
- Files where coverage cannot reach 80% without major architectural changes (explain why)
完成
总结:
- 修复前后总体覆盖率,以及与 80% 门槛的比较(PASS 或 FAIL)
- 编写的测试文件数和新增的测试数
- 为提高可测试性而重构的文件数
- 任何仍需人工审核的必须修复事项