关于 frappe-testing-cicd
面向 Frappe 应用开发者的 CI/CD 技能,汇集 GitHub Actions、代码检查、测试与发布配置建议,并说明常见故障的处理方式。
主要功能
- 01GitHub Actions 测试工作流配置
- 02pre-commit 与 Ruff 检查
- 03ESLint 配置建议
- 04测试覆盖率与报告生成
- 05Semgrep 安全规则示例
- 06CI 故障排查提示
适用场景
- 01为 Frappe 应用搭建 GitHub Actions CI
- 02配置 Python 与 JavaScript 代码检查
- 03在 CI 中运行测试并生成覆盖率
- 04排查数据库、Redis 或测试站点错误
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ impertio-studio/ frappe_claude_skill_package --skill frappe-testing-cicd安装后在 Claude Code 中输入/frappe-testing-cicd确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/.gitkeepanti-patterns.mdexamples.mdgithub-actions.mdlinting.md详细介绍
CI/CD 流水线
快速参考
| 任务 | 工具 / 文件 |
|---|---|
| 安装 pre-commit 钩子 | pre-commit install --hook-type pre-commit --hook-type commit-msg |
| 运行所有 pre-commit 检查 | pre-commit run --all-files |
| 运行代码检查器 | ruff check . |
| 运行格式化工具 | ruff format . |
| 运行 ESLint | npx eslint "**/*.js" --quiet |
| 在 CI 中运行测试 | bench --site test_site run-tests --app myapp |
| 并行运行测试 | bench --site test_site run-parallel-tests --total-builds 2 --build-number 0 |
| 生成覆盖率报告 | coverage run -m pytest && coverage xml |
| 生成 JUnit XML | bench --site test_site run-tests --junit-xml-output report.xml |
决策树:CI/CD 设置
Setting up CI for a Frappe app?
├─ Start with GitHub Actions workflow
│ ├─ ALWAYS include MariaDB + Redis services
│ ├─ ALWAYS use test matrix for Python versions
│ └─ Optionally add PostgreSQL for dual-DB support
├─ Add pre-commit hooks
│ ├─ ALWAYS include ruff (Python linting + formatting)
│ ├─ ALWAYS include eslint + prettier (JS/Vue)
│ └─ Add commitlint for conventional commits
├─ Add security scanning?
│ └─ YES → Add semgrep with Frappe-specific rules
└─ Need release automation?
└─ YES → Add tag-based release workflow
Frappe 应用的 GitHub Actions 工作流
标准服务端测试工作流
name: Server Tests
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
concurrency:
group: server-tests-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
python-version: ["3.11", "3.12"]
db: ["mariadb"]
services:
mariadb:
image: mariadb:11.4
ports:
- 3306:3306
env:
MARIADB_ROOT_PASSWORD: db_root
options: >-
--health-cmd="healthcheck.sh --connect --innodb_initialized"
--health-interval=5s
--health-timeout=5s
--health-retries=10
redis-cache:
image: redis:alpine
ports:
- 13000:6379
redis-queue:
image: redis:alpine
ports:
- 11000:6379
steps:
- name: Checkout frappe
uses: actions/checkout@v4
with:
repository: frappe/frappe
path: frappe-bench/apps/frappe
- name: Checkout app
uses: actions/checkout@v4
with:
path: frappe-bench/apps/myapp
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install bench
run: pip install frappe-bench
- name: Init bench
working-directory: frappe-bench
run: |
bench init --skip-assets --skip-redis-config-generation .
bench set-config -g db_root_password db_root
bench set-config -g redis_cache redis://localhost:13000
bench set-config -g redis_queue redis://localhost:11000
- name: Install app
working-directory: frappe-bench
run: |
bench get-app --skip-assets myapp ./apps/myapp
bench setup requirements --dev
bench new-site test_site \
--db-root-password db_root \
--admin-password admin \
--no-mariadb-socket
bench --site test_site install-app myapp
bench build --apps myapp
- name: Run tests
working-directory: frappe-bench
run: bench --site test_site run-tests --app myapp --failfast
- name: Upload coverage
if: always()
uses: actions/upload-artifact@v4
with:
name: coverage-${{ matrix.python-version }}-${{ matrix.db }}
path: frappe-bench/sites/coverage.xml
PostgreSQL 支持(额外的矩阵项)
strategy:
matrix:
include:
- python-version: "3.12"
db: "postgres"
services:
postgres:
image: postgres:16
ports:
- 5432:5432
env:
POSTGRES_PASSWORD: db_root
options: >-
--health-cmd pg_isready
--health-interval=10s
--health-timeout=5s
--health-retries=5
使用 PostgreSQL 时,请修改 bench new-site 命令:
bench new-site test_site \
--db-type postgres \
--db-root-password db_root \
--admin-password admin
Pre-Commit 配置
Frappe 应用的最简 .pre-commit-config.yaml
exclude: "node_modules|.git"
default_stages: [pre-commit]
fail_fast: false
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
files: "myapp.*"
exclude: ".*json$|.*txt$|.*csv$|.*md$|.*svg$"
- id: check-merge-conflict
- id: check-ast
- id: check-json
- id: check-toml
- id: check-yaml
- id: debug-statements
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.0
hooks:
- id: ruff
args: [--select=I, --fix]
name: ruff (import sorter)
- id: ruff
name: ruff (linter)
- id: ruff-format
name: ruff (formatter)
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v2.7.1
hooks:
- id: prettier
types_or: [javascript, vue, scss]
exclude: ".*dist.*|node_modules"
- repo: https://github.com/pre-commit/mirrors-eslint
rev: v8.44.0
hooks:
- id: eslint
types: [javascript]
args: [--quiet]
exclude: ".*dist.*|node_modules"
- repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook
rev: v9.16.0
hooks:
- id: commitlint
stages: [commit-msg]
additional_dependencies:
- conventional-changelog-conventionalcommits
克隆后务必运行 pre-commit install --hook-type pre-commit --hook-type commit-msg。
Ruff 配置(pyproject.toml)
[tool.ruff]
line-length = 110
target-version = "py311"
[tool.ruff.lint]
select = [
"F", # Pyflakes
"E", # pycodestyle errors
"W", # pycodestyle warnings
"I", # isort
"UP", # pyupgrade
"B", # flake8-bugbear
"RUF", # Ruff-specific rules
]
ignore = [
"E501", # line too long (handled by formatter)
"F401", # unused import (common in __init__.py)
"F403", # wildcard import (Frappe convention)
"F405", # undefined from wildcard (Frappe convention)
"E402", # module-level import order (Frappe convention)
]
[tool.ruff.format]
quote-style = "double"
indent-style = "tab"
docstring-code-format = true
[tool.ruff.lint.per-file-ignores]
# Ignore in auto-generated boilerplate
"**/doctype/**/boilerplate/**" = ["ALL"]
规则:
- Frappe 项目务必使用
indent-style = "tab"——这是框架约定 - 务必忽略 F401/F403/F405——Frappe 按惯例使用通配符导入
- 切勿将
line-length设为低于 110——Frappe 标准为 110 个字符
ESLint 配置
{
"env": {
"browser": true,
"node": true,
"es2021": true
},
"extends": "eslint:recommended",
"globals": {
"frappe": "readonly",
"cur_frm": "readonly",
"__": "readonly",
"cur_dialog": "readonly",
"cur_page": "readonly",
"cur_list": "readonly"
},
"rules": {
"no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],
"no-console": "warn"
}
}
务必声明 Frappe 全局变量(frappe、cur_frm、__ 等),否则 ESLint 会报告误报。
Semgrep 安全规则
# .semgrep/frappe-security.yml
rules:
- id: frappe-sql-injection
pattern: frappe.db.sql($X.format(...))
message: "NEVER use .format() in SQL — use parameterized queries"
severity: ERROR
languages: [python]
- id: frappe-raw-sql-concat
pattern: frappe.db.sql($X + ...)
message: "NEVER concatenate strings in SQL — use parameterized queries"
severity: ERROR
languages: [python]
- id: frappe-eval-usage
pattern: eval(...)
message: "NEVER use eval() — use frappe.safe_eval() instead"
severity: ERROR
languages: [python]
添加到 CI:
- name: Run Semgrep
uses: returntocorp/semgrep-action@v1
with:
config: .semgrep/
测试覆盖率
设置 coverage.py
# .coveragerc
[run]
source = myapp
omit =
*/test_*.py
*/tests/*
*/setup.py
[report]
exclude_lines =
pragma: no cover
if frappe.flags.in_test:
if TYPE_CHECKING:
CI 覆盖率步骤
- name: Run tests with coverage
working-directory: frappe-bench
run: |
cd apps/myapp
coverage run -m pytest
coverage xml -o ../../sites/coverage.xml
- name: Upload to Codecov
uses: codecov/codecov-action@v4
with:
file: frappe-bench/sites/coverage.xml
token: ${{ secrets.CODECOV_TOKEN }}
发布工作流
name: Release
on:
push:
tags:
- "v*"
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Generate changelog
id: changelog
uses: mikepenz/release-changelog-builder-action@v4
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
body: ${{ steps.changelog.outputs.changelog }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
分支保护规则
务必为 main 和 develop 配置以下分支保护规则:
- 要求状态检查:合并前必须通过 Server Tests
- 要求拉取请求审查:至少获得 1 个批准
- 要求分支保持最新:合并前强制变基
- 要求线性历史:强制使用 squash 或变基合并
通过 GitHub CLI 配置:
gh api repos/{owner}/{repo}/branches/main/protection -X PUT \
-f "required_status_checks[strict]=true" \
-f "required_status_checks[contexts][]=Server Tests" \
-f "required_pull_request_reviews[required_approving_review_count]=1"
常见 CI 故障及修复方法
| 故障 | 原因 | 修复方法 |
|---|---|---|
MariaDB not ready | 健康检查时间过短 | 将 --health-retries 增加到 10+ |
Redis connection refused | 端口映射错误 | 确认端口映射与 bench set-config 一致 |
ModuleNotFoundError | 未安装应用 | 确保同时运行 bench get-app 和 bench install-app |
Site not found | 未运行 bench new-site | 运行测试前务必创建站点 |
Permission denied on bench | pip 安装位置 | 使用 pip install frappe-bench,不要加 sudo |
Assets build failed | Node 版本不匹配 | 使用 Node 18 或 20(切勿使用 Node 16) |
frappe.exceptions.DoesNotExistError | 缺少测试固件 | 确保依赖的 DocType 存在 test_records.json |
Timeout in parallel tests | 并行构建过多 | 减少 --total-builds 或增加 timeout-minutes |
另请参阅
- references/examples.md — 完整的工作流示例
- references/anti-patterns.md — 应避免的 CI/CD 错误做法
- references/github-actions.md — 完整的 GitHub Actions 参考
- references/linting.md — 代码检查与格式化详解
- frappe-testing-unit — 单元测试与集成测试
评论
支持 Markdown