资讯动态

Ignite 测试体系深度解析:test 目录布局与 i18n 翻译键完整性检测

发布时间:2026/9/13 13:45:54 来源:尧图企业网站定制
Ignite 测试体系深度解析test 目录布局与 i18n 翻译键完整性检测【免费下载链接】igniteInfinite Reds battle-tested React Native project boilerplate, along with a CLI, component/model generators, and more! 9 years of continuous development and counting.项目地址: https://gitcode.com/GitHub_Trending/ig/igniteIgnite 为基于其模板创建的 React Native 应用内置了一套开箱即用的 Jest 测试体系boilerplate/test目录承载全局测试初始化与翻译键完整性校验而业务测试可分散在代码库任意位置。本文以 docs/boilerplate/test/Test.md 为骨架结合 boilerplate/test 下的真实源码、boilerplate/jest.config.js 与 boilerplate/package.json 中的脚本配置深入讲解 test 目录的组织方式、i18n.test.ts的检测原理与局限以及如何在 Ignite 应用中编写自己的单元测试。test 目录全局测试的默认归属地Ignite 模板对测试文件的组织规则非常宽松Jest 测试可以放在代码库的任意位置。但以下三类内容被约定放入test目录即 boilerplate/test初始的 Jest 环境配置如setup.ts供测试使用的 mock 对象与桩数据如mockFile.ts作用域为全局的测试如跨整个代码库扫描的i18n.test.ts。当前模板中该目录的实际构成如下文件职责i18n.test.ts全代码库翻译键完整性校验本篇文章的核心主题setup.tsJest 全局 setup 文件统一 mock 原生模块与 i18n 依赖mockFile.ts导出一个默认图片资源对象供图片相关 mock 使用test-tsconfig.json专用于测试文件的 TypeScript 编译配置全局测试环境是如何接入的boilerplate/jest.config.js 是这一切的入口配置极为精简/** type {import(jest/types).Config.ProjectConfig} */ module.exports { preset: jest-expo, setupFiles: [rootDir/test/setup.ts], }preset: jest-expo复用 Expo 官方提供的 Jest preset自动处理 Babel 转译、React Native 模块解析等繁琐配置这也是模板依赖中同时出现jest-expoboilerplate/package.json devDependencies 中版本~55.0.9的原因。setupFiles: [rootDir/test/setup.ts]在每个测试文件执行之前先运行 boilerplate/test/setup.ts完成全局 mock 的注册。setup.ts 中的全局 Mock 策略阅读 boilerplate/test/setup.ts 可以看到Ignite 对 React Native 测试中最常见的三类副作用源做了统一处理react-native的图片 API通过jest.doMock(react-native, ...)扩展原生Image将resolveAssetSource替换为返回 boilerplate/test/mockFile.ts 固定对象的 mock同时用getSize: jest.fn(...)模拟图片尺寸回调success(100, 100)。这样测试中渲染任何Image组件都不会触发真实的资源加载。i18next及其应用封装将t/translate都替换为返回${key} ${JSON.stringify(params)}的纯函数并把 boilerplate/app/i18n/index.ts mock 成isInitialized: true、language: en的固定实现避免测试环境初始化真正的国际化引擎。expo-localization固定返回[{ languageTag: en-US, textDirection: ltr }]保证设备区域相关逻辑在测试中行为一致。此外setup.ts末尾还声明了全局类型__TEST__: boolean供代码在运行时判断是否处于测试环境。正因为这些 mock 是全局生效的业务测试文件里就不必再为图片、i18n 等基础设施编写重复的桩代码——这也是把 setup 放进全局test目录而非散落各处的意义所在。测试专用 tsconfigboilerplate/test/test-tsconfig.json 继承根 boilerplate/tsconfig.json放宽了noImplicitAny与noUnusedLocals两个约束并只 include**/*.test.ts与**/*.test.tsx避免测试代码的类型检查误伤业务源码。i18n.test.ts翻译键完整性检测boilerplate/test/i18n.test.ts 是 test 目录中唯一一个全局扫描型测试。它的目标很明确检查应用中是否存在缺失或拼写错误的翻译键translation key从而避免运行时渲染出错误的字符串。其工作机制可以拆解为三步第一步扫描代码库中所有被使用的翻译键测试内部通过child_process.exec执行一条看起来比较吓人的 grep 命令grep [T\|t]x[{]\?\\S*\[}]\?\|translate(\\S*\ -ohr ./app | grep -o \.*\-ohr表示只输出匹配片段、不区分大小写、递归搜索。第一条 grep 实际是在匹配以下四种常见写法并抓取引号中的 i18n keytx*如Text txcommon.ok /Txtx{}Tx{}translate(即 boilerplate/app/i18n/translate.ts 中translate(key, options)的调用方式第二条grep -o \.*\则进一步把双引号包裹的 key 文本提取出来作为代码库中实际使用的翻译键清单。测试代码中的注释明确说明这种匹配方式同时覆盖了 Ignite 的高阶组件 props——不仅tx还包括fooTx这类以Tx结尾的变体 prop。第二步枚举所有已定义的翻译键测试中的iterate函数递归遍历 boilerplate/app/i18n/en.ts 的翻译对象把所有叶子节点拼接成namespace.key形式的完整路径再通过key.replace(., :)把第一处.替换为 i18next 的命名空间分隔符:得到应用已定义的全部翻译键集合。第三步断言两边完全匹配对扫描到的每一个使用中的 key只要它不在EXCEPTIONS名单里就执行expect(allTranslationsDefined).toContainEqual(allTranslationsUsed[i])一旦某个组件引用了未定义的 key该断言立即失败测试会在 4 分钟超时第 74 行的240000毫秒内给出明确报错把问题拦截在 CI 而非用户设备上。EXCEPTIONS必要的逃生舱由于 grep 无法区分注释与真实代码测试维护者难免遇到合法但无法被正确识别的 key。为此i18n.test.ts顶部预留了EXCEPTIONS数组const EXCEPTIONS: string[] [ // welcomeScreen:readyForLaunch, /** * This translation key actually shows up in a comment describing the usage of the translate * function in the app/i18n/translate.ts file. ... */ hello, ]文件注释给出了一个真实案例keyhello实际上出现在 boilerplate/app/i18n/translate.ts 的 JSDoc 示例注释里translate(hello, { name: world })grep 不会跳过注释因此必须手动将其加入例外名单否则测试会因一段注释而永远失败。凡是因条件赋值、注释、拼接等原因无法被 grep 识别、但确实合法的 key都应登记到EXCEPTIONS中。检测方式的已知局限原文档明确指出这种方案并非 100% 完美如果你把 key 字符串存在变量中再条件性地赋值例如const key isLoggedIn ? welcomeScreen:title : welcomeScreen:guestgrep 只能看到变量名而看不到引号里的字面量这样的 key 将无法被扫描到也就绕过了检测。设计上这是可以接受的权衡——动态 key 场景下静态扫描天然力不从心。运行测试脚本与模式测试脚本定义在 boilerplate/package.json 中与测试直接相关的有三个命令作用pnpm run test运行一次完整的 Jest 测试套件pnpm run test:watch以 watch 模式启动 Jest监听文件变更并自动重跑相关测试pnpm run test:maestro运行 Maestro 端到端流程maestro test -e MAESTRO_APP_IDcom.helloworld .maestro/flowstest:watch适合在编辑器里持续迭代保存测试文件或被测源码后 Jest 立即反馈结果是调校断言与边界值的常用工作流。单元/集成测试与 Maestro 端到端测试的分层思想在 docs/concept/Testing.md 中有更完整的论述——Infinite Red 的测试哲学概括为一句话Write tests. Not too many. Mostly integration.写测试但别太多以集成测试为主其单元测试部分建议遵循 Given / When / Then即 AAAArrange, Act, Assert的结构来组织断言。编写自己的测试位置与示例按 docs/concept/Testing.md 的约定在app或test目录下创建*.test.ts/*.test.tsx文件即可被 Jest 自动发现。模板本身已提供两三类典型样例可直接作为范本组件测试testing-library/react-nativeboilerplate/app/components/Text.test.tsx 展示了如何用 React Native Testing Library 渲染真实组件import { NavigationContainer } from react-navigation/native import { render } from testing-library/react-native import { Text } from ./Text import { ThemeProvider } from ../theme/context const testText Test string describe(Text, () { it(should render the component, () { const { getByText } render( ThemeProvider NavigationContainer Text text{testText} / /NavigationContainer /ThemeProvider, ) expect(getByText(testText)).toBeDefined() }) })注意它用ThemeProvider与NavigationContainer包住被测组件——这正是依赖注入在测试中的体现组件依赖的上下文必须由测试显式提供。模板 devDependencies 中的testing-library/react-native版本^13.2.0即为此类组件测试准备的。纯函数测试apiProblem 与 MMKV 存储boilerplate/app/services/api/apiProblem.test.ts 是纯函数测试的典型它直接调用getGeneralApiProblem用一行expect(...).toEqual(...)验证每种错误类型连接错误、超时、401、403、404 等到业务错误kind的映射例如test(handles unauthorized errors, () { expect( getGeneralApiProblem({ problem: CLIENT_ERROR, status: 401 } as ApiErrorResponsenull), ).toEqual({ kind: unauthorized, }) })boilerplate/app/utils/storage/storage.test.ts 则针对load/save/remove/clear等存储 API 编写了读、写、删除、清空的完整用例并在beforeEach中调用storage.clearAll()重置状态保证用例之间互不污染——这正对应Given 前置条件 → When 动作 → Then 期望结果的书写范式。何时值得写单元测试并非每行代码都适合单元测试。按 docs/concept/Testing.md 的建议以下三类不含外部依赖、又有非平凡逻辑的代码收益最高复杂的正则表达式正则往往只有写的时候才看得懂用一组有效/无效输入固定其行为能保护未来的维护者深层嵌套的 if/else条件分支一旦超过一只手人工很难确认每条路径都被覆盖测试可以强制逐一访问校验类函数类似isJson()这样的函数若被关键逻辑依赖其正确性必须被测试锁定。需要 Mock 时的三件武器真实业务代码几乎总带着副作用——网络请求、原生模块、全局对象。在需要隔离测试单元时Jest 提供了三种层级的 mock 手段详见 docs/concept/Testing.md1. Mock 函数jest.fn创建带调用记录的替身函数之后可断言.mock.calls等属性const mockCallback jest.fn((x) 42 x) expect(mockCallback.mock.calls.length).toBe(2)2. Mock 模块jest.mock把axios之类的依赖整体替换为可控实现让测试不依赖真实网络jest.mock(axios) axios.get.mockImplementation(() Promise.resolve(res)) getUsers().then((data) { expect(data).toEqual(users) })3. Mock 原生模块对 React Native 的第三方原生模块可以直接替换为字符串或组件桩jest.mock(react-native-video, () Video);在 Ignite 模板中setup.ts正是把上述第 1、3 类策略规模化应用到了每个测试文件的执行前置阶段从而让业务测试专注于自身逻辑。小结Ignite 的测试体系设计思路清晰全局环境与基建 mock 收敛在boilerplate/test目录setup.ts、mockFile.ts、test-tsconfig.json业务测试分散在app各模块旁而i18n.test.ts以一条精心构造的 grep 命令 递归枚举 EXCEPTIONS白名单在测试套件层面为整个代码库的翻译键正确性兜底。理解这套约定后你既可以放心地在任意位置添加自己的*.test.ts也知道当动态 key 或注释误报时该如何通过EXCEPTIONS优雅放行。如果你希望进一步了解端到端测试与集成测试的完整分层可以继续阅读 docs/concept/Testing.md 与 docs/boilerplate/maestro.md想查看本文涉及的全部测试源码可前往 boilerplate/test、boilerplate/app/components/Text.test.tsx、boilerplate/app/services/api/apiProblem.test.ts 与 boilerplate/app/utils/storage/storage.test.ts。【免费下载链接】igniteInfinite Reds battle-tested React Native project boilerplate, along with a CLI, component/model generators, and more! 9 years of continuous development and counting.项目地址: https://gitcode.com/GitHub_Trending/ig/ignite创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价