CodeRoad Map 程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。
由 程序员柏风 独立维护 · 关于作者
学习 更多
测试与发布
这一课你将能做到
用 jest-expo 和 @testing-library/react-native 给工具函数与组件写基础测试
用 EAS Build 在云端打出 iOS / Android 安装包,走通 TestFlight 内测与商店上架
理解 OTA 热更新的边界与 runtimeVersion 管理,安全地推送线上修复
为什么要学
代码写完到用户手里,中间隔着「测试 → 构建 → 上架 → 更新」四道关。原生时代每道关都要配 Xcode、证书、签名、开发者账号,劝退无数前端工程师。Expo 的 EAS(Expo Application Services)把它们全部云化——从代码到安装包一键出,没有 Mac 也能上架 iOS。这是 Expo 对初学者最大的善意。
测试同样如此。很多人写 App 靠「自己点一遍」,但 20 个功能 × 5 种状态 × 2 个平台 = 200 种组合,手动点不过来。Jest 单元测试加 Testing Library 的组件交互测试,把回归成本降到零。
人话:EAS Build 就像云打印店——你把源码(电子稿)发给它,它在云端用 Mac 机器帮你出 iOS 安装包(你本地连 Mac 都没有也行),印好了给你取件码(下载链接)。而 OTA 热更新则是「云打印店还能帮你换页眉」——只改 JS 文案或逻辑,不用重新出书(商店审核),用户下次打开自动生效。
先建立图像
从代码到用户的完整发布路线图:
本地开发完成
↓
Jest 测试通过(工具函数 + 核心组件交互)
↓
eas build --profile preview(出可内测包)
→ Android:直接给同事装 APK / 二维码
→ iOS:TestFlight 邀请内测邮箱
↓
内测没问题
↓
eas build --profile production(出商店包)
→ 手动上传到 App Store Connect / Google Play Console
→ 或 eas submit 自动提交
↓
审核通过,用户下载
↓
日常修 bug / 改文案:eas update --branch production(OTA,免审核)
跟着做
步骤 1:Jest 测试环境
Expo 项目自带 jest-expo 预设,装一下就能跑:
npx expo install jest-expo jest --dev
package.json 加脚本和预设:
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch"
},
"jest": {
"preset": "jest-expo"
}
}
然后 npm test 就能跑。jest-expo 预设帮你处理好了 RN 组件的 mock(AsyncStorage、相机模块等),不需要额外配置。
步骤 2:写第一个工具函数测试
// utils/format.js
export const formatPrice = (cents) => {
if (typeof cents !== 'number' || cents < 0) return '¥0.00';
return `¥${(cents / 100).toFixed(2)}`;
};
export const truncate = (str, len = 20) => {
if (!str || str.length <= len) return str;
return str.slice(0, len) + '...';
};
// utils/__tests__/format.test.js
import { formatPrice, truncate } from '../format';
describe('formatPrice', () => {
test('正常价格', () => {
expect(formatPrice(999)).toBe('¥9.99');
expect(formatPrice(0)).toBe('¥0.00');
expect(formatPrice(123456)).toBe('¥1234.56');
});
test('边界值', () => {
expect(formatPrice(-1)).toBe('¥0.00');
expect(formatPrice(null)).toBe('¥0.00');
expect(formatPrice('abc')).toBe('¥0.00');
});
});
describe('truncate', () => {
test('短字符串不截断', () => {
expect(truncate('你好')).toBe('你好');
});
test('长字符串截断加省略号', () => {
expect(truncate('这是一段非常长的文字内容', 8)).toBe('这是一段非常长的...');
});
});
运行 npm test,绿了继续。工具函数测试是投入产出比最高的:输入固定、输出确定、没有 UI 依赖、跑得快、覆盖业务核心逻辑。
步骤 3:组件交互测试 给 @testing-library/react-native 装依赖(jest-expo 预设已兼容):
npx expo install @testing-library/react-native --dev
// components/Counter.js
import { useState } from 'react';
import { Button, Text, View } from 'react-native';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<View>
<Text testID="count">{count}</Text>
<Button title="点我 +1" onPress={() => setCount(c => c + 1)} />
</View>
);
}
// components/__tests__/Counter.test.js
import { render, screen, fireEvent } from '@testing-library/react-native';
import Counter from '../Counter';
test('点按钮计数加一', () => {
render(<Counter />);
// 以用户视角查元素:findByText 查找可见文字
const btn = screen.getByText('点我 +1');
fireEvent.press(btn);
// 验证结果:计数文本变成 1
expect(screen.getByText('1')).toBeTruthy();
});
test('初始状态是 0', () => {
render(<Counter />);
expect(screen.getByTestId('count').children[0]).toBe('0');
});
Testing Library 的核心哲学:以用户视角测试组件。用户看不到组件内部 state,用户看到的是文字和按钮。所以测试里不写 expect(wrapper.state()),而是写 expect(screen.getByText(...))——测试越贴近真实用户操作,越有保护力。
步骤 4:EAS 云端构建 npm install -g eas-cli
eas login # 用 Expo 账号登录(免费注册)
eas build:configure # 自动检测项目,生成 eas.json
eas.json 是 EAS 的配置中心,三种 profile 对应三种场景:
{
"cli": { "version": ">= 7.0.0" },
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"preview": {
"distribution": "internal",
"android": { "buildType": "apk" }
},
"production": {
"autoIncrement": true
}
}
}
development :带调试器的开发包(可连 Flipper / React DevTools),日常开发用,需要 expo-dev-client
preview :给同事/朋友装的内测包,出 APK(安卓直接装)或 ad-hoc IPA(iOS 指定设备安装)
production :提交商店审核的包,开启代码优化、资源压缩
# Android 内测包(出 APK,发群里就能装)
eas build --platform android --profile preview
# iOS 内测包(出 IPA,需 Apple 开发者账号 $99/年)
eas build --platform ios --profile preview
# 生产包(上架用)
eas build --platform all --profile production
构建在 Expo 的服务器上进行,本地不需要 Mac、不需要 Android Studio,完成后给你一个下载链接或二维码。构建日志可以在网页上实时查看。
步骤 5:内测分发 Android :最简单。APK 链接发给同事直接安装,或者上传到自己的 CDN / 分发平台(如蒲公英)。
# 把 preview 包提交到 App Store Connect
eas submit --platform ios
然后在 App Store Connect 后台:
进入「我的 App」→「TestFlight」
添加内部测试员(用你的 Apple ID 就行,最多 100 人)
测试员会收到邮件,安装 TestFlight App,下载内测版本
步骤 6:正式上架
App Store Connect 填资料:应用名、副标题、截图(6.5 寸 iPhone + 12.9 寸 iPad)、描述、隐私问卷
隐私合规是近年审核重点:收集了什么数据、用到了什么权限,必须如实声明
提交审核,通常 1-3 天出结果。被拒原因 80% 是:权限用途文案不清、测试账号没提供、隐私声明不完整
Google Play Console 填类似资料
国内上架还要:① 各手机厂商应用商店(华为、小米、OPPO、vivo…);② 软件著作权 (国内应用商店硬性要求,提前申请);③ 隐私合规自评估报告
隐私合规提示 :App 里用到的每一个权限、收集的每一个字段,都必须在上架资料里如实声明。别抱侥幸心理——审核系统越来越智能,虚假声明会被下架甚至封号。
步骤 7:OTA 热更新(日常修 bug 的神器) 改了 JS 代码或文案,不想走商店审核?OTA 热更新让你分钟级推送修复:
# 推送一条更新到 production 分支
eas update --branch production --message "修复首页加载慢的问题"
用户下次打开 App 时,会自动拉取新的 JS bundle 并重启生效。整个过程用户无感知(可以在后台静默完成)。
OTA 的硬性边界 (这条边界决定了你的开发策略):
能走 OTA 必须重新 build 上架 JS 代码改动 新增原生模块(如装了 expo-camera) 样式调整 权限变更(新增权限声明) 文案/逻辑修改 app.json 原生配置变更 图片/静态资源替换 修改原生代码(Swift/Kotlin)
也就是说:你加了个按钮、改了 API 接口地址、调了文案——OTA 搞定。你新装了 expo-location 要调定位——必须重新 build 上架。
runtimeVersion 管理 :OTA 只能推给「原生层匹配」的 App。如果新版本改了大量原生依赖,runtimeVersion 变了,旧版本 App 不会收到这条更新(否则原生层不支持新 JS 代码会白屏崩溃)。
{
"expo": {
"runtimeVersion": { "policy": "appVersion" } // 按 app.json 的 version 自动管理
}
}
推荐用 appVersion 策略自动管理,别手动写字符串——手动维护版本号极容易推错。
讲清楚要点
测试金字塔在 RN 同样成立 :大量工具函数单测(快、稳、覆盖核心逻辑) + 少量关键组件交互测试(模拟用户操作) + 极少量 E2E(真机跑流程)。别追求 UI 全覆盖,投入产出比低,维护成本高。
EAS Build 的三种 profile 分工明确 :development 连调试器、preview 给同事装、production 上架。日常开发用 development,发给别人用 preview,只有上架才出 production。
iOS 上架绕不开 Apple Developer Program :$99/年(个人或公司账号),TestFlight 内测和 App Store 上架都需要。这是苹果收的入场券,省不了。好消息是 EAS 让你在云端完成全部构建和签名,本地不需要 Mac。
OTA 是 RN 的超级武器 :JS 层改动分钟级全量推送,零审核等待。但加了新的原生依赖(比如新装了 expo-camera)必须重新 build 上架。开发策略上:尽量把变更控制在 JS 层,最大化利用 OTA 提速。
隐私合规不是可选项 :无论 iOS 还是 Android,权限和数据收集声明都是审核红线。App 里用什么写什么,措辞要让审核员和用户都看得懂。
没有 Mac 也能上架 iOS :EAS Build + eas submit 全在云端完成,证书也可以用 eas credentials 托管。这是 Expo 相对纯 RN 开发流程的最大优势之一。
你可能会懵的地方
没有 Mac 怎么搞 iOS 上架?
完全不需要 Mac。EAS Build 在云端 Mac 上构建,eas submit 自动提交到 App Store Connect,证书用 eas credentials 命令让 EAS 云端托管生成。你唯一需要本地操作的是:在 Apple Developer 网站申请开发者账号、在 App Store Connect 填上架资料。构建和签名全部云端化。
OTA 热更新会被苹果拒吗?
不会——苹果明确允许「不实质性改变 App 用途」的 JS 热更新。EAS Update 和之前的 CodePush 都被数百万 App 使用。但红线是:不能用 OTA 把 App 的核心功能完全替换(比如把计算器改成赌博 App),这种会被下架。
EAS 收费吗?
有免费额度(每月有限构建次数和 OTA 推送量),个人学习和小团队内测够用。高频构建或大团队需要订阅 EAS 付费计划。如果只是本地开发,完全免费。
测试要写到什么程度?
工具函数、数据转换、关键业务逻辑(如价格计算、权限判断)必须有测试;UI 组件挑核心交互测(点按钮、表单提交、列表加载);网络请求一律 mock,不要调真实接口。覆盖率不是目标,「关键路径不出回归 bug」才是。
国内上架有什么额外要求?
① 软件著作权(软著),国内应用商店硬性要求;② 各厂商商店(华为、小米、OPPO、vivo)需要分别申请开发者账号并单独提交;③ 隐私合规自评估报告;④ ICP 备案(含网络服务的 App)。其中软著办理周期 1-2 个月,建议提前准备。
Android 的 APK 和 AAB 怎么选?
preview 默认出 APK,可以直接安装;Google Play 上架要求上传 AAB(Android App Bundle),EAS production 构建默认出 AAB。国内应用商店大多也接受 AAB,少数(如小米早期版本)可能仍要 APK,按商店要求调整 eas.json 的 buildType 即可。
错法对照 错法 问题 正解 本地改了原生依赖还发 OTA 用户端打开白屏或崩溃 原生变更必须重新 build 上架 只在模拟器测试就上架 真机性能、权限、手势问题全漏 至少一台 iOS + 一台 Android 真机完整过一遍 隐私权限乱声明 / 不声明 审核被拒 用了什么写什么,文案清晰、用户视角 runtimeVersion 手动写死 OTA 推送到不匹配的原生包,白屏 用 policy: "appVersion" 自动管理 不发 preview 直接 production 内测问题全带给最终用户 先 preview → TestFlight → 上架 测试里直接请求真实接口 测试慢、不稳定、依赖网络 mock fetch / axios 上架资料截图用模拟器截 苹果审核要求用真机截图 用真机截,或直接按尺寸规范合成 生产包 console.log 没删 JS 线程被同步日志拖慢 生产环境去掉 console.log,或用 babel 插件自动移除
卡点急救
eas build 排队太久 :免费账户高峰期排队(尤其 iOS build 用 Mac 资源更紧张),错峰构建或升级 EAS 付费计划。Android 本地构建可以 eas build --platform android --local 在本地出包。
TestFlight 构建版本不显示 :检查 version / build 号是否比上一次递增;刚上传的构建需要苹果服务器处理几分钟;TestFlight 里要切换到正确的 App 版本。
OTA 更新后用户没收到 :确认分支(branch)和 runtimeVersion 匹配——旧 App 只拉旧 runtimeVersion 的更新;更新在下次冷启动时生效(用户杀掉 App 再打开),不是实时的。
App Store 审核被拒 :先看拒审邮件里的具体原因,通常集中在:① 权限用途文案不清晰;② 没有提供测试账号;③ 隐私声明不完整;④ 功能描述和实际体验不符。针对性修复后回复审核团队即可。
Android 安装包太大 :检查是否误打包了原生库的多架构版本(arm64 + armeabi + x86);EAS production 构建默认只出 arm64,不需要手动处理。
面试常问
EAS Build 相比本地打包的优势?
① 云端构建不需要本地 Mac / Android Studio 环境;② 证书统一托管,团队共享构建产物;③ CI/CD 集成简单(GitHub Actions 调用 eas build 一行搞定);④ 跨平台统一配置(一个 eas.json 管两端)。劣势是对第三方服务的依赖,极敏感场景可能选择本地自主构建。
OTA 热更新的原理和边界?
App 启动时检查 Expo 更新服务器,下载新的 JS bundle 替换本地缓存,然后重启生效。边界:只能更新 JS 代码和静态资源;原生代码变更(新装原生模块、权限配置变更、AndroidManifest / Info.plist 修改)必须走商店发版。runtimeVersion 保证旧版 App 不会收到不兼容的新 bundle。
RN 项目怎么做测试?
单元测试用 Jest(jest-expo 预设已配好 RN 环境);组件交互测试用 React Native Testing Library(以用户视角查元素、触发事件、断言可见结果);E2E 用 Maestro 或 Detox 跑真机流程。网络层一律 mock,不要依赖真实接口。
自测三问
EAS 的 development / preview / production 三种 profile 各自用途是什么?
什么改动可以走 OTA 热更新?什么改动必须重新 build 上架?
没有 Mac 的情况下,如何完成 iOS 构建和 App Store 上架?
小结一句话 从代码到用户:Jest 保质量、EAS 管构建、TestFlight 做内测、商店审核上架、OTA 做日常更新——Expo 把发布这条最难的路铺平了。
延伸 至此 React Native Essentials 系列完结。如果这 12 课的内容你已经跟着跑通了代码,强烈建议挑一个真实小项目(记账 App、天气查询、待办清单)完整走一遍「开发 → 测试 → 构建 → 上架 → OTA 更新」的全流程。真实项目是最好的老师,只有亲手把 App 装进用户手机,这些知识才会变成肌肉记忆。