Jenkins -- 自动化部署Python项目
前言:
本次记录基于 Linux(CentOS)Stream 9
系统,实现 Git
+Jenkins
自动拉取并启动 Python
的 Pytest
项目。
一、环境准备:
# Python3 和 pip
sudo dnf install -y python39 python39-pip python39-devel
# Git
sudo dnf install -y git
# 虚拟环境(隔离项目依赖)
sudo pip3 install virtualenv
通过 GitHub 下载 Allure
# 下载 Allure 2.25.0(最新稳定版)
wget https://github.com/allure-framework/allure2/releases/download/2.25.0/allure-2.25.0.zip
# 安装 unzip
sudo dnf install -y unzip
# 解压
unzip allure-2.25.0.zip -d /opt/
# 创建软链接(allure目录,为后续Jenkins中的allure插件使用)
sudo ln -s /opt/allure-2.25.0 /opt/allure
# 测试 allure
/opt/allure/bin/allure --version
核验Jenkins的运行用户是否适配环境:
①、查看jenkins通过啥用户运行的
ps aux | grep java | grep jenkins
②、临时切换jenkins运行用户,并测试依赖权限是否正常
# 测试 Python
sudo -u jenkins python3 --version
# 测试 pip
sudo -u jenkins pip3 --version
# 测试 Git
sudo -u jenkins git --version
二、Jenkins 配置
1、安装/检查插件
- Git
- Pipeline(用于写 Jenkinsfile)
- Workspace Cleanup Plugin(清理工作空间)
- Allure(用于展示测试报告链接)
- Email Extension Plugin(邮件插件)
2、进行Allure配置
3、创建流水线任务
4、添加git连接凭证
①、在linux上生成了公私钥,且将公钥配置到gitee上。
操作可参考该链接:https://help.gitee.com/base/account/SSH%E5%85%AC%E9%92%A5%E8%AE%BE%E7%BD%AE
# 1. 创建 jenkins 的 .ssh 目录(如果不存在)
sudo -u jenkins mkdir -p /var/lib/jenkins/.ssh
# 2. 生成 Ed25519 密钥对
sudo -u jenkins ssh-keygen -t ed25519 -b 4096 -C "729513753@qq.com" -f /var/lib/jenkins/.ssh/id_ed25519 -N ""
# 3. 设置正确的权限(非常重要!)
sudo chmod 700 /var/lib/jenkins/.ssh
sudo chmod 600 /var/lib/jenkins/.ssh/id_ed25519
sudo chmod 644 /var/lib/jenkins/.ssh/id_ed25519.pub
sudo chown -R jenkins:jenkins /var/lib/jenkins/.ssh
# 4. 查看公钥
cat /var/lib/jenkins/.ssh/id_ed25519.pub
将公钥配置到gitee上
②、为jenkins用户信任Gitee
sudo -u jenkins ssh -T git@gitee.com
然后 yes
确认即可。(不基于jenkins这个用户操作这步,是连接不通gitee的!!!)
③、jenkins流水线配置git
新增凭证:
5、新增Jenkinsfile脚本
大致结构:
pipeline {
agent any // 在任何可用的 Jenkins Agent 上运行
environment { ... } // 定义环境变量
stages { ... } // 分阶段执行任务
post { ... } // 后置处理(无论成功/失败都会执行)
}
脚本内容:
pipeline {
agent any
environment {
VENV = 'venv'
REPORT_DIR = 'reports'
RESULTS_DIR = 'reports/results'
HTML_DIR = 'reports/html'
LOG_DIR = 'logs'
CONFIG_DIR = 'config'
ALLURE_REPORT_NAME = '我的自动化测试报告'
}
stages {
stage('初始化') {
steps {
script {
// 在 pipeline 中定义变量,支持 Jenkins 内置变量
ALLURE_HISTORY_PATH = "${JENKINS_HOME}/userContent/allure-history/${JOB_NAME}"
echo "Allure 历史数据路径: ${ALLURE_HISTORY_PATH}"
}
}
}
stage('创建虚拟环境') {
steps {
sh '''
echo "创建 Python 虚拟环境..."
python3 -m venv ${VENV}
python3 --version
echo "虚拟环境创建成功: ${VENV}"
'''
}
}
stage('安装依赖') {
steps {
sh '''
echo "激活虚拟环境并升级 pip..."
source ${VENV}/bin/activate
pip install --upgrade pip -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn
pip cache purge
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn
echo "依赖安装完成"
'''
}
}
stage('生成环境变量文件') {
steps {
sh '''
source ${VENV}/bin/activate
python3 ${CONFIG_DIR}/generate_env.py
echo "环境变量文件生成完成"
'''
}
}
stage('准备报告目录') {
steps {
sh '''
mkdir -p ${RESULTS_DIR} ${HTML_DIR}
echo "报告目录创建完成"
'''
}
}
stage('恢复 Allure 历史数据') {
steps {
script {
def historyDir = "${ALLURE_HISTORY_PATH}/history"
if (sh(script: "test -d '${historyDir}'", returnStatus: true) == 0) {
sh """
echo "恢复历史数据..."
cp -r '${historyDir}' '${RESULTS_DIR}/'
"""
} else {
echo "未发现历史数据,本次为首次运行"
}
}
}
}
stage('运行 Pytest 测试') {
steps {
script {
// 直接在这里执行 pytest 并处理退出码
def exitCode = sh(
script: """
source venv/bin/activate
pytest testcases/test_sdk_api.py --alluredir=reports/results -v
""",
returnStatus: true // 关键:返回状态码,而不是抛异常
)
if (exitCode == 0) {
echo "测试全部通过"
// 构建继续
} else if (exitCode == 1) {
echo "有测试用例失败"
currentBuild.result = 'UNSTABLE'
// 构建继续,标记为黄色
} else {
echo "pytest 执行异常(exit code: ${exitCode})"
error("Pytest 执行失败,可能是命令不存在或环境错误,终止构建")
}
}
}
}
stage('持久化 Allure 历史数据') {
steps {
sh """
echo "持久化历史数据..."
mkdir -p '${ALLURE_HISTORY_PATH}'
rm -rf '${ALLURE_HISTORY_PATH}/history'
if [ -d '${HTML_DIR}/history' ]; then
cp -r '${HTML_DIR}/history' '${ALLURE_HISTORY_PATH}/'
echo "历史数据已保存至: ${ALLURE_HISTORY_PATH}"
else
echo "未生成 history 目录"
fi
"""
}
}
stage('归档报告与结果') {
steps {
archiveArtifacts(
artifacts: "${LOG_DIR}/*",
fingerprint: true,
allowEmptyArchive: true
)
}
}
}
post {
success {
echo '✅ 构建成功,测试全部通过!'
}
unstable {
echo '⚠️ 构建完成,但有测试失败'
echo "📊 查看报告: ${BUILD_URL}allure"
}
failure {
echo '❌ 构建失败!可能是脚本错误、依赖问题或 pytest 执行异常'
echo "📎 检查日志: ${BUILD_URL}console"
}
always {
echo "构建状态: ${currentBuild.result}"
// ✅ 使用 Allure 插件发布报告(核心)
allure([
includeProperties: false,
jdk: '',
properties: [],
reportBuildPolicy: 'ALWAYS', // 无论成功失败都生成报告
results: [[path: 'reports/results']] // 必须和 --alluredir 一致
])
}
}
}
6、添加Clean before checkout
三、查看报告
离线看allure报告
下载解压 allure-report.zip 后进入目录,在cmd执行下列指令
allure open .
四、添加邮件通知
1、为系统管理员添加邮箱,不配置会发送邮件失败!
2、去QQ邮箱等邮箱设置中生成对应信息填入。
3、优化Jenkinsfile脚本
post {
success {
echo '✅ 构建成功,测试全部通过!'
emailext(
subject: "✅ 构建成功: ${JOB_NAME} #${BUILD_NUMBER}",
body: """
<p>项目: <strong>${JOB_NAME}</strong></p>
<p>构建号: ${BUILD_NUMBER}</p>
<p>状态: <span style='color:green;'>SUCCESS</span></p>
<p>分支: ${env.BRANCH_NAME}</p>
<p>构建耗时: ${currentBuild.durationString}</p>
<p>触发人: ${env.CHANGE_AUTHOR ?: 'System'}</p>
<p>构建日志: <a href='${BUILD_URL}'>点击查看</a></p>
<p><img src='${BUILD_URL}buildStatus/icon' /> Jenkins 构建状态图标</p>
""",
recipientProviders: [
[$class: 'DevelopersRecipientProvider'], // 提交代码的人
[$class: 'RequesterRecipientProvider'] // 触发构建的人
],
to: 'xu.chunjie@foxmail.com',
mimeType: 'text/html'
)
}
unstable {
echo '⚠️ 构建完成,但有测试失败'
echo "📊 查看报告: ${BUILD_URL}allure"
emailext(
subject: "⚠️ 构建不稳定: ${JOB_NAME} #${BUILD_NUMBER}",
body: """
<p>项目: ${JOB_NAME}</p>
<p>状态: UNSTABLE(测试失败)</p>
<p>请检查测试报告。</p>
<p><a href='${BUILD_URL}'>构建地址</a></p>
""",
to: 'xu.chunjie@foxmail.com',
mimeType: 'text/html'
)
}
failure {
echo '❌ 构建失败!可能是脚本错误、依赖问题或 pytest 执行异常'
echo "📎 检查日志: ${BUILD_URL}console"
emailext(
subject: "❌ 构建失败: ${JOB_NAME} #${BUILD_NUMBER}",
body: """
<p>项目: <strong>${JOB_NAME}</strong></p>
<p>构建号: ${BUILD_NUMBER}</p>
<p>状态: <span style='color:red;'>FAILED</span></p>
<p>分支: ${env.BRANCH_NAME}</p>
<p>触发人: ${env.CHANGE_AUTHOR ?: 'Unknown'}</p>
<p>错误日志:</p>
<pre style='background:#f4f4f4; padding:10px; border:1px solid #ccc;'>
${currentBuild.rawBuild.getLog(100).join('\n')}
</pre>
<p>构建日志: <a href='${BUILD_URL}console'>点击查看完整日志</a></p>
<p>请尽快修复!</p>
""",
recipientProviders: [
[$class: 'DevelopersRecipientProvider'],
[$class: 'CulpritsRecipientProvider'] // 最近提交导致失败的人
],
to: 'xu.chunjie@foxmail.com',
mimeType: 'text/html'
)
}
always {
echo "构建状态: ${currentBuild.result}"
// ✅ 使用 Allure 插件发布报告(核心)
allure([
includeProperties: false,
jdk: '',
properties: [],
reportBuildPolicy: 'ALWAYS', // 无论成功失败都生成报告
results: [[path: 'reports/results']] // 必须和 --alluredir 一致
])
}
}
五、企业邮箱通知
使用 Qy Wechat Notification
插件,在 Jenkins Pipeline 构建成功或失败时,自动发送消息到企业微信群。
1、安装Jenkins插件:WXWork Notification
2、获取企业微信机器人 Webhook URL
- 在企业微信中打开一个群聊
- 点击右上角「…」→ 群机器人 → 添加机器人 → 选择「自定义机器人」
- 保存生成的 Webhook URL,格式如下:
https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
3、添加一个新的机器人配置
这个 ID 名称将在 Jenkinsfile 中使用。
4、优化Jenkinsfile脚本
在always中添加markdown语法通知。
always {
script {
// 定义消息标题和状态图标
def statusTitle = "构建结果: ${currentBuild.currentResult}"
def statusEmoji = currentBuild.currentResult == 'SUCCESS' ? '✅' : '❌'
// 构建 Markdown 消息内容数组
def markdownText = [
"# ${statusTitle} ${statusEmoji}", // 一级标题,包含结果和图标
"> ## 项目信息", // 二级标题
"- **项目名称**: `${env.JOB_NAME}`", // 项目全名
"- **构建编号**: `${currentBuild.displayName}`", // 构建号,如 #15
"- **触发原因**: ${currentBuild.getBuildCauses().collect{it.shortDescription}.join(', ')}", // 触发原因
"> ", // 空行
"## 构建详情", // 二级标题
"- **开始时间**: ${new Date(currentBuild.startTimeInMillis).format('yyyy-MM-dd HH:mm:ss')}", // 格式化时间
"- **持续时间**: ${currentBuild.durationString}", // 如 "1 min 20 sec"
"- **构建日志**: [点击查看完整日志](${currentBuild.absoluteUrl}console)", // 超链接到日志
"> ", // 空行
"**请及时关注构建状态!**" // 强调文字
]
// 发送 Markdown 消息
wxwork(
robot: 'Jenkins-CI-Bot', // 替换成你在Jenkins系统配置中的机器人ID
type: 'markdown', // 指定消息类型为 markdown
text: markdownText // 传入构建好的Markdown内容数组
)
}
}
六、最终完整的Jenkinsfile
再补充 --junitxml=report.xml
+ junit 'report.xml'
,可以在Jenkin上展示出趋势图。
pipeline {
agent any
environment {
VENV = 'venv'
REPORT_DIR = 'reports'
RESULTS_DIR = 'reports/results'
HTML_DIR = 'reports/html'
LOG_DIR = 'logs'
CONFIG_DIR = 'config'
ALLURE_REPORT_NAME = '我的自动化测试报告'
REPORTFILE = 'report.xml'
}
stages {
stage('初始化') {
steps {
script {
// 在 pipeline 中定义变量,支持 Jenkins 内置变量
ALLURE_HISTORY_PATH = "${JENKINS_HOME}/userContent/allure-history/${JOB_NAME}"
echo "Allure 历史数据路径: ${ALLURE_HISTORY_PATH}"
}
}
}
stage('创建虚拟环境') {
steps {
sh '''
echo "创建 Python 虚拟环境..."
python3 -m venv ${VENV}
python3 --version
echo "虚拟环境创建成功: ${VENV}"
'''
}
}
stage('安装依赖') {
steps {
sh '''
echo "激活虚拟环境并升级 pip..."
source ${VENV}/bin/activate
pip install --upgrade pip -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn
pip cache purge
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn
echo "依赖安装完成"
'''
}
}
stage('生成环境变量文件') {
steps {
sh '''
source ${VENV}/bin/activate
python3 ${CONFIG_DIR}/generate_env.py
echo "环境变量文件生成完成"
'''
}
}
stage('准备报告目录') {
steps {
sh '''
mkdir -p ${RESULTS_DIR} ${HTML_DIR}
echo "报告目录创建完成"
'''
}
}
stage('恢复 Allure 历史数据') {
steps {
script {
def historyDir = "${ALLURE_HISTORY_PATH}/history"
if (sh(script: "test -d '${historyDir}'", returnStatus: true) == 0) {
sh """
echo "恢复历史数据..."
cp -r '${historyDir}' '${RESULTS_DIR}/'
"""
} else {
echo "未发现历史数据,本次为首次运行"
}
}
}
}
stage('运行 Pytest 测试') {
steps {
script {
// 直接在这里执行 pytest 并处理退出码
def exitCode = sh(
script: """
source venv/bin/activate
pytest testcases/test_sdk_api.py --alluredir=reports/results -v --junitxml=${REPORTFILE}
""",
returnStatus: true // 关键:返回状态码,而不是抛异常
)
// 只有在 report.xml 存在时才收集并解析 JUnit 报告
if (fileExists(REPORTFILE)) {
echo "发现测试报告文件: ${REPORTFILE},正在收集..."
junit REPORTFILE
} else {
echo "未生成测试报告文件: ${REPORTFILE}"
}
if (exitCode == 0) {
echo "测试全部通过"
// 构建继续
} else if (exitCode == 1) {
echo "有测试用例失败"
currentBuild.result = 'UNSTABLE'
// 构建继续,标记为黄色
} else {
echo "pytest 执行异常(exit code: ${exitCode})"
error("Pytest 执行失败,可能是命令不存在或环境错误,终止构建")
}
}
}
}
stage('持久化 Allure 历史数据') {
steps {
sh """
echo "持久化历史数据..."
mkdir -p '${ALLURE_HISTORY_PATH}'
rm -rf '${ALLURE_HISTORY_PATH}/history'
if [ -d '${HTML_DIR}/history' ]; then
cp -r '${HTML_DIR}/history' '${ALLURE_HISTORY_PATH}/'
echo "历史数据已保存至: ${ALLURE_HISTORY_PATH}"
else
echo "未生成 history 目录"
fi
"""
}
}
stage('归档报告与结果') {
steps {
archiveArtifacts(
artifacts: "${LOG_DIR}/*,report.xml",
fingerprint: true,
allowEmptyArchive: true
)
}
}
}
post {
success {
echo '✅ 构建成功,测试全部通过!'
emailext(
subject: "✅ 构建成功: ${JOB_NAME} #${BUILD_NUMBER}",
body: """
<p>项目: <strong>${JOB_NAME}</strong></p>
<p>构建号: ${BUILD_NUMBER}</p>
<p>状态: <span style='color:green;'>SUCCESS</span></p>
<p>构建耗时: ${currentBuild.durationString}</p>
<p>触发人: ${env.CHANGE_AUTHOR ?: 'System'}</p>
<p>构建日志: <a href='${BUILD_URL}'>点击查看</a></p>
""",
recipientProviders: [
[$class: 'DevelopersRecipientProvider'], // 提交代码的人
[$class: 'RequesterRecipientProvider'] // 触发构建的人
],
to: 'xu.chunjie@foxmail.com',
mimeType: 'text/html'
)
}
unstable {
echo '⚠️ 构建完成,但有测试失败'
echo "📊 查看报告: ${BUILD_URL}allure"
emailext(
subject: "⚠️构建不稳定: ${JOB_NAME} #${BUILD_NUMBER}",
body: """
<p>项目: ${JOB_NAME}</p>
<p>状态: UNSTABLE(测试失败)</p>
<p>触发人: ${env.CHANGE_AUTHOR ?: 'Unknown'}</p>
<p>请检查测试报告。</p>
<p><a href='${BUILD_URL}'>构建地址</a></p>
""",
to: 'xu.chunjie@foxmail.com',
mimeType: 'text/html'
)
}
failure {
echo '❌ 构建失败!可能是脚本错误、依赖问题或 pytest 执行异常'
echo "📎 检查日志: ${BUILD_URL}console"
emailext(
subject: "❌ 构建失败: ${JOB_NAME} #${BUILD_NUMBER}",
body: """
<p>项目: <strong>${JOB_NAME}</strong></p>
<p>构建号: ${BUILD_NUMBER}</p>
<p>状态: <span style='color:red;'>FAILED</span></p>
<p>触发人: ${env.CHANGE_AUTHOR ?: 'Unknown'}</p>
<p>错误日志:</p>
<pre style='background:#f4f4f4; padding:10px; border:1px solid #ccc;'>
${currentBuild.rawBuild.getLog(100).join('\n')}
</pre>
<p>构建日志: <a href='${BUILD_URL}console'>点击查看完整日志</a></p>
<p>请尽快修复!</p>
""",
recipientProviders: [
[$class: 'DevelopersRecipientProvider'],
[$class: 'CulpritsRecipientProvider'] // 最近提交导致失败的人
],
to: 'xu.chunjie@foxmail.com',
mimeType: 'text/html'
)
}
always {
echo "构建状态: ${currentBuild.result}"
// ✅ 使用 Allure 插件发布报告(核心)
allure([
includeProperties: false,
jdk: '',
properties: [],
reportBuildPolicy: 'ALWAYS', // 无论成功失败都生成报告
results: [[path: 'reports/results']] // 必须和 --alluredir 一致
])
script {
// 定义消息标题和状态图标
def statusTitle = "构建结果: ${currentBuild.currentResult}"
def statusEmoji = currentBuild.currentResult == 'SUCCESS' ? '✅' : '❌'
// 构建 Markdown 消息内容数组
def markdownText = [
"# ${statusTitle} ${statusEmoji}", // 一级标题,包含结果和图标
"> ## 项目信息", // 二级标题
"- **项目名称**: `${env.JOB_NAME}`", // 项目全名
"- **构建编号**: `${currentBuild.displayName}`", // 构建号,如 #15
"- **触发原因**: ${currentBuild.getBuildCauses().collect{it.shortDescription}.join(', ')}", // 触发原因
"> ", // 空行
"## 构建详情", // 二级标题
"- **开始时间**: ${new Date(currentBuild.startTimeInMillis).format('yyyy-MM-dd HH:mm:ss')}", // 格式化时间
"- **持续时间**: ${currentBuild.durationString}", // 如 "1 min 20 sec"
"- **构建日志**: [点击查看完整日志](${currentBuild.absoluteUrl}console)", // 超链接到日志
"> ", // 空行
"**请及时关注构建状态!**" // 强调文字
]
// 发送 Markdown 消息
wxwork(
robot: 'Jenkins-CI-Bot', // 替换成你在Jenkins系统配置中的机器人ID
type: 'markdown', // 指定消息类型为 markdown
text: markdownText // 传入构建好的Markdown内容数组
)
}
}
}
}
评论区