大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
测试与发布
这一课你将能做到
- 用 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 云端构建
EAS CLI 安装与登录:
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
}
}
}
三种 profile 的用途:
- 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 / 分发平台(如蒲公英)。
iOS:TestFlight 是标准流程:
# 把 preview 包提交到 App Store Connect
eas submit --platform ios
然后在 App Store Connect 后台:
- 进入「我的 App」→「TestFlight」
- 添加内部测试员(用你的 Apple ID 就行,最多 100 人)
- 测试员会收到邮件,安装 TestFlight App,下载内测版本
步骤 6:正式上架
iOS(App Store):
- App Store Connect 填资料:应用名、副标题、截图(6.5 寸 iPhone + 12.9 寸 iPad)、描述、隐私问卷
- 隐私合规是近年审核重点:收集了什么数据、用到了什么权限,必须如实声明
- 提交审核,通常 1-3 天出结果。被拒原因 80% 是:权限用途文案不清、测试账号没提供、隐私声明不完整
Android(Google Play):
- 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 装进用户手机,这些知识才会变成肌肉记忆。