SparkyFitness 全栈测试体系实战指南:Jest、pytest 与路径感知的 CI 测试流水线
后端前端移动开发【免费下载链接】SparkyFitnessSparkyFitness: Built for Families. Powered by AI. Track food, fitness, water, and health — together.项目地址https://gitcode.com/gh_mirrors/sp/SparkyFitness点击查看免费下载导读SparkyFitness 是一个横跨前端Vite React、后端Node.js Express、移动端React Native Expo与 Garmin 微服务Python的多仓组件项目。本文以仓库开发者文档 docs/src/developer/testing.md 为核心骨架结合 .github/workflows/ci-tests.yml 及各子项目的真实配置与测试用例系统讲解四个组件的本地测试命令、统一的 Mock 编写规范、基于路径变更检测的 CI 流水线以及覆盖率报告的处理方式。读完本文你将掌握如何在本地快速跑通任意组件的测试、如何按仓库既有模式编写可维护的前端组件测试以及理解 CI 中只测受影响组件和数据库迁移双重校验的设计思路。一、测试技术栈总览SparkyFitness 的测试体系按组件选用两套主流工具链组件技术栈测试框架关键配置位置SparkyFitnessFrontendVite React TypeScriptJestts-jest jsdompackage.json、setupTests.tsSparkyFitnessServerNode.js Express TypeScriptVitestpackage.json、vitest.config.tsSparkyFitnessMobileReact Native ExpoJestjest-expo presetpackage.json、jest.setup.jsSparkyFitnessGarminPython 微服务pytest unittest 兼容requirements.txt、tests/test_daily_calories.py从源码结构看这种前端/移动端用 Jest、后端用 Vitest、Python 服务用 pytest的分工既保持了各组件生态内最成熟的测试实践如前端 jest-dom 匹配器、后端 Vitest 的 ESM 原生支持又让每个 CI Job 拥有独立的依赖与运行环境互不干扰。二、本地运行测试四个组件的命令清单各组件脚本定义在其自身的 package.json或 Garmin 的依赖清单中以下命令与文档一致并标注了每个命令在配置文件中的真实出处# Frontend (Vite React) —— 对应 SparkyFitnessFrontend/package.json cd SparkyFitnessFrontend pnpm test # Run tests in watch mode对应 test: jest pnpm test:ci # Run tests once with coverage对应 test:ci: jest --ci --coverage --maxWorkers2 # Backend (Node.js Express) —— 对应 SparkyFitnessServer/package.json cd SparkyFitnessServer pnpm test # Run tests in watch mode实际为 test: vitest runtest:watch: vitest pnpm test:ci # Run tests once with coveragetest:ci: vitest run --coverage --reporterverbose # Mobile (React Native Expo) —— 对应 SparkyFitnessMobile/package.json cd SparkyFitnessMobile npm run test:run # Run tests oncetest:run: jest npm run test:ci # Run tests once with coveragetest:ci: jest --ci --coverage --maxWorkers2 # Garmin Microservice (Python) cd SparkyFitnessGarmin pytest --cov. --cov-reporthtml几点值得注意的细节CI 模式的共性三个 JS/TS 组件在test:ci中都使用了--ci标志前端与移动端还带--maxWorkers2限制并行 Worker 数避免 CI 资源争抢后端则改用--reporterverbose输出更详细的失败信息。Watch 模式的差异前端与移动端的pnpm test/npm run test默认进入 Jest watch 模式后端文档中写的pnpm test实际执行vitest run单次运行如需监听则使用pnpm test:watch即vitest。从 SparkyFitnessServer/package.json 的 scripts 定义可以看出这一差别。验证链CI 在跑测试前还会执行各组的validate脚本——前端为typecheck lint format:check knip后端为typecheck lint format:check移动端则在 i18n 与肌肉图生成校验之外叠加 typecheck、lint、knip 与原生本地化检查。测试与静态检查在流水线中是两道并行的质量闸门。三、CI 工作流路径感知的按需测试仓库的 CI 流水线定义在 .github/workflows/ci-tests.yml在 pull request 以及推送到main分支时触发。与每次全量跑所有测试的朴素做法不同该流水线借助 dorny/paths-filter 做路径变更检测只对实际改动的组件运行测试从而显著压缩 PR 的等待时间。触发条件与组件映射表工作流最外层通过paths限定触发范围——只有四个组件目录、移动端/iOS 相关文件或工作流自身发生变化时才会启动组件触发路径包管理器测试命令对应 CI JobFrontendSparkyFitnessFrontend/**pnpmpnpm run test:cifrontend-testsMobileSparkyFitnessMobile/**npmnpm run test:cimobile-testsServerSparkyFitnessServer/**pnpmpnpm run test:ciserver-testsGarminSparkyFitnessGarmin/**pippytestgarmin-tests注Mobile 一列文档标注 npm但实际工作流中移动端 Job 仍使用pnpm install --frozen-lockfile安装依赖测试命令则为pnpm run test:ci见 ci-tests.yml 的mobile-tests步骤与 SparkyFitnessMobile/package.json 中定义的脚本一致此处以工作流实际内容为准。两阶段执行模型流水线由第一个changesJob 和四个/六个后续测试 Job 组成测试 Job 均通过needs: changes与if: needs.changes.outputs.component true做条件门控changesDetect Changesactions/checkoutv4检出代码后用dorny/paths-filterv2按五组过滤器frontend、mobile、server、garmin、migrations计算哪些组件有改动并将结果以 job outputs 形式暴露。按需测试 Jobfrontend-tests、mobile-tests、server-tests、garmin-tests各自只在自己的目录working-directory下执行migration-check与migration-upgrade-check则仅在迁移相关文件变化时启动详见下文。各测试 Job 的通用流程可概括为actions/checkoutv4→pnpm/action-setupv4安装 pnpm →actions/setup-nodev4配置指定 Node 版本并缓存 pnpm 依赖cache-dependency-path: pnpm-lock.yaml→pnpm install --frozen-lockfile锁定版本安装 → 运行validate类型检查、Lint、格式化→ 运行test:ci并输出覆盖率 →actions/upload-artifactv4上传 coverage 目录retention-days: 7保留 7 天。各 Job 的独有细节Node 版本前端、后端、迁移 Job 使用 Node 24移动端使用 Node 20见各 Job 的setup-node步骤与各自依赖的运行时要求匹配。后端测试的密钥处理server-testsJob 环境特意不设置SPARKY_FITNESS_API_ENCRYPTION_KEY与BETTER_AUTH_SECRET而是由 vitest.config.ts 在每次运行时用crypto.randomBytes生成随机回退值。这样仓库中不落任何密钥字面量避免 GitGuardian 等密钥扫描器在每次 push 时报警。后端 Job 还设置了SKIP_RLS_MATRIX: 1因为该 Job 没有数据库RLS 权限矩阵测试会被跳过留待专门的迁移 Job 在真实 Postgres 上验证。Garmin Job使用actions/setup-pythonv5Python 3.12 pip 缓存先pip install -r requirements.txt再补装pytest pytest-cov执行pytest --cov. --cov-reportxml --cov-reporthtml当目录中不存在测试时打印提示跳过且该步骤配置了continue-on-error: true覆盖率上传仍通过if: always()保证执行。数据库迁移的双重校验这是流水线中值得单独讲透的部分。migration-checkFresh-install Migrations与migration-upgrade-checkUpgrade-path Migrations共享同一个migrations过滤条件覆盖路径包括SparkyFitnessServer/db/migrations/**、rls_policies.sql、grantPermissions.ts、dbMigrations.ts、applyRlsPolicies.ts、initializeDatabase.ts及相关集成测试文件。两个 Job 都通过services拉起postgres:18.3-alpine容器POSTGRES_DB: sparky_test健康检查pg_isreadymigration-check全新安装路径在空库上并行启动两个pnpm run test:migrations进程tests/migrate.script.ts入口验证并发初始化时数据库锁与幂等机制的正确性随后依次执行 RLS 权限矩阵、Strava 清理、OIDC 提供方、Better Auth schema对应历史上 #2469/#2470 认证中断事故的回归测试、登录限流、provider 同步声明等集成测试并固定SPARKY_FITNESS_FRONTEND_URLhttp://localhost:3004以保证 Better Auth 实例构建方式一致。migration-upgrade-check升级路径fetch-depth: 0拉取完整历史先用git checkout ${BASE_SHA}把基础分支的 SQL 迁移文件换入并跑一遍test:migrations再换回 PR 的迁移文件跑第二遍。由于schema_migrations表在两次运行间保留第二遍只会应用新增迁移——恰好模拟存量部署平滑升级的真实场景专门捕获那些只在已填充数据的 schema 上才会暴露的迁移问题。四、测试文件布局文档给出了各组件测试目录的组织方式结合仓库实际文件结构可进一步确认SparkyFitnessFrontend/ src/tests/ setupTests.ts # 全局测试初始化jest-dom、polyfills test-utils.tsx # renderWithClient 测试渲染辅助 stubs/ # betterAuth、react-markdown 等第三方模块桩 components/ # 组件测试与组件领域一一对应 MealBuilder.test.tsx MealManagement.test.tsx MealPlanCalendar.test.tsx SparkyFitnessServer/ tests/ # 后端单元与集成测试*.test.ts被 vitest include 捕获 migrate.script.ts # 数据库迁移执行脚本test:migrations 入口 SparkyFitnessMobile/ __tests__/ components/ # 移动端组件测试 hooks/ # 自定义 Hook 测试 services/ # 服务层测试 screens/ # 屏幕级测试 localization/ # i18n 本地化测试从源码看前端测试集中在src/tests/而非与组件文件同目录后端则统一放在tests/下由 vitest.config.ts 的include: [**/tests/**/*.test.ts]收集移动端 Jest 配置还通过testPathIgnorePatterns排除了__tests__/helpers/、__tests__/hooks/queryTestUtils.ts等辅助文件避免辅助代码被误判为测试。五、编写前端测试统一的 Mock 模式文档强调所有前端组件测试遵循一致的 Mock 策略仓库中最具代表性的例子是 MealBuilder.test.tsx该文件中对waitFor/renderWithClient/initialFoods的组合使用共出现 45 处是前端测试密集区的典型样本。标准流程如下// 1. Mock i18n —— 返回回退字符串或翻译 key jest.mock(react-i18next, () ({ useTranslation: () ({ t: (key: string, defaultValueOrOpts?: string | Recordstring, unknown) { if (typeof defaultValueOrOpts string) return defaultValueOrOpts; if (defaultValueOrOpts typeof defaultValueOrOpts object defaultValue in defaultValueOrOpts) { return defaultValueOrOpts.defaultValue as string; } return key; }, }), })); // 2. Mock contexts jest.mock(/contexts/ActiveUserContext, () ({ useActiveUser: () ({ activeUserId: test-user-id }), })); jest.mock(/contexts/PreferencesContext, () ({ usePreferences: () ({ loggingLevel: debug, itemDisplayLimit: 100 }), })); // 3. Mock toast jest.mock(/hooks/use-toast, () ({ toast: jest.fn() })); // 4. Mock logging jest.mock(/utils/logging, () ({ debug: jest.fn(), info: jest.fn(), warn: jest.fn(), error: jest.fn(), })); // 5. Mock services with trackable fns —— 用可断言的 mock 函数包一层便于后续断言调用 const mockGetMeals jest.fn(); jest.mock(/services/mealService, () ({ getMeals: (...args: unknown[]) mockGetMeals(...args), }));仓库真实测试中的进阶变体对照 MealBuilder.test.tsx 的开头部分可以看到文档模式的落地细节i18n mock 补全了initReactI18next除useTranslation外还导出了{ type: 3rdParty, init: () {} }避免组件初始化 i18n 实例时报错。Context mock 携带业务默认值PreferencesContext的 mock 额外返回nutrientDisplayPreferences含view_group: quick_info与可见营养项数组、energyUnit: kcal与convertEnergy说明 mock 需覆盖被测组件实际读取的全部字段。API 服务按模块整体 mock如jest.mock(/api/Foods/meals, ...)同时提供createMeal、updateMeal、getMealById三个可跟踪函数。复杂子组件 stub 化FoodUnitSelector、FoodSearchDialog等子组件被替换为返回带data-testid的简单 div将被测组件与子组件实现彻底隔离。beforeEach(() jest.clearAllMocks())保证用例之间互不污染。约定与测试辅助文档列出的约定在仓库中得到一一印证测试文件与其组件领域同放于src/tests/components/按*.test.tsx命名使用testing-library/react进行渲染与断言test-utils.tsx 提供了renderWithClient辅助内部创建retry: false的QueryClient并包裹QueryClientProvider同时挂载 Query/MutationCache 的全局错误 toast 处理用waitFor等待异步操作如await waitFor(() expect(screen.getByLabelText(Total Servings)).toBeInTheDocument())等待 API 调用后的 UI 状态更新通过initialFoods之类的 props 直接注入数据避免依赖交互型子组件MealBuilder即以initialFoods{sampleFoods}注入样本食物数据。六、移动端测试环境jest-expo 与全局桩移动端测试的复杂度主要来自大量原生模块。其 Jest 配置SparkyFitnessMobile/package.json 的jest字段使用jest-expopreset 与 jsdom 环境并通过一份数百行的 jest.setup.js 集中桩掉无法在 Node 中运行的原生能力标准库 polyfillTextEncoder/TextDecoderExpo winter 运行时按需安装 whatwg-url 需要它们本地化与系统 APIexpo-localization固定返回en-USexpo-application/expo-constants提供固定版本号健康数据桥kingstinct/react-native-healthkit的读写与授权 API 全部 mock写回保存返回带uuid的样本对象确保 UUID 跟踪断言真实有效react-native-health-connect的权限、读取、聚合 API 亦全部桩化动画与手势react-native-reanimated、react-native-gesture-handler、react-native-keyboard-controller提供链式可调用的桩实现保证拖拽排序、手势等交互代码在单元测试中可安全执行第三方渲染库victory-native、shopify/react-native-skia、react-native-maps、gorhom/bottom-sheet渲染为带testID的 View供断言画了什么i18n 生产实例文件末尾加载真实的src/localization/i18n并以initImmediate: false同步初始化让被隔离渲染的组件也能解析英文默认文案而非返回原始 key。同时moduleNameMapper将workspace/shared指向../shared/src/index.ts跨包共享代码直接在测试中解析 TS 源码并通过精心编写的transformIgnorePatterns白名单让 react-native、expo、react-navigation、workspace/shared、zod、better-auth 等 ESM 包通过 babel 转换。七、后端测试Vitest 与运行时密钥策略后端使用 Vitest 且采用globals: true测试文件中可直接使用 describe/it/expect环境为 Node。两个值得关注的设计跨包共享代码解析resolve.alias将workspace/shared指向../shared/src与前端、移动端的 moduleNameMapper 策略一致共享包源码被直接纳入各组件测试。随机密钥回退vitest.config.ts测试进程需要SPARKY_FITNESS_API_ENCRYPTION_KEY32 字节 hex与BETTER_AUTH_SECRETbase64配置在读取仓库根.env后若缺失则用crypto.randomBytes生成每次运行不同的随机值注入test.env。因为这些值只在进程内用于加解密与 cookie 签名不持久化、不跨运行复用随机生成完全安全同时保证仓库内没有任何密钥字面量——这正是不把秘密写进代码的工程实践在测试层的体现。后端测试脚本一览SparkyFitnessServer/package.jsontest: vitest run, // 单次运行 test:watch: vitest, // 监听模式 test:coverage: vitest run --coverage, test:ci: vitest run --coverage --reporterverbose, test:migrations: tsx tests/migrate.script.tstest:migrations专供 CI 的迁移 Job 调用配合tests/migrate.script.ts与 Postgres 服务容器完成空库初始化与升级路径验证。八、Garmin 微服务测试pytestGarmin 微服务Python使用 pytest 并带覆盖率输出。仓库中的 tests/test_daily_calories.py 是典型样例以unittest.TestCase组织用例通过sys.path.insert将父目录加入模块搜索路径后直接导入service.py的业务函数。测试覆盖了规范字段解析activeKilocalories/bmrKilocalories/totalKilocalories正确映射为active_calories/bmr_calories/total_calories浮点值别名回退activeCalories/bmrCalories/totalCalories等已知别名在规范字段缺失时生效且字符串数字可被正确转换边界值保留合法的0剔除None、inf与 not-a-number 等缺失或非法值。本地运行方式pytest --cov. --cov-reporthtmlCI 中则执行pytest --cov. --cov-reportxml --cov-reporthtml并将htmlcov/作为构建产物上传。九、覆盖率报告与产物管理运行test:ci后各组件会在自身目录下生成coverage/前端与移动端Garmin 为htmlcov/。CI 中这些目录通过actions/upload-artifactv4上传为构建产物retention-days: 7表示保留 7 天后自动清理Job上传产物名上传路径保留天数frontend-testsfrontend-coverageSparkyFitnessFrontend/coverage/7mobile-testsmobile-coverageSparkyFitnessMobile/coverage/7server-testsserver-coverageSparkyFitnessServer/coverage/7garmin-testsgarmin-coverageSparkyFitnessGarmin/htmlcov/7上传步骤均使用if: always()即使测试失败也会保留覆盖率产物便于事后在 GitHub Actions 的 Artifacts 中下载分析。本地查看覆盖率时可直接打开 HTML 报告前端/移动端coverage/lcov-report/index.htmlGarminhtmlcov/index.html逐文件浏览未覆盖分支。十、编写测试的通用建议综合文档约定与仓库实践可沉淀出以下可复用的编写准则Mock 一切外部依赖只测被测单元i18n、Context、toast、日志、API 服务、复杂子组件逐一桩化对需要断言的服务调用用可跟踪的jest.fn包裹并在测试内断言调用参数与次数。用真实辅助函数包裹 Provider如renderWithClient统一注入 QueryClient关闭重试避免每个测试重复样板代码。异步一律waitForAPI 调用、状态更新等异步 UI 断言放在waitFor中配合screen查询器与 jest-dom 匹配器toBeInTheDocument、toBeEnabled书写。通过 props 注入数据优先用initialFoods这类输入属性驱动组件而不是依赖子组件交互来间接准备数据。保持 CI 与本地一致本地先跑validatetypecheck/lint/format再跑test:ci与 CI 的检查顺序保持一致避免本地绿、CI 红。涉及数据库的改动务必关注迁移 Job任何db/migrations/**、RLS 策略或迁移运行器文件的改动都会触发两个需要真实 Postgres 的集成验证本地可借助 docker-compose 中的数据库服务先行演练。延伸阅读开发者文档目录架构architecture.md、数据库database.md、权限层级database-security-tiers.md、troubleshooting.md 等配套文档。前端全局测试初始化jsdom polyfillsmatchMedia、ResizeObserver、PointerEvent与 react-leaflet/leaflet 桩。移动端全局测试初始化Expo 生态原生模块的完整 mock 清单。后端测试运行器配置随机密钥回退与workspace/shared别名。Garmin 测试样例Python 侧单元测试的编写范式。赞分享后端前端移动开发【免费下载链接】SparkyFitnessSparkyFitness: Built for Families. Powered by AI. Track food, fitness, water, and health — together.项目地址https://gitcode.com/gh_mirrors/sp/SparkyFitness点击查看免费下载相关推荐FastLED 的 CI 测试套件指南ci/tests pytest 测试体系全解析FastLED 的 CI 测试套件指南ci/tests pytest 测试体系全解析 FastLED 仓库在 ci/tests https://link.gi嵌入式物联网硬件开发驱动开发 Transformers 测试指南从 CI 流水线到测试编写实战 Transformers 测试指南从 CI 流水线到测试编写实战 本文是 Transformers 仓库官方日语版《Testing》文档的深度解读人工智能大模型深度学习NLP预训练微调模型推理服务Perfetto 测试体系实战指南单元测试、集成测试、Diff 测试与 CI 全链路Perfetto 测试体系实战指南单元测试、集成测试、Diff 测试与 CI 全链路 Perfetto 因构建配置与嵌入目标独立构建、Android in可观测性后端开发工具前端数据可视化上一篇LightTable快捷键大全提升编码速度的50个必备快捷键下一篇Minerva模型可视化工具使用教程从特征提取到热力图分析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考