本文目录导读:

Git钩子脚本运行单元测试是实现持续集成和代码质量保证的有效方式,以下是详细的操作指南:
常用的Git钩子类型
适合运行测试的钩子
| 钩子名称 | 触发时机 | 用途 |
|---|---|---|
pre-commit |
执行git commit前 |
阻止提交到有失败测试的代码 |
pre-push |
执行git push前 |
阻止推送有失败测试的代码 |
commit-msg |
提交消息编辑后 | 验证提交信息格式 |
基础示例:pre-commit 钩子
创建钩子文件
# 进入项目根目录的 .git/hooks 目录 cd .git/hooks # 创建 pre-commit 钩子(去除默认的 .sample 后缀) touch pre-commit chmod +x pre-commit
Python项目的pre-commit钩子
#!/bin/bash
# .git/hooks/pre-commit
echo "🚀 运行单元测试..."
# 保存当前暂存的文件列表
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACMR | grep '\.py$')
if [ -z "$STAGED_FILES" ]; then
echo "✅ 没有需要测试的Python文件"
exit 0
fi
# 运行单元测试(仅测试被修改的文件)
python -m pytest $STAGED_FILES -v
if [ $? -ne 0 ]; then
echo "❌ 测试失败,提交被阻止"
exit 1
fi
echo "✅ 所有测试通过"
exit 0
JavaScript/TypeScript项目的pre-commit钩子
#!/bin/bash
# .git/hooks/pre-commit
echo "🏃 运行单元测试..."
# 运行所有测试
npm test
if [ $? -ne 0 ]; then
echo "❌ 测试失败,提交被阻止"
exit 1
fi
# 或者在lint-staged模式下只测试修改的文件
# npx jest --bail --findRelatedTests $(git diff --cached --name-only --diff-filter=ACMR | grep -E '\.(js|ts|jsx|tsx)$')
echo "✅ 所有测试通过"
exit 0
进阶:限制测试范围(性能优化)
仅测试修改的文件
#!/bin/bash
# 更高效的pre-commit钩子
function run_tests_for_staged() {
local staged_files=$(git diff --cached --name-only --diff-filter=ACMR)
if [ -z "$staged_files" ]; then
echo "没有暂存的文件"
exit 0
fi
# 根据项目类型选择测试工具
if [ -f "pytest.ini" ] || [ -f "setup.cfg" ]; then
echo "🔍 检测到Python项目"
python -m pytest $(echo "$staged_files" | grep '\.py$') -v --tb=short
elif [ -f "package.json" ]; then
echo "🔍 检测到Node.js项目"
npx jest --bail --findRelatedTests $(echo "$staged_files" | grep -E '\.(js|ts|jsx|tsx)$')
else
echo "未知项目类型,运行所有测试"
npm test || python -m pytest
fi
}
run_tests_for_staged
if [ $? -ne 0 ]; then
echo "❌ 测试失败"
exit 1
fi
使用工具管理钩子
推荐使用工具来管理钩子,避免手动操作:
使用 Husky (JavaScript/TypeScript)
# 安装 husky npm install husky --save-dev # 初始化 husky npx husky install # 创建 pre-commit 钩子 npx husky add .husky/pre-commit "npm test" # .husky/pre-commit 文件内容 #!/bin/sh . "$(dirname "$0")/_/husky.sh" npm test
使用 pre-commit (Python/多语言)
# 安装 pre-commit
pip install pre-commit
# 创建 .pre-commit-config.yaml
cat > .pre-commit-config.yaml << 'EOF'
repos:
- repo: local
hooks:
- id: pytest
name: pytest
entry: pytest
language: system
types: [python]
pass_filenames: false
always_run: true
args: ['-v', '--tb=short']
EOF
# 安装钩子
pre-commit install
最佳实践
速度优化
#!/bin/bash
# 优化后的 pre-commit 钩子
# 1. 并行执行测试(如果支持)
function run_tests_parallel() {
echo "Running tests in parallel..."
pytest -n auto -v &
TEST_PID=$!
wait $TEST_PID
}
# 2. 缓存测试结果
pytest --cache-clear --last-failed
# 3. 只测试受影响的模块
git diff --cached --name-only | grep '\.py$' | xargs pytest -v
跳过测试的特殊情况
#!/bin/bash
# 允许带参数跳过测试
SKIP_TESTS=$(git log -1 --format=%B | grep -c "\[skip-tests\]")
if [ "$SKIP_TESTS" -gt 0 ]; then
echo "⏭️ 跳过测试(commit message包含 [skip-tests])"
exit 0
fi
# 或者通过环境变量
if [ "$SKIP_GIT_HOOKS" = "true" ]; then
echo "⏭️ 跳过测试(环境变量 SKIP_GIT_HOOKS=true)"
exit 0
fi
# 正常运行测试
npm test
完整的生产环境示例
#!/bin/bash
# .git/hooks/pre-commit - 生产环境版本
set -euo pipefail
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo -e "${YELLOW}🏃 启动预提交检查...${NC}"
# 1. 语法检查
echo -e "${YELLOW}📝 检查代码语法...${NC}"
if [ -f "package.json" ]; then
npx eslint --quiet .
elif [ -f "setup.cfg" ]; then
python -m py_compile $(git diff --cached --name-only --diff-filter=ACMR | grep '\.py$')
fi
# 2. 运行单元测试
echo -e "${YELLOW}🧪 运行单元测试...${NC}"
if [ -f "package.json" ]; then
npm test -- --bail --silent
elif [ -f "pytest.ini" ] || [ -f "setup.cfg" ]; then
python -m pytest -v --tb=short --disable-warnings -q
fi
if [ $? -ne 0 ]; then
echo -e "${RED}❌ 测试失败,提交被阻止${NC}"
echo -e "${YELLOW}💡 提示: 如果需要跳过测试,使用 'git commit --no-verify'${NC}"
exit 1
fi
echo -e "${GREEN}✅ 所有检查通过,可以提交${NC}"
exit 0
注意事项
- 性能影响:测试运行会增加提交时间,建议只运行必要的测试
- 依赖管理:确保测试环境在钩子中可用(如虚拟环境、node_modules)
- 跨平台兼容:注意路径分隔符和命令的兼容性
- 错误处理:提供清晰的错误信息和跳过选项
- 版本控制:将钩子脚本纳入项目仓库(非.git/hooks目录,而是使用工具管理)
使用Git钩子运行单元测试可以显著提高代码质量,但需要平衡速度和覆盖范围。