Heroku CI #
CI 概述 #
Heroku CI 是 Heroku 内置的持续集成服务,可以自动运行测试,确保代码质量。
CI 流程 #
text
┌─────────────────────────────────────────────────────┐
│ Heroku CI 流程 │
├─────────────────────────────────────────────────────┤
│ │
│ 代码推送 │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────┐ │
│ │ 创建测试环境 │ │
│ │ ├── 创建临时 Dyno │ │
│ │ ├── 设置环境变量 │ │
│ │ └── 配置 Add-ons │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────┐ │
│ │ 运行测试 │ │
│ │ ├── 安装依赖 │ │
│ │ ├── 执行测试脚本 │ │
│ │ └── 收集测试结果 │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ├──────────────┬──────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ 测试通过 测试失败 测试跳过 │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ 允许部署 阻止部署 继续流程 │
│ │
└─────────────────────────────────────────────────────┘
启用 CI #
在 Pipeline 中启用 #
bash
# 创建 Pipeline
heroku pipelines:create myapp-pipeline -a myapp-staging -s staging
# 启用 CI
heroku ci:enable -a myapp-staging
# 查看 CI 状态
heroku ci:info
app.json 配置 #
json
{
"name": "myapp",
"environments": {
"test": {
"scripts": {
"test": "npm test"
},
"addons": [
"heroku-postgresql:mini",
"heroku-redis:mini"
],
"env": {
"NODE_ENV": "test"
}
}
}
}
测试配置 #
Node.js 测试 #
json
// package.json
{
"scripts": {
"test": "jest",
"test:coverage": "jest --coverage",
"test:watch": "jest --watch"
}
}
json
// app.json
{
"environments": {
"test": {
"scripts": {
"test": "npm run test:coverage"
}
}
}
}
Python 测试 #
json
// app.json
{
"environments": {
"test": {
"scripts": {
"test": "pytest --cov=app tests/"
}
}
}
}
Ruby 测试 #
json
// app.json
{
"environments": {
"test": {
"scripts": {
"test": "bundle exec rspec"
}
}
}
}
测试数据库 #
配置测试数据库 #
json
// app.json
{
"environments": {
"test": {
"addons": [
"heroku-postgresql:mini"
],
"env": {
"NODE_ENV": "test"
}
}
}
}
使用测试数据库 #
javascript
// jest 配置
module.exports = {
testEnvironment: 'node',
setupFilesAfterEnv: ['./tests/setup.js']
};
// tests/setup.js
const { Pool } = require('pg');
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: { rejectUnauthorized: false }
});
beforeAll(async () => {
// 创建测试表
await pool.query(`
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(255)
)
`);
});
afterAll(async () => {
await pool.end();
});
测试命令 #
查看测试运行 #
bash
# 查看最近的测试运行
heroku ci:runs
# 输出示例
# === CI Runs
# Run # Status Branch Commit Created
# ───── ────── ────── ────── ───────
# 42 passed main abc123 10 minutes ago
# 41 failed main def456 1 hour ago
# 查看特定测试运行详情
heroku ci:run 42
# 查看测试日志
heroku ci:run 42 --log
手动触发测试 #
bash
# 手动触发测试
heroku ci:run
# 指定分支
heroku ci:run --branch feature-branch
测试环境变量 #
配置测试环境变量 #
json
// app.json
{
"environments": {
"test": {
"env": {
"NODE_ENV": "test",
"DATABASE_URL": {
"from_database": {
"name": "heroku-postgresql",
"addon": "heroku-postgresql:mini"
}
}
}
}
}
}
使用密钥 #
bash
# 设置测试环境密钥
heroku config:set TEST_API_KEY=xxx --app myapp-staging
# CI 会自动继承应用的环境变量
测试报告 #
JUnit 报告 #
javascript
// jest 配置
module.exports = {
reporters: [
'default',
['jest-junit', { outputDirectory: './reports' }]
]
};
覆盖率报告 #
javascript
// jest 配置
module.exports = {
coverageReporters: ['text', 'lcov', 'html'],
coverageDirectory: './coverage'
};
GitHub Actions 替代方案 #
完整 CI/CD 配置 #
yaml
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
redis:
image: redis:7
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
env:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/test
REDIS_URL: redis://localhost:6379
NODE_ENV: test
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Deploy to Heroku
uses: akhileshns/heroku-deploy@v3.13.15
with:
heroku_api_key: ${{ secrets.HEROKU_API_KEY }}
heroku_app_name: myapp-staging
heroku_email: ${{ secrets.HEROKU_EMAIL }}
测试最佳实践 #
1. 测试结构 #
text
tests/
├── unit/ # 单元测试
│ ├── user.test.js
│ └── product.test.js
├── integration/ # 集成测试
│ ├── api.test.js
│ └── auth.test.js
└── e2e/ # 端到端测试
└── flow.test.js
2. 测试脚本 #
json
// package.json
{
"scripts": {
"test": "jest",
"test:unit": "jest tests/unit",
"test:integration": "jest tests/integration",
"test:e2e": "jest tests/e2e",
"test:coverage": "jest --coverage",
"test:watch": "jest --watch"
}
}
3. 测试数据库隔离 #
javascript
// 每个测试使用独立的数据库
beforeEach(async () => {
await pool.query('BEGIN');
});
afterEach(async () => {
await pool.query('ROLLBACK');
});
故障排查 #
测试超时 #
javascript
// 增加测试超时时间
jest.setTimeout(30000);
// 或在特定测试中
test('long running test', async () => {
// ...
}, 30000);
数据库连接问题 #
javascript
// 检查数据库连接
beforeAll(async () => {
try {
await pool.query('SELECT 1');
console.log('Database connected');
} catch (error) {
console.error('Database connection failed:', error);
throw error;
}
});
环境变量问题 #
bash
# 检查 CI 环境变量
heroku ci:run 42 --log | grep -i env
下一步 #
CI 掌握后,接下来学习 私有空间 了解网络隔离配置!
最后更新:2026-03-28