init commit:API自动化测试
0 parents
Showing
17 changed files
with
2913 additions
and
0 deletions
.dockerignore
0 → 100644
| 1 | .git | ||
| 2 | .gitignore | ||
| 3 | __pycache__ | ||
| 4 | *.py[cod] | ||
| 5 | *.so | ||
| 6 | build/ | ||
| 7 | dist/ | ||
| 8 | eggs/ | ||
| 9 | .eggs/ | ||
| 10 | lib/ | ||
| 11 | .venv | ||
| 12 | venv/ | ||
| 13 | ENV/ | ||
| 14 | env/ | ||
| 15 | .vscode | ||
| 16 | .idea | ||
| 17 | *.swp | ||
| 18 | *.swo | ||
| 19 | *~ | ||
| 20 | .DS_Store | ||
| 21 | .pytest_cache/ | ||
| 22 | .coverage | ||
| 23 | *.md | ||
| 24 | api_test_report_*.html | ||
| 25 | api_test_report_*.xlsx | ||
| 26 | .env | ||
| 27 | *.log | ||
| 28 | *.tmp |
.gitignore
0 → 100644
| 1 | # Python | ||
| 2 | __pycache__/ | ||
| 3 | *.py[cod] | ||
| 4 | *$py.class | ||
| 5 | .venv/ | ||
| 6 | venv/ | ||
| 7 | ENV/ | ||
| 8 | env/ | ||
| 9 | *.egg-info/ | ||
| 10 | dist/ | ||
| 11 | build/ | ||
| 12 | |||
| 13 | # IDE | ||
| 14 | .vscode/ | ||
| 15 | .idea/ | ||
| 16 | *.swp | ||
| 17 | *.swo | ||
| 18 | *.iml | ||
| 19 | |||
| 20 | # Reports | ||
| 21 | api_test_report_*.html | ||
| 22 | api_test_report_*.xlsx | ||
| 23 | reports/ | ||
| 24 | *.log | ||
| 25 | |||
| 26 | # OS | ||
| 27 | .DS_Store | ||
| 28 | Thumbs.db | ||
| 29 | .directory | ||
| 30 | |||
| 31 | # Excel | ||
| 32 | ~$* | ||
| 33 | .~* | ||
| 34 | |||
| 35 | # Misc | ||
| 36 | .env | ||
| 37 | .env.local | ||
| 38 | nul |
DEPLOYMENT_GUIDE.md
0 → 100644
| 1 | # 部署指南 | ||
| 2 | |||
| 3 | 本文档涵盖Linux服务器部署、Jenkins配置和多环境设置。 | ||
| 4 | |||
| 5 | --- | ||
| 6 | |||
| 7 | ## 📝 多环境支持 | ||
| 8 | |||
| 9 | 该框架支持在**三个不同的环境**中运行: | ||
| 10 | |||
| 11 | | 环境 | DEPLOY_ENV | IP_HOST | 用途 | | ||
| 12 | |------|-----------|---------|------| | ||
| 13 | | 测试 | test | ai-test.hikoon.com | 开发测试 | | ||
| 14 | | UAT | uat | ai-uat.hikoon.com | 用户验收 | | ||
| 15 | | 生产 | prod | api.hikoon.com | 生产验证 | | ||
| 16 | |||
| 17 | ### 环境选择方法 | ||
| 18 | |||
| 19 | #### 本地运行 | ||
| 20 | |||
| 21 | ```bash | ||
| 22 | # 测试环境(默认) | ||
| 23 | export DEPLOY_ENV=test | ||
| 24 | python3 api_test.py | ||
| 25 | |||
| 26 | # UAT环境 | ||
| 27 | export DEPLOY_ENV=uat | ||
| 28 | python3 api_test.py | ||
| 29 | |||
| 30 | # 生产环境 | ||
| 31 | export DEPLOY_ENV=prod | ||
| 32 | python3 api_test.py | ||
| 33 | ``` | ||
| 34 | |||
| 35 | #### Jenkins中配置 | ||
| 36 | |||
| 37 | 已在Jenkinsfile中配置参数化支持,使用**Build with Parameters**选择环境。 | ||
| 38 | |||
| 39 | --- | ||
| 40 | |||
| 41 | ## 🐳 使用 Docker(推荐) | ||
| 42 | |||
| 43 | ### 快速开始 | ||
| 44 | |||
| 45 | **构建镜像**: | ||
| 46 | ```bash | ||
| 47 | docker-compose build | ||
| 48 | ``` | ||
| 49 | |||
| 50 | **运行测试**: | ||
| 51 | ```bash | ||
| 52 | # 测试环境 | ||
| 53 | docker-compose run --rm -e DEPLOY_ENV=test api-test-runner | ||
| 54 | |||
| 55 | # UAT环境 | ||
| 56 | docker-compose run --rm -e DEPLOY_ENV=uat api-test-runner | ||
| 57 | |||
| 58 | # 生产环境 | ||
| 59 | docker-compose run --rm -e DEPLOY_ENV=prod api-test-runner | ||
| 60 | ``` | ||
| 61 | |||
| 62 | 详见 [DOCKER_GUIDE.md](./DOCKER_GUIDE.md) | ||
| 63 | |||
| 64 | --- | ||
| 65 | |||
| 66 | ## 🖥️ Linux 服务器部署(非Docker) | ||
| 67 | |||
| 68 | ### 前提条件 | ||
| 69 | |||
| 70 | - Python 3.8+ | ||
| 71 | - pip 和 venv | ||
| 72 | - Git | ||
| 73 | |||
| 74 | ### 部署步骤 | ||
| 75 | |||
| 76 | #### 1️⃣ 克隆项目 | ||
| 77 | |||
| 78 | ```bash | ||
| 79 | git clone <repo-url> /opt/api_test | ||
| 80 | cd /opt/api_test | ||
| 81 | ``` | ||
| 82 | |||
| 83 | #### 2️⃣ 创建虚拟环境 | ||
| 84 | |||
| 85 | ```bash | ||
| 86 | python3 -m venv venv | ||
| 87 | source venv/bin/activate | ||
| 88 | pip install --upgrade pip | ||
| 89 | pip install -r requirements.txt | ||
| 90 | ``` | ||
| 91 | |||
| 92 | #### 3️⃣ 测试运行 | ||
| 93 | |||
| 94 | ```bash | ||
| 95 | export DEPLOY_ENV=test | ||
| 96 | python3 api_test.py | ||
| 97 | ``` | ||
| 98 | |||
| 99 | #### 4️⃣ 生成报告 | ||
| 100 | |||
| 101 | 报告文件位于项目目录:`api_test_report_*.html` | ||
| 102 | |||
| 103 | --- | ||
| 104 | |||
| 105 | ## 🔗 Jenkins 配置 | ||
| 106 | |||
| 107 | ### 前提条件 | ||
| 108 | |||
| 109 | - Jenkins 已安装 | ||
| 110 | - Git 插件已启用 | ||
| 111 | - Python 环境已配置 | ||
| 112 | |||
| 113 | ### Jenkins 任务配置 | ||
| 114 | |||
| 115 | #### 1️⃣ 创建新 Pipeline 任务 | ||
| 116 | |||
| 117 | 1. Jenkins → New Item | ||
| 118 | 2. 输入任务名称 | ||
| 119 | 3. 选择 Pipeline | ||
| 120 | 4. 点击 OK | ||
| 121 | |||
| 122 | #### 2️⃣ 配置 Pipeline | ||
| 123 | |||
| 124 | 1. **Build Triggers** → 选择 Poll SCM → `0 10 * * *`(每天10点) | ||
| 125 | 2. **Pipeline** → 选择 Pipeline script from SCM | ||
| 126 | 3. **SCM** → Git | ||
| 127 | 4. **Repository URL** → `<your-repo-url>` | ||
| 128 | 5. **Branch** → `*/main` | ||
| 129 | 6. **Script Path** → `Jenkinsfile` | ||
| 130 | 7. **Save** | ||
| 131 | |||
| 132 | ### 3️⃣ 参数化执行 | ||
| 133 | |||
| 134 | 已在 Jenkinsfile 中配置参数化支持: | ||
| 135 | |||
| 136 | ```groovy | ||
| 137 | parameters { | ||
| 138 | choice( | ||
| 139 | name: 'DEPLOY_ENV', | ||
| 140 | choices: ['test', 'uat', 'prod'], | ||
| 141 | description: '选择部署环境' | ||
| 142 | ) | ||
| 143 | } | ||
| 144 | ``` | ||
| 145 | |||
| 146 | 点击 **Build with Parameters** 选择环境执行 | ||
| 147 | |||
| 148 | --- | ||
| 149 | |||
| 150 | ## 🔧 环境变量配置 | ||
| 151 | |||
| 152 | ### env_config.py | ||
| 153 | |||
| 154 | 定义多个环境的IP配置: | ||
| 155 | |||
| 156 | ```python | ||
| 157 | IP_HOST_MAP = { | ||
| 158 | 'test': 'ai-test.hikoon.com', | ||
| 159 | 'uat': 'ai-uat.hikoon.com', | ||
| 160 | 'prod': 'api.hikoon.com' | ||
| 161 | } | ||
| 162 | ``` | ||
| 163 | |||
| 164 | ### 添加新环境 | ||
| 165 | |||
| 166 | **1. 编辑 env_config.py**: | ||
| 167 | ```python | ||
| 168 | IP_HOST_MAP = { | ||
| 169 | 'test': '...', | ||
| 170 | 'staging': 'ai-staging.hikoon.com', # 新增 | ||
| 171 | 'uat': '...', | ||
| 172 | 'prod': '...' | ||
| 173 | } | ||
| 174 | ``` | ||
| 175 | |||
| 176 | **2. 编辑 Jenkinsfile**: | ||
| 177 | ```groovy | ||
| 178 | parameters { | ||
| 179 | choice( | ||
| 180 | name: 'DEPLOY_ENV', | ||
| 181 | choices: ['test', 'staging', 'uat', 'prod'], # 新增 | ||
| 182 | description: '选择部署环境' | ||
| 183 | ) | ||
| 184 | } | ||
| 185 | ``` | ||
| 186 | |||
| 187 | --- | ||
| 188 | |||
| 189 | ## 📊 查看测试报告 | ||
| 190 | |||
| 191 | ### 报告位置 | ||
| 192 | |||
| 193 | - HTML报告:`api_test_report_api_cases_[env]_*.html` | ||
| 194 | - Excel报告:`api_test_report_api_cases_[env]_*.xlsx` | ||
| 195 | |||
| 196 | ### 报告内容 | ||
| 197 | |||
| 198 | - 📈 测试统计:总数、成功、失败、耗时 | ||
| 199 | - 🔍 筛选功能:按失败、响应时间筛选 | ||
| 200 | - 📋 详细结果:请求参数、响应数据、错误信息 | ||
| 201 | |||
| 202 | --- | ||
| 203 | |||
| 204 | ## 🆘 常见问题 | ||
| 205 | |||
| 206 | ### 依赖安装失败 | ||
| 207 | |||
| 208 | ```bash | ||
| 209 | # 使用国内镜像 | ||
| 210 | pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/ | ||
| 211 | ``` | ||
| 212 | |||
| 213 | ### Python 版本不对 | ||
| 214 | |||
| 215 | ```bash | ||
| 216 | # 检查版本 | ||
| 217 | python3 --version | ||
| 218 | |||
| 219 | # 如需指定版本 | ||
| 220 | python3.8 -m venv venv | ||
| 221 | ``` | ||
| 222 | |||
| 223 | ### 权限不足 | ||
| 224 | |||
| 225 | ```bash | ||
| 226 | # 添加执行权限 | ||
| 227 | chmod +x api_test.py | ||
| 228 | |||
| 229 | # 修改目录权限 | ||
| 230 | sudo chown -R $USER:$USER /opt/api_test | ||
| 231 | ``` | ||
| 232 | |||
| 233 | ### 报告未生成 | ||
| 234 | |||
| 235 | ```bash | ||
| 236 | # 检查 api_cases.xlsx 是否存在 | ||
| 237 | ls -la api_cases.xlsx | ||
| 238 | |||
| 239 | # 运行测试查看错误 | ||
| 240 | python3 api_test.py | ||
| 241 | ``` | ||
| 242 | |||
| 243 | --- | ||
| 244 | |||
| 245 | ## 📞 获取帮助 | ||
| 246 | |||
| 247 | - 详细步骤 → [README.md](./README.md) | ||
| 248 | - 使用问题 → [USER_GUIDE.md](./USER_GUIDE.md) | ||
| 249 | - Docker部署 → [DOCKER_GUIDE.md](./DOCKER_GUIDE.md) | ||
| 250 | - 验证方式 → [OPERATOR_GUIDE.md](./OPERATOR_GUIDE.md) | ||
| 251 | |||
| 252 | --- | ||
| 253 | |||
| 254 | **🎉 部署完成!** |
DOCKER_GUIDE.md
0 → 100644
| 1 | # Docker 配置指南 | ||
| 2 | |||
| 3 | 本文档说明如何使用Docker运行API自动化测试框架。 | ||
| 4 | |||
| 5 | --- | ||
| 6 | |||
| 7 | ## 📋 快速开始 | ||
| 8 | |||
| 9 | ### 1️⃣ 构建镜像 | ||
| 10 | |||
| 11 | ```bash | ||
| 12 | # 使用docker-compose构建 | ||
| 13 | docker-compose build | ||
| 14 | |||
| 15 | # 或使用docker构建 | ||
| 16 | docker build -t api-test:latest . | ||
| 17 | ``` | ||
| 18 | |||
| 19 | ### 2️⃣ 运行容器 | ||
| 20 | |||
| 21 | **测试环境**: | ||
| 22 | ```bash | ||
| 23 | docker-compose run --rm -e DEPLOY_ENV=test api-test-runner | ||
| 24 | ``` | ||
| 25 | |||
| 26 | **UAT环境**: | ||
| 27 | ```bash | ||
| 28 | docker-compose run --rm -e DEPLOY_ENV=uat api-test-runner | ||
| 29 | ``` | ||
| 30 | |||
| 31 | **生产环境**: | ||
| 32 | ```bash | ||
| 33 | docker-compose run --rm -e DEPLOY_ENV=prod api-test-runner | ||
| 34 | ``` | ||
| 35 | |||
| 36 | ### 3️⃣ 查看报告 | ||
| 37 | |||
| 38 | 报告文件保存在 `./reports` 目录: | ||
| 39 | |||
| 40 | ```bash | ||
| 41 | ls -la reports/api_test_report_*.html | ||
| 42 | ``` | ||
| 43 | |||
| 44 | --- | ||
| 45 | |||
| 46 | ## 🐳 Docker命令 | ||
| 47 | |||
| 48 | ### 使用 docker-compose | ||
| 49 | |||
| 50 | ```bash | ||
| 51 | # 运行指定环境的测试 | ||
| 52 | DEPLOY_ENV=test docker-compose run --rm api-test-runner | ||
| 53 | |||
| 54 | # 后台运行 | ||
| 55 | docker-compose up -d | ||
| 56 | |||
| 57 | # 查看日志 | ||
| 58 | docker-compose logs -f api-test-runner | ||
| 59 | |||
| 60 | # 停止服务 | ||
| 61 | docker-compose down | ||
| 62 | ``` | ||
| 63 | |||
| 64 | ### 使用 docker 直接命令 | ||
| 65 | |||
| 66 | ```bash | ||
| 67 | # 构建镜像 | ||
| 68 | docker build -t api-test:latest . | ||
| 69 | |||
| 70 | # 运行容器 | ||
| 71 | docker run --rm -e DEPLOY_ENV=test api-test:latest | ||
| 72 | |||
| 73 | # 挂载卷 | ||
| 74 | docker run --rm \ | ||
| 75 | -e DEPLOY_ENV=test \ | ||
| 76 | -v $(pwd)/reports:/app \ | ||
| 77 | api-test:latest | ||
| 78 | |||
| 79 | # 交互式运行 | ||
| 80 | docker run -it --rm \ | ||
| 81 | -e DEPLOY_ENV=test \ | ||
| 82 | -v $(pwd)/reports:/app \ | ||
| 83 | api-test:latest /bin/bash | ||
| 84 | ``` | ||
| 85 | |||
| 86 | --- | ||
| 87 | |||
| 88 | ## 📂 文件说明 | ||
| 89 | |||
| 90 | ### Dockerfile | ||
| 91 | |||
| 92 | ```dockerfile | ||
| 93 | FROM python:3.8-slim # 基础镜像 | ||
| 94 | WORKDIR /app # 工作目录 | ||
| 95 | COPY . /app/ # 复制项目 | ||
| 96 | RUN pip install -r requirements.txt # 安装依赖 | ||
| 97 | ENV DEPLOY_ENV=test # 默认环境 | ||
| 98 | CMD ["python3", "api_test.py"] # 执行命令 | ||
| 99 | ``` | ||
| 100 | |||
| 101 | ### docker-compose.yml | ||
| 102 | |||
| 103 | 关键配置: | ||
| 104 | - 自动环境变量传入 | ||
| 105 | - 卷挂载:输入Excel、输出报告 | ||
| 106 | - 资源限制:1核CPU、512MB内存 | ||
| 107 | - 日志配置:json-file驱动 | ||
| 108 | |||
| 109 | ### .dockerignore | ||
| 110 | |||
| 111 | 排除不必要文件,减小镜像体积 | ||
| 112 | |||
| 113 | --- | ||
| 114 | |||
| 115 | ## 🔧 环境变量 | ||
| 116 | |||
| 117 | ### 支持的环境变量 | ||
| 118 | |||
| 119 | | 变量 | 说明 | 默认值 | | ||
| 120 | |------|------|--------| | ||
| 121 | | `DEPLOY_ENV` | 执行环境 | test | | ||
| 122 | | `IP_HOST` | 覆盖IP地址 | 不设置 | | ||
| 123 | | `PYTHONUNBUFFERED` | 禁用缓冲 | 1 | | ||
| 124 | |||
| 125 | ### 设置方式 | ||
| 126 | |||
| 127 | **命令行**: | ||
| 128 | ```bash | ||
| 129 | docker-compose run --rm -e DEPLOY_ENV=test api-test-runner | ||
| 130 | ``` | ||
| 131 | |||
| 132 | **.env 文件**: | ||
| 133 | ``` | ||
| 134 | DEPLOY_ENV=test | ||
| 135 | IP_HOST=ai-test.hikoon.com | ||
| 136 | ``` | ||
| 137 | |||
| 138 | --- | ||
| 139 | |||
| 140 | ## 🔗 与 Jenkins 集成 | ||
| 141 | |||
| 142 | ### Jenkinsfile 中的 Docker 配置 | ||
| 143 | |||
| 144 | ```groovy | ||
| 145 | stage('构建镜像') { | ||
| 146 | steps { | ||
| 147 | sh 'docker build -t api-test:${BUILD_NUMBER} .' | ||
| 148 | } | ||
| 149 | } | ||
| 150 | |||
| 151 | stage('执行测试') { | ||
| 152 | steps { | ||
| 153 | sh ''' | ||
| 154 | docker run --rm \ | ||
| 155 | -e DEPLOY_ENV=${DEPLOY_ENV} \ | ||
| 156 | -v ${WORKSPACE}/reports:/app \ | ||
| 157 | api-test:${BUILD_NUMBER} | ||
| 158 | ''' | ||
| 159 | } | ||
| 160 | } | ||
| 161 | |||
| 162 | post { | ||
| 163 | always { | ||
| 164 | sh 'docker rmi api-test:${BUILD_NUMBER}' | ||
| 165 | } | ||
| 166 | } | ||
| 167 | ``` | ||
| 168 | |||
| 169 | --- | ||
| 170 | |||
| 171 | ## 🐛 常见问题 | ||
| 172 | |||
| 173 | ### Q1: 容器报错找不到api_cases.xlsx | ||
| 174 | |||
| 175 | ```bash | ||
| 176 | docker run -v $(pwd)/api_cases.xlsx:/app/api_cases.xlsx:ro api-test:latest | ||
| 177 | ``` | ||
| 178 | |||
| 179 | ### Q2: 报告文件生成在容器内无法访问 | ||
| 180 | |||
| 181 | ```bash | ||
| 182 | docker run -v $(pwd)/reports:/app api-test:latest | ||
| 183 | ``` | ||
| 184 | |||
| 185 | ### Q3: 环境变量未传入 | ||
| 186 | |||
| 187 | ```bash | ||
| 188 | # 正确 | ||
| 189 | docker run -e DEPLOY_ENV=test api-test:latest | ||
| 190 | |||
| 191 | # 错误 | ||
| 192 | docker run api-test:latest -e DEPLOY_ENV=test | ||
| 193 | ``` | ||
| 194 | |||
| 195 | ### Q4: 镜像体积过大 | ||
| 196 | |||
| 197 | 使用 `.dockerignore` 排除不必要文件 | ||
| 198 | |||
| 199 | --- | ||
| 200 | |||
| 201 | ## 📞 获取帮助 | ||
| 202 | |||
| 203 | - Docker官方文档:https://docs.docker.com/ | ||
| 204 | - docker-compose:https://docs.docker.com/compose/ | ||
| 205 | - Jenkins Docker:https://www.jenkins.io/doc/book/installing/docker/ | ||
| 206 | |||
| 207 | --- | ||
| 208 | |||
| 209 | **🎉 Docker配置完成!** |
Dockerfile
0 → 100644
| 1 | FROM python:3.8-slim | ||
| 2 | WORKDIR /app | ||
| 3 | RUN apt-get update && apt-get install -y git curl && rm -rf /var/lib/apt/lists/* | ||
| 4 | COPY . /app/ | ||
| 5 | RUN pip install --no-cache-dir -r requirements.txt | ||
| 6 | ENV DEPLOY_ENV=test | ||
| 7 | ENV PYTHONUNBUFFERED=1 | ||
| 8 | RUN mkdir -p /app/reports | ||
| 9 | HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" || exit 1 | ||
| 10 | CMD ["python3", "api_test.py"] |
Jenkinsfile
0 → 100644
| 1 | pipeline { | ||
| 2 | agent any | ||
| 3 | |||
| 4 | parameters { | ||
| 5 | choice( | ||
| 6 | name: 'DEPLOY_ENV', | ||
| 7 | choices: ['test', 'uat', 'prod'], | ||
| 8 | description: '选择部署环境' | ||
| 9 | ) | ||
| 10 | } | ||
| 11 | |||
| 12 | triggers { | ||
| 13 | cron('0 10 * * *') | ||
| 14 | } | ||
| 15 | |||
| 16 | environment { | ||
| 17 | PYTHON_VERSION = '3.8' | ||
| 18 | PROJECT_NAME = 'api_test' | ||
| 19 | DEPLOY_ENV = "${params.DEPLOY_ENV ?: 'test'}" | ||
| 20 | IP_HOST_TEST = 'ai-test.hikoon.com' | ||
| 21 | IP_HOST_UAT = 'ai-uat.hikoon.com' | ||
| 22 | IP_HOST_PROD = 'api.hikoon.com' | ||
| 23 | } | ||
| 24 | |||
| 25 | stages { | ||
| 26 | stage('检出代码') { | ||
| 27 | steps { | ||
| 28 | echo "开始检出代码..." | ||
| 29 | checkout scm | ||
| 30 | } | ||
| 31 | } | ||
| 32 | |||
| 33 | stage('环境准备') { | ||
| 34 | steps { | ||
| 35 | echo "准备Python环境..." | ||
| 36 | sh ''' | ||
| 37 | python3 --version | ||
| 38 | if [ ! -d "venv" ]; then | ||
| 39 | python3 -m venv venv | ||
| 40 | fi | ||
| 41 | . venv/bin/activate | ||
| 42 | pip install --upgrade pip -q | ||
| 43 | pip install -r requirements.txt -q | ||
| 44 | echo "依赖安装完成" | ||
| 45 | ''' | ||
| 46 | } | ||
| 47 | } | ||
| 48 | |||
| 49 | stage('执行测试') { | ||
| 50 | steps { | ||
| 51 | echo "开始执行API自动化测试 (环境: ${DEPLOY_ENV})..." | ||
| 52 | script { | ||
| 53 | def ipHost = 'ai-test.hikoon.com' | ||
| 54 | if (env.DEPLOY_ENV == 'uat') { | ||
| 55 | ipHost = env.IP_HOST_UAT | ||
| 56 | } else if (env.DEPLOY_ENV == 'prod') { | ||
| 57 | ipHost = env.IP_HOST_PROD | ||
| 58 | } else { | ||
| 59 | ipHost = env.IP_HOST_TEST | ||
| 60 | } | ||
| 61 | env.IP_HOST = ipHost | ||
| 62 | } | ||
| 63 | sh ''' | ||
| 64 | . venv/bin/activate | ||
| 65 | export DEPLOY_ENV="${DEPLOY_ENV}" | ||
| 66 | export IP_HOST="${IP_HOST}" | ||
| 67 | echo "当前执行环境: ${DEPLOY_ENV}" | ||
| 68 | echo "IP_HOST: ${IP_HOST}" | ||
| 69 | python3 api_test.py | ||
| 70 | ''' | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | stage('生成报告') { | ||
| 75 | steps { | ||
| 76 | echo "正在生成测试报告..." | ||
| 77 | sh ''' | ||
| 78 | ls -la api_test_report_* 2>/dev/null || echo "未找到报告文件" | ||
| 79 | ''' | ||
| 80 | } | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | post { | ||
| 85 | always { | ||
| 86 | echo "清理工作目录..." | ||
| 87 | sh 'echo "测试执行完成"' | ||
| 88 | } | ||
| 89 | |||
| 90 | success { | ||
| 91 | echo "✅ [${DEPLOY_ENV}环境] 所有测试用例运行成功!" | ||
| 92 | } | ||
| 93 | |||
| 94 | failure { | ||
| 95 | echo "❌ [${DEPLOY_ENV}环境] 有测试用例执行失败!" | ||
| 96 | } | ||
| 97 | } | ||
| 98 | } |
Jenkinsfile.docker
0 → 100644
| 1 | // Docker 环境下的 Jenkins Pipeline 示例 | ||
| 2 | |||
| 3 | pipeline { | ||
| 4 | agent any | ||
| 5 | |||
| 6 | parameters { | ||
| 7 | choice( | ||
| 8 | name: 'DEPLOY_ENV', | ||
| 9 | choices: ['test', 'uat', 'prod'], | ||
| 10 | description: '选择部署环境' | ||
| 11 | ) | ||
| 12 | } | ||
| 13 | |||
| 14 | environment { | ||
| 15 | DEPLOY_ENV = "${params.DEPLOY_ENV ?: 'test'}" | ||
| 16 | IMAGE_NAME = "api-test" | ||
| 17 | IMAGE_TAG = "${BUILD_NUMBER}" | ||
| 18 | WORKSPACE_REPORTS = "${WORKSPACE}/reports" | ||
| 19 | } | ||
| 20 | |||
| 21 | stages { | ||
| 22 | stage('检出代码') { | ||
| 23 | steps { | ||
| 24 | echo "检出代码..." | ||
| 25 | checkout scm | ||
| 26 | } | ||
| 27 | } | ||
| 28 | |||
| 29 | stage('构建镜像') { | ||
| 30 | steps { | ||
| 31 | echo "构建Docker镜像..." | ||
| 32 | script { | ||
| 33 | sh ''' | ||
| 34 | docker build -t ${IMAGE_NAME}:${IMAGE_TAG} . | ||
| 35 | docker tag ${IMAGE_NAME}:${IMAGE_TAG} ${IMAGE_NAME}:latest | ||
| 36 | ''' | ||
| 37 | } | ||
| 38 | } | ||
| 39 | } | ||
| 40 | |||
| 41 | stage('执行测试') { | ||
| 42 | steps { | ||
| 43 | echo "执行API自动化测试 (环境: ${DEPLOY_ENV})..." | ||
| 44 | script { | ||
| 45 | sh ''' | ||
| 46 | mkdir -p ${WORKSPACE_REPORTS} | ||
| 47 | |||
| 48 | docker run --rm \ | ||
| 49 | -e DEPLOY_ENV=${DEPLOY_ENV} \ | ||
| 50 | -v ${WORKSPACE_REPORTS}:/app \ | ||
| 51 | ${IMAGE_NAME}:${IMAGE_TAG} | ||
| 52 | ''' | ||
| 53 | } | ||
| 54 | } | ||
| 55 | } | ||
| 56 | |||
| 57 | stage('生成报告') { | ||
| 58 | steps { | ||
| 59 | echo "正在生成测试报告..." | ||
| 60 | script { | ||
| 61 | sh ''' | ||
| 62 | ls -la ${WORKSPACE_REPORTS}/api_test_report_* 2>/dev/null || echo "未找到报告文件" | ||
| 63 | ''' | ||
| 64 | } | ||
| 65 | } | ||
| 66 | } | ||
| 67 | |||
| 68 | stage('发布HTML报告') { | ||
| 69 | when { | ||
| 70 | expression { | ||
| 71 | return fileExists("${WORKSPACE_REPORTS}/api_test_report_api_cases_${DEPLOY_ENV}*.html") | ||
| 72 | } | ||
| 73 | } | ||
| 74 | steps { | ||
| 75 | echo "发布HTML测试报告..." | ||
| 76 | publishHTML([ | ||
| 77 | reportDir: 'reports', | ||
| 78 | reportFiles: "api_test_report_api_cases_${DEPLOY_ENV}*.html", | ||
| 79 | reportName: "API测试报告-${DEPLOY_ENV}环境", | ||
| 80 | keepAll: true, | ||
| 81 | alwaysLinkToLastBuild: true | ||
| 82 | ]) | ||
| 83 | } | ||
| 84 | } | ||
| 85 | } | ||
| 86 | |||
| 87 | post { | ||
| 88 | always { | ||
| 89 | echo "清理工作环境..." | ||
| 90 | script { | ||
| 91 | sh ''' | ||
| 92 | docker container prune -f | ||
| 93 | echo "========== 镜像列表 ==========" | ||
| 94 | docker images | grep ${IMAGE_NAME} | ||
| 95 | ''' | ||
| 96 | } | ||
| 97 | } | ||
| 98 | |||
| 99 | success { | ||
| 100 | echo "✅ [${DEPLOY_ENV}环境] 所有测试用例运行成功!" | ||
| 101 | } | ||
| 102 | |||
| 103 | failure { | ||
| 104 | echo "❌ [${DEPLOY_ENV}环境] 有测试用例执行失败!" | ||
| 105 | } | ||
| 106 | } | ||
| 107 | } |
OPERATOR_GUIDE.md
0 → 100644
| 1 | # 关系运算符使用指南 | ||
| 2 | |||
| 3 | ## 概述 | ||
| 4 | |||
| 5 | `expected_response` 字段支持多种关系运算符,可以进行更灵活的响应验证。 | ||
| 6 | |||
| 7 | --- | ||
| 8 | |||
| 9 | ## 支持的运算符 | ||
| 10 | |||
| 11 | ### 1. 比较运算符 | ||
| 12 | |||
| 13 | #### `$ne` - 不等于 | ||
| 14 | ```json | ||
| 15 | {"code": {"$ne": 500}} | ||
| 16 | ``` | ||
| 17 | 检查 `code` 字段不等于 500。 | ||
| 18 | |||
| 19 | #### `$eq` - 等于 | ||
| 20 | ```json | ||
| 21 | {"count": {"$eq": 10}} | ||
| 22 | ``` | ||
| 23 | 检查 `count` 字段等于 10。 | ||
| 24 | |||
| 25 | #### `$gt` - 大于 | ||
| 26 | ```json | ||
| 27 | {"count": {"$gt": 0}} | ||
| 28 | ``` | ||
| 29 | 检查 `count` 字段大于 0。 | ||
| 30 | |||
| 31 | #### `$gte` - 大于等于 | ||
| 32 | ```json | ||
| 33 | {"score": {"$gte": 60}} | ||
| 34 | ``` | ||
| 35 | |||
| 36 | #### `$lt` - 小于 | ||
| 37 | ```json | ||
| 38 | {"timeout": {"$lt": 1000}} | ||
| 39 | ``` | ||
| 40 | |||
| 41 | #### `$lte` - 小于等于 | ||
| 42 | ```json | ||
| 43 | {"age": {"$lte": 18}} | ||
| 44 | ``` | ||
| 45 | |||
| 46 | ### 2. 包含运算符 | ||
| 47 | |||
| 48 | #### `$in` - 值在列表中 | ||
| 49 | ```json | ||
| 50 | {"status": {"$in": ["pending", "done", "processing"]}} | ||
| 51 | ``` | ||
| 52 | |||
| 53 | #### `$nin` - 值不在列表中 | ||
| 54 | ```json | ||
| 55 | {"type": {"$nin": ["admin", "root", "system"]}} | ||
| 56 | ``` | ||
| 57 | |||
| 58 | ### 3. 字符串运算符 | ||
| 59 | |||
| 60 | #### `$contains` - 字符串包含 | ||
| 61 | ```json | ||
| 62 | {"msg": {"$contains": "success"}} | ||
| 63 | ``` | ||
| 64 | |||
| 65 | #### `$not_contains` - 字符串不包含 | ||
| 66 | ```json | ||
| 67 | {"error": {"$not_contains": "timeout"}} | ||
| 68 | ``` | ||
| 69 | |||
| 70 | #### `$regex` - 正则表达式匹配 | ||
| 71 | ```json | ||
| 72 | {"email": {"$regex": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"}} | ||
| 73 | ``` | ||
| 74 | |||
| 75 | ### 4. 范围运算符 | ||
| 76 | |||
| 77 | #### `$range` - 范围判断 [min, max] | ||
| 78 | ```json | ||
| 79 | {"value": {"$range": [0, 100]}} | ||
| 80 | ``` | ||
| 81 | 检查 `value` 在 0 到 100 之间(包含边界)。 | ||
| 82 | |||
| 83 | --- | ||
| 84 | |||
| 85 | ## 使用示例 | ||
| 86 | |||
| 87 | ### 示例1:验证状态码 | ||
| 88 | ```json | ||
| 89 | { | ||
| 90 | "code": {"$ne": 500}, | ||
| 91 | "msg": {"$contains": "成功"} | ||
| 92 | } | ||
| 93 | ``` | ||
| 94 | |||
| 95 | ### 示例2:验证列表数据 | ||
| 96 | ```json | ||
| 97 | { | ||
| 98 | "code": 0, | ||
| 99 | "data": { | ||
| 100 | "count": {"$gt": 0}, | ||
| 101 | "items": ["__NOT_NULL__"], | ||
| 102 | "status": {"$in": ["active", "inactive"]} | ||
| 103 | } | ||
| 104 | } | ||
| 105 | ``` | ||
| 106 | |||
| 107 | ### 示例3:验证分页数据 | ||
| 108 | ```json | ||
| 109 | { | ||
| 110 | "code": 0, | ||
| 111 | "pageIndex": {"$gte": 1}, | ||
| 112 | "pageSize": {"$range": [1, 1000]}, | ||
| 113 | "total": {"$gte": 0} | ||
| 114 | } | ||
| 115 | ``` | ||
| 116 | |||
| 117 | ### 示例4:验证邮箱和年龄 | ||
| 118 | ```json | ||
| 119 | { | ||
| 120 | "code": 0, | ||
| 121 | "email": {"$regex": "^.*@.*$"}, | ||
| 122 | "age": {"$range": [18, 100]}, | ||
| 123 | "roles": {"$in": ["user", "admin"]}, | ||
| 124 | "status": {"$not_contains": "banned"} | ||
| 125 | } | ||
| 126 | ``` | ||
| 127 | |||
| 128 | --- | ||
| 129 | |||
| 130 | ## Excel中的使用 | ||
| 131 | |||
| 132 | 在 `expected_response` 列中填入 JSON 格式的期望响应: | ||
| 133 | |||
| 134 | ``` | ||
| 135 | case_id | api_name | expected_response | ||
| 136 | 1 | 测试接口 | {"code": 0, "msg": "success"} | ||
| 137 | 2 | 列表接口 | {"code": 0, "data": {"count": {"$gt": 0}}} | ||
| 138 | 3 | 错误处理 | {"code": {"$ne": 200}} | ||
| 139 | ``` | ||
| 140 | |||
| 141 | --- | ||
| 142 | |||
| 143 | ## 特殊值 | ||
| 144 | |||
| 145 | - `"__NOT_NULL__"` - 检查字段非空 | ||
| 146 | - `["__NOT_NULL__"]` - 检查数组非空 | ||
| 147 | |||
| 148 | --- | ||
| 149 | |||
| 150 | ## 注意事项 | ||
| 151 | |||
| 152 | 1. **类型匹配**:比较运算符(`$gt`, `$lt` 等)要求数据类型兼容 | ||
| 153 | 2. **正则表达式**:使用 `$regex` 时,值会被转换为字符串后进行匹配 | ||
| 154 | 3. **列表范围**:`$range` 只支持两元素列表 `[min, max]`,两个值都是包含的 | ||
| 155 | 4. **错误处理**:运算符验证失败会记录在测试报告的 `error` 字段中 | ||
| 156 | |||
| 157 | --- | ||
| 158 | |||
| 159 | **更多示例请查看 USER_GUIDE.md** |
OPTIMIZATION_REQUIREMENTS.md
0 → 100644
| 1 | # 优化需求与增强建议 | ||
| 2 | |||
| 3 | ## 📋 概述 | ||
| 4 | |||
| 5 | 本文档列出了项目的优化建议和潜在增强功能,供未来版本参考。 | ||
| 6 | |||
| 7 | --- | ||
| 8 | |||
| 9 | ## 🚀 性能优化 | ||
| 10 | |||
| 11 | ### 1. 并发执行用例 | ||
| 12 | |||
| 13 | **当前**:单线程串行执行用例 | ||
| 14 | |||
| 15 | **优化方案**: | ||
| 16 | ```python | ||
| 17 | from concurrent.futures import ThreadPoolExecutor | ||
| 18 | |||
| 19 | # 使用线程池并发执行 | ||
| 20 | with ThreadPoolExecutor(max_workers=5) as executor: | ||
| 21 | futures = [executor.submit(test_case, case) for case in cases] | ||
| 22 | results = [f.result() for f in futures] | ||
| 23 | ``` | ||
| 24 | |||
| 25 | **效果**:可将执行时间缩短 50-70% | ||
| 26 | |||
| 27 | ### 2. 连接池复用 | ||
| 28 | |||
| 29 | **当前**:每个请求创建新连接 | ||
| 30 | |||
| 31 | **优化方案**: | ||
| 32 | ```python | ||
| 33 | from requests.adapters import HTTPAdapter | ||
| 34 | from urllib3.util.retry import Retry | ||
| 35 | |||
| 36 | session = requests.Session() | ||
| 37 | retry = Retry(connect=3, backoff_factor=0.5) | ||
| 38 | adapter = HTTPAdapter(max_retries=retry) | ||
| 39 | session.mount('http://', adapter) | ||
| 40 | session.mount('https://', adapter) | ||
| 41 | ``` | ||
| 42 | |||
| 43 | **效果**:减少网络开销,提升请求速度 | ||
| 44 | |||
| 45 | ### 3. Excel文件缓存 | ||
| 46 | |||
| 47 | **当前**:每次运行都重新加载Excel | ||
| 48 | |||
| 49 | **优化方案**: | ||
| 50 | - 使用内存缓存 | ||
| 51 | - 增量读取 | ||
| 52 | - 支持预编译 | ||
| 53 | |||
| 54 | --- | ||
| 55 | |||
| 56 | ## 💡 功能增强 | ||
| 57 | |||
| 58 | ### 1. 数据库支持 | ||
| 59 | |||
| 60 | **建议**:支持从数据库读取测试用例 | ||
| 61 | |||
| 62 | ```python | ||
| 63 | def read_cases_from_db(connection_string): | ||
| 64 | # 从MySQL、PostgreSQL等数据库读取 | ||
| 65 | pass | ||
| 66 | ``` | ||
| 67 | |||
| 68 | ### 2. 数据驱动测试(DDT) | ||
| 69 | |||
| 70 | **建议**:支持参数化测试 | ||
| 71 | |||
| 72 | ```python | ||
| 73 | # 使用 @ddt 装饰器 | ||
| 74 | @ddt | ||
| 75 | class TestAPI(unittest.TestCase): | ||
| 76 | @data([{"param": 1}, {"param": 2}]) | ||
| 77 | def test_api(self, case): | ||
| 78 | pass | ||
| 79 | ``` | ||
| 80 | |||
| 81 | ### 3. 测试报告增强 | ||
| 82 | |||
| 83 | **建议**: | ||
| 84 | - 图表展示(ECharts) | ||
| 85 | - 趋势分析 | ||
| 86 | - 性能对比 | ||
| 87 | - 邮件通知 | ||
| 88 | - 钉钉集成 | ||
| 89 | |||
| 90 | ### 4. 环境管理 | ||
| 91 | |||
| 92 | **建议**: | ||
| 93 | - 环境变量密钥管理 | ||
| 94 | - 敏感信息加密 | ||
| 95 | - 配置文件版本管理 | ||
| 96 | - 动态环境切换 | ||
| 97 | |||
| 98 | ### 5. 测试数据管理 | ||
| 99 | |||
| 100 | **建议**: | ||
| 101 | - 测试数据工厂 | ||
| 102 | - 数据清理机制 | ||
| 103 | - 数据备份恢复 | ||
| 104 | - 依赖数据预置 | ||
| 105 | |||
| 106 | --- | ||
| 107 | |||
| 108 | ## 🔒 安全增强 | ||
| 109 | |||
| 110 | ### 1. 敏感信息保护 | ||
| 111 | |||
| 112 | **建议**: | ||
| 113 | ```python | ||
| 114 | # 不存储明文密码 | ||
| 115 | # 使用环境变量或密钥管理系统 | ||
| 116 | import os | ||
| 117 | API_TOKEN = os.getenv('API_TOKEN') | ||
| 118 | |||
| 119 | # 打印前脱敏 | ||
| 120 | def mask_sensitive(data): | ||
| 121 | # 隐藏密钥、密码等 | ||
| 122 | pass | ||
| 123 | ``` | ||
| 124 | |||
| 125 | ### 2. 证书管理 | ||
| 126 | |||
| 127 | **建议**: | ||
| 128 | - SSL/TLS 证书管理 | ||
| 129 | - 证书过期预警 | ||
| 130 | - 自动续期支持 | ||
| 131 | |||
| 132 | --- | ||
| 133 | |||
| 134 | ## 📊 可观测性增强 | ||
| 135 | |||
| 136 | ### 1. 日志系统 | ||
| 137 | |||
| 138 | **建议**: | ||
| 139 | ```python | ||
| 140 | import logging | ||
| 141 | |||
| 142 | # 结构化日志 | ||
| 143 | logger = logging.getLogger(__name__) | ||
| 144 | logger.info("Test case executed", extra={ | ||
| 145 | "case_id": case_id, | ||
| 146 | "env": env, | ||
| 147 | "duration": duration | ||
| 148 | }) | ||
| 149 | ``` | ||
| 150 | |||
| 151 | ### 2. 链路追踪 | ||
| 152 | |||
| 153 | **建议**: | ||
| 154 | - 使用 OpenTelemetry | ||
| 155 | - 支持分布式追踪 | ||
| 156 | - 性能监控 | ||
| 157 | |||
| 158 | ### 3. 指标收集 | ||
| 159 | |||
| 160 | **建议**: | ||
| 161 | - Prometheus metrics | ||
| 162 | - 成功率、响应时间等指标 | ||
| 163 | - 长期趋势分析 | ||
| 164 | |||
| 165 | --- | ||
| 166 | |||
| 167 | ## 📦 架构优化 | ||
| 168 | |||
| 169 | ### 1. 模块化设计 | ||
| 170 | |||
| 171 | **建议**: | ||
| 172 | ``` | ||
| 173 | core/ | ||
| 174 | ├── request/ | ||
| 175 | ├── validation/ | ||
| 176 | ├── reporting/ | ||
| 177 | └── storage/ | ||
| 178 | |||
| 179 | plugins/ | ||
| 180 | ├── slack/ | ||
| 181 | ├── email/ | ||
| 182 | └── dingding/ | ||
| 183 | ``` | ||
| 184 | |||
| 185 | ### 2. 插件系统 | ||
| 186 | |||
| 187 | **建议**: | ||
| 188 | - 支持自定义验证器 | ||
| 189 | - 支持自定义报告生成器 | ||
| 190 | - 支持自定义通知器 | ||
| 191 | |||
| 192 | ### 3. REST API | ||
| 193 | |||
| 194 | **建议**: | ||
| 195 | - 提供 REST API | ||
| 196 | - Web UI 管理界面 | ||
| 197 | - 远程执行能力 | ||
| 198 | |||
| 199 | --- | ||
| 200 | |||
| 201 | ## 🧪 测试框架增强 | ||
| 202 | |||
| 203 | ### 1. 单元测试 | ||
| 204 | |||
| 205 | **建议**: | ||
| 206 | - 为核心模块增加单元测试 | ||
| 207 | - 达到 80% 覆盖率 | ||
| 208 | |||
| 209 | ### 2. 集成测试 | ||
| 210 | |||
| 211 | **建议**: | ||
| 212 | - Docker 容器化测试 | ||
| 213 | - 多环境集成测试 | ||
| 214 | - CI/CD 流水线 | ||
| 215 | |||
| 216 | ### 3. 性能测试 | ||
| 217 | |||
| 218 | **建议**: | ||
| 219 | - 压力测试支持 | ||
| 220 | - 基准测试 | ||
| 221 | - 性能回归检测 | ||
| 222 | |||
| 223 | --- | ||
| 224 | |||
| 225 | ## 🔄 工作流增强 | ||
| 226 | |||
| 227 | ### 1. 前置条件处理 | ||
| 228 | |||
| 229 | **建议**: | ||
| 230 | ```python | ||
| 231 | def setup_test_data(): | ||
| 232 | # 创建必要的测试数据 | ||
| 233 | pass | ||
| 234 | |||
| 235 | def cleanup_test_data(): | ||
| 236 | # 清理测试数据 | ||
| 237 | pass | ||
| 238 | ``` | ||
| 239 | |||
| 240 | ### 2. 后置处理 | ||
| 241 | |||
| 242 | **建议**: | ||
| 243 | - 自动生成测试报告 | ||
| 244 | - 自动发送通知 | ||
| 245 | - 自动更新测试矩阵 | ||
| 246 | |||
| 247 | ### 3. 失败重试 | ||
| 248 | |||
| 249 | **建议**: | ||
| 250 | ```python | ||
| 251 | @retry(max_attempts=3, backoff_factor=2) | ||
| 252 | def execute_test(case): | ||
| 253 | pass | ||
| 254 | ``` | ||
| 255 | |||
| 256 | --- | ||
| 257 | |||
| 258 | ## 📱 跨平台支持 | ||
| 259 | |||
| 260 | ### 1. 移动端API测试 | ||
| 261 | |||
| 262 | **建议**: | ||
| 263 | - 支持 Mobile API 测试 | ||
| 264 | - 支持 GraphQL | ||
| 265 | |||
| 266 | ### 2. 跨平台兼容性 | ||
| 267 | |||
| 268 | **建议**: | ||
| 269 | - Windows / Linux / macOS | ||
| 270 | - Python 3.9+ | ||
| 271 | - 更新依赖库 | ||
| 272 | |||
| 273 | --- | ||
| 274 | |||
| 275 | ## 🤖 AI/ML 集成 | ||
| 276 | |||
| 277 | ### 1. 智能测试生成 | ||
| 278 | |||
| 279 | **建议**: | ||
| 280 | - 使用 AI 自动生成测试用例 | ||
| 281 | - 使用 AI 进行断言生成 | ||
| 282 | |||
| 283 | ### 2. 异常检测 | ||
| 284 | |||
| 285 | **建议**: | ||
| 286 | - 使用 ML 检测异常行为 | ||
| 287 | - 自动关联异常根因 | ||
| 288 | |||
| 289 | --- | ||
| 290 | |||
| 291 | ## 📈 优先级 | ||
| 292 | |||
| 293 | | 优化项 | 优先级 | 难度 | 工作量 | | ||
| 294 | |-------|--------|------|--------| | ||
| 295 | | 并发执行 | 高 | 中 | 2-3天 | | ||
| 296 | | 连接池 | 高 | 低 | 1天 | | ||
| 297 | | 报告增强 | 中 | 低 | 3-5天 | | ||
| 298 | | 数据库支持 | 中 | 中 | 1周 | | ||
| 299 | | 插件系统 | 低 | 高 | 2周 | | ||
| 300 | | REST API | 低 | 高 | 2周 | | ||
| 301 | |||
| 302 | --- | ||
| 303 | |||
| 304 | ## 🎯 建议实施顺序 | ||
| 305 | |||
| 306 | 1. **第一阶段**:并发执行、连接池、报告增强 | ||
| 307 | 2. **第二阶段**:数据库支持、前置/后置处理 | ||
| 308 | 3. **第三阶段**:REST API、插件系统、Web UI | ||
| 309 | 4. **第四阶段**:AI/ML 集成、可观测性增强 | ||
| 310 | |||
| 311 | --- | ||
| 312 | |||
| 313 | ## 📞 反馈 | ||
| 314 | |||
| 315 | 如有优化建议,欢迎提交 Issue 或 Pull Request。 | ||
| 316 | |||
| 317 | --- | ||
| 318 | |||
| 319 | **🚀 不断优化,持续改进!** |
QUICK_DEPLOY.md
0 → 100644
| 1 | # 🚀 傻瓜式部署指南 | ||
| 2 | |||
| 3 | **目标**:从零开始,把项目部署到Linux服务器,使用Docker运行,集成到Jenkins,代码托管在Git仓库 | ||
| 4 | |||
| 5 | **预计时间**:30-60分钟 | ||
| 6 | |||
| 7 | **难度**:⭐⭐(超简单) | ||
| 8 | |||
| 9 | --- | ||
| 10 | |||
| 11 | ## 📋 前置条件(必须有) | ||
| 12 | |||
| 13 | 在开始之前,请确保你有: | ||
| 14 | |||
| 15 | - [ ] 一个GitHub/GitLab账户(用于托管代码) | ||
| 16 | - [ ] 一台Linux服务器(Ubuntu 20+ 或 CentOS 8+) | ||
| 17 | - [ ] Linux服务器可以上网 | ||
| 18 | - [ ] 一个Jenkins服务器(已安装) | ||
| 19 | - [ ] 本地电脑有Git命令行工具 | ||
| 20 | |||
| 21 | --- | ||
| 22 | |||
| 23 | ## 第一步:初始化本地Git仓库(5分钟) | ||
| 24 | |||
| 25 | ### 1.1 在项目目录初始化Git | ||
| 26 | |||
| 27 | ```bash | ||
| 28 | cd "D:\百音引擎项目\interface_test" | ||
| 29 | |||
| 30 | # Windows PowerShell 执行 | ||
| 31 | git init | ||
| 32 | git config user.name "你的名字" | ||
| 33 | git config user.email "你的邮箱@example.com" | ||
| 34 | ``` | ||
| 35 | |||
| 36 | ### 1.2 创建 .gitignore 文件 | ||
| 37 | |||
| 38 | 在项目根目录创建 `.gitignore` 文件(排除不需要的文件): | ||
| 39 | |||
| 40 | ```bash | ||
| 41 | # 创建文件 | ||
| 42 | cat > .gitignore << 'EOF' | ||
| 43 | # Python | ||
| 44 | __pycache__/ | ||
| 45 | *.py[cod] | ||
| 46 | *$py.class | ||
| 47 | .venv/ | ||
| 48 | venv/ | ||
| 49 | ENV/ | ||
| 50 | |||
| 51 | # IDE | ||
| 52 | .vscode/ | ||
| 53 | .idea/ | ||
| 54 | *.swp | ||
| 55 | *.swo | ||
| 56 | |||
| 57 | # Reports | ||
| 58 | api_test_report_*.html | ||
| 59 | api_test_report_*.xlsx | ||
| 60 | |||
| 61 | # OS | ||
| 62 | .DS_Store | ||
| 63 | Thumbs.db | ||
| 64 | |||
| 65 | # Logs | ||
| 66 | *.log | ||
| 67 | EOF | ||
| 68 | ``` | ||
| 69 | |||
| 70 | ### 1.3 添加所有文件到Git | ||
| 71 | |||
| 72 | ```bash | ||
| 73 | git add . | ||
| 74 | git commit -m "Initial commit: API自动化测试框架 | ||
| 75 | |||
| 76 | - 多环境支持(test/uat/prod) | ||
| 77 | - Docker容器化 | ||
| 78 | - Jenkins集成 | ||
| 79 | - 详细文档" | ||
| 80 | ``` | ||
| 81 | |||
| 82 | --- | ||
| 83 | |||
| 84 | ## 第二步:创建远程Git仓库(5分钟) | ||
| 85 | |||
| 86 | ### 2.1 在GitHub/GitLab创建新仓库 | ||
| 87 | |||
| 88 | **GitHub步骤**: | ||
| 89 | 1. 访问 https://github.com/new | ||
| 90 | 2. 输入仓库名:`api-test-framework` | ||
| 91 | 3. 描述:`API自动化测试框架 - 支持多环境、Docker、Jenkins` | ||
| 92 | 4. 选择 **Public**(如果是私有项目选Private) | ||
| 93 | 5. 点击 **Create repository** | ||
| 94 | |||
| 95 | **获取仓库URL**: | ||
| 96 | - HTTPS: `https://github.com/你的用户名/api-test-framework.git` | ||
| 97 | - SSH: `git@github.com:你的用户名/api-test-framework.git` | ||
| 98 | |||
| 99 | ### 2.2 推送代码到远程仓库 | ||
| 100 | |||
| 101 | ```bash | ||
| 102 | # 添加远程仓库地址 | ||
| 103 | git remote add origin https://github.com/你的用户名/api-test-framework.git | ||
| 104 | |||
| 105 | # 推送到远程(第一次) | ||
| 106 | git branch -M main | ||
| 107 | git push -u origin main | ||
| 108 | |||
| 109 | # 后续推送只需要 | ||
| 110 | git push | ||
| 111 | ``` | ||
| 112 | |||
| 113 | --- | ||
| 114 | |||
| 115 | ## 第三步:Linux服务器准备(10分钟) | ||
| 116 | |||
| 117 | ### 3.1 连接到Linux服务器 | ||
| 118 | |||
| 119 | ```bash | ||
| 120 | # 从本地电脑连接到服务器 | ||
| 121 | ssh root@你的服务器IP | ||
| 122 | # 或 | ||
| 123 | ssh ubuntu@你的服务器IP | ||
| 124 | ``` | ||
| 125 | |||
| 126 | ### 3.2 检查并安装Docker | ||
| 127 | |||
| 128 | ```bash | ||
| 129 | # 检查Docker是否已安装 | ||
| 130 | docker --version | ||
| 131 | |||
| 132 | # 如果未安装,执行以下命令(Ubuntu) | ||
| 133 | sudo apt update | ||
| 134 | sudo apt install -y docker.io docker-compose | ||
| 135 | |||
| 136 | # 如果是CentOS | ||
| 137 | sudo yum install -y docker docker-compose | ||
| 138 | |||
| 139 | # 启动Docker | ||
| 140 | sudo systemctl start docker | ||
| 141 | sudo systemctl enable docker | ||
| 142 | |||
| 143 | # 验证安装 | ||
| 144 | docker run hello-world | ||
| 145 | ``` | ||
| 146 | |||
| 147 | ### 3.3 检查并安装Git | ||
| 148 | |||
| 149 | ```bash | ||
| 150 | # 检查Git是否已安装 | ||
| 151 | git --version | ||
| 152 | |||
| 153 | # 如果未安装 | ||
| 154 | sudo apt install -y git # Ubuntu | ||
| 155 | # 或 | ||
| 156 | sudo yum install -y git # CentOS | ||
| 157 | ``` | ||
| 158 | |||
| 159 | ### 3.4 创建项目目录 | ||
| 160 | |||
| 161 | ```bash | ||
| 162 | # 创建项目目录 | ||
| 163 | sudo mkdir -p /opt/api-test | ||
| 164 | sudo chown -R $USER:$USER /opt/api-test | ||
| 165 | |||
| 166 | cd /opt/api-test | ||
| 167 | ``` | ||
| 168 | |||
| 169 | --- | ||
| 170 | |||
| 171 | ## 第四步:克隆项目到服务器(5分钟) | ||
| 172 | |||
| 173 | ```bash | ||
| 174 | cd /opt/api-test | ||
| 175 | |||
| 176 | # 克隆代码 | ||
| 177 | git clone https://github.com/你的用户名/api-test-framework.git . | ||
| 178 | |||
| 179 | # 验证文件是否齐全 | ||
| 180 | ls -la | ||
| 181 | ``` | ||
| 182 | |||
| 183 | **应该看到**: | ||
| 184 | ``` | ||
| 185 | Dockerfile | ||
| 186 | docker-compose.yml | ||
| 187 | api_test.py | ||
| 188 | env_config.py | ||
| 189 | requirements.txt | ||
| 190 | README.md | ||
| 191 | 等... | ||
| 192 | ``` | ||
| 193 | |||
| 194 | --- | ||
| 195 | |||
| 196 | ## 第五步:在Linux上测试Docker运行(10分钟) | ||
| 197 | |||
| 198 | ### 5.1 构建Docker镜像 | ||
| 199 | |||
| 200 | ```bash | ||
| 201 | cd /opt/api-test | ||
| 202 | |||
| 203 | # 构建镜像(第一次会很慢,10-15分钟) | ||
| 204 | docker-compose build | ||
| 205 | ``` | ||
| 206 | |||
| 207 | ### 5.2 运行第一次测试 | ||
| 208 | |||
| 209 | ```bash | ||
| 210 | # 创建报告目录 | ||
| 211 | mkdir -p reports | ||
| 212 | |||
| 213 | # 测试环境运行 | ||
| 214 | DEPLOY_ENV=test docker-compose run --rm api-test-runner | ||
| 215 | |||
| 216 | # 查看报告 | ||
| 217 | ls -la reports/ | ||
| 218 | ``` | ||
| 219 | |||
| 220 | **预期输出**: | ||
| 221 | ``` | ||
| 222 | ✅ 所有测试用例运行成功! | ||
| 223 | HTML测试报告已生成:api_test_report_api_cases_test_*.html | ||
| 224 | ``` | ||
| 225 | |||
| 226 | --- | ||
| 227 | |||
| 228 | ## 第六步:配置Jenkins(15分钟) | ||
| 229 | |||
| 230 | ### 6.1 进入Jenkins Web界面 | ||
| 231 | |||
| 232 | 打开浏览器访问: | ||
| 233 | ``` | ||
| 234 | http://你的Jenkins服务器IP:8080 | ||
| 235 | ``` | ||
| 236 | |||
| 237 | ### 6.2 创建新Pipeline任务 | ||
| 238 | |||
| 239 | 1. 点击 **+ 新建Item** | ||
| 240 | 2. 输入任务名称:`api-test-framework` | ||
| 241 | 3. 选择 **Pipeline** | ||
| 242 | 4. 点击 **OK** | ||
| 243 | |||
| 244 | ### 6.3 配置Pipeline | ||
| 245 | |||
| 246 | **第一部分:General** | ||
| 247 | - 描述:`API自动化测试框架 - 多环境支持` | ||
| 248 | |||
| 249 | **第二部分:Parameters** | ||
| 250 | 1. 点击 **Add Parameter** | ||
| 251 | 2. 选择 **Choice Parameter** | ||
| 252 | 3. 配置: | ||
| 253 | - Name: `DEPLOY_ENV` | ||
| 254 | - Choices: | ||
| 255 | ``` | ||
| 256 | test | ||
| 257 | uat | ||
| 258 | prod | ||
| 259 | ``` | ||
| 260 | |||
| 261 | **第三部分:Pipeline** | ||
| 262 | 1. Definition: 选择 **Pipeline script from SCM** | ||
| 263 | 2. SCM: 选择 **Git** | ||
| 264 | 3. 配置Git: | ||
| 265 | - Repository URL: `https://github.com/你的用户名/api-test-framework.git` | ||
| 266 | - Credentials: 如需认证,点击 **Add** 添加用户名/密码 | ||
| 267 | - Branch: `*/main` | ||
| 268 | - Script Path: `Jenkinsfile` | ||
| 269 | |||
| 270 | 4. 点击 **Save** | ||
| 271 | |||
| 272 | ### 6.4 首次运行Pipeline | ||
| 273 | |||
| 274 | 1. 点击 **Build with Parameters** | ||
| 275 | 2. 选择 **DEPLOY_ENV**: `test` | ||
| 276 | 3. 点击 **Build** | ||
| 277 | |||
| 278 | **查看构建日志**: | ||
| 279 | - 点击左侧 **#1** 查看构建进度 | ||
| 280 | - 点击 **Console Output** 查看详细日志 | ||
| 281 | |||
| 282 | --- | ||
| 283 | |||
| 284 | ## 第七步:验证完整流程(5分钟) | ||
| 285 | |||
| 286 | ### 7.1 验证Git仓库 | ||
| 287 | |||
| 288 | ```bash | ||
| 289 | # 确认远程仓库有代码 | ||
| 290 | git remote -v | ||
| 291 | git log --oneline | ||
| 292 | ``` | ||
| 293 | |||
| 294 | ### 7.2 验证Docker运行 | ||
| 295 | |||
| 296 | ```bash | ||
| 297 | cd /opt/api-test | ||
| 298 | |||
| 299 | # 再次运行测试 | ||
| 300 | DEPLOY_ENV=uat docker-compose run --rm api-test-runner | ||
| 301 | |||
| 302 | # 检查报告 | ||
| 303 | ls -la reports/api_test_report_*uat* | ||
| 304 | ``` | ||
| 305 | |||
| 306 | ### 7.3 验证Jenkins任务 | ||
| 307 | |||
| 308 | 1. 打开Jenkins Web界面 | ||
| 309 | 2. 点击你创建的任务 `api-test-framework` | ||
| 310 | 3. 点击 **Build History** 查看构建记录 | ||
| 311 | 4. 点击最新的构建号查看详情 | ||
| 312 | 5. 点击 **Console Output** 确认执行成功 | ||
| 313 | |||
| 314 | --- | ||
| 315 | |||
| 316 | ## 第八步:配置定时任务(可选,5分钟) | ||
| 317 | |||
| 318 | ### 8.1 在Jenkins中设置定时执行 | ||
| 319 | |||
| 320 | 1. 进入任务配置页面 | ||
| 321 | 2. 找到 **Build Triggers** | ||
| 322 | 3. 勾选 **Poll SCM** | ||
| 323 | 4. 输入 Cron 表达式(每天上午10点执行): | ||
| 324 | ``` | ||
| 325 | 0 10 * * * | ||
| 326 | ``` | ||
| 327 | |||
| 328 | ### 8.2 定时任务含义 | ||
| 329 | |||
| 330 | ``` | ||
| 331 | 分钟 小时 日 月 周几 | ||
| 332 | |||
| 333 | 0 10 * * * 每天上午10点执行 | ||
| 334 | 0 10 * * 1-5 工作日上午10点执行 | ||
| 335 | 0 */6 * * * 每6小时执行一次 | ||
| 336 | 0 2 * * * 每天凌晨2点执行 | ||
| 337 | ``` | ||
| 338 | |||
| 339 | --- | ||
| 340 | |||
| 341 | ## ✅ 检查清单 | ||
| 342 | |||
| 343 | 完成以下所有步骤,确保部署成功: | ||
| 344 | |||
| 345 | - [ ] Git仓库已初始化,代码已推送到GitHub/GitLab | ||
| 346 | - [ ] Linux服务器已安装Docker和Git | ||
| 347 | - [ ] 项目代码已克隆到 `/opt/api-test` | ||
| 348 | - [ ] Docker镜像已成功构建 | ||
| 349 | - [ ] 本地Docker测试已通过(能生成报告文件) | ||
| 350 | - [ ] Jenkins任务已创建 | ||
| 351 | - [ ] Jenkins能够参数化执行(Build with Parameters) | ||
| 352 | - [ ] 至少成功运行过一次Pipeline | ||
| 353 | - [ ] 报告文件已生成并包含环境标识 | ||
| 354 | |||
| 355 | --- | ||
| 356 | |||
| 357 | ## 🚀 快速命令参考 | ||
| 358 | |||
| 359 | ### 本地操作 | ||
| 360 | |||
| 361 | ```bash | ||
| 362 | # Git初始化和提交 | ||
| 363 | git init | ||
| 364 | git add . | ||
| 365 | git commit -m "message" | ||
| 366 | git remote add origin <url> | ||
| 367 | git push -u origin main | ||
| 368 | |||
| 369 | # 后续更新 | ||
| 370 | git add . | ||
| 371 | git commit -m "message" | ||
| 372 | git push | ||
| 373 | ``` | ||
| 374 | |||
| 375 | ### 服务器操作 | ||
| 376 | |||
| 377 | ```bash | ||
| 378 | # 连接服务器 | ||
| 379 | ssh root@IP | ||
| 380 | |||
| 381 | # Docker操作 | ||
| 382 | docker-compose build | ||
| 383 | DEPLOY_ENV=test docker-compose run --rm api-test-runner | ||
| 384 | |||
| 385 | # Git更新代码 | ||
| 386 | cd /opt/api-test | ||
| 387 | git pull | ||
| 388 | ``` | ||
| 389 | |||
| 390 | ### Jenkins操作 | ||
| 391 | |||
| 392 | ``` | ||
| 393 | Build with Parameters → 选择环境 → Build | ||
| 394 | ``` | ||
| 395 | |||
| 396 | --- | ||
| 397 | |||
| 398 | ## 🆘 常见问题速解 | ||
| 399 | |||
| 400 | ### Q1: Git提交报错"fatal: not a git repository" | ||
| 401 | |||
| 402 | **解决**: | ||
| 403 | ```bash | ||
| 404 | cd 到项目目录 | ||
| 405 | git init | ||
| 406 | ``` | ||
| 407 | |||
| 408 | ### Q2: Docker镜像构建失败 | ||
| 409 | |||
| 410 | **解决**: | ||
| 411 | ```bash | ||
| 412 | # 检查网络 | ||
| 413 | ping 8.8.8.8 | ||
| 414 | |||
| 415 | # 重试构建 | ||
| 416 | docker-compose build --no-cache | ||
| 417 | ``` | ||
| 418 | |||
| 419 | ### Q3: Jenkins连接Git仓库失败 | ||
| 420 | |||
| 421 | **解决**: | ||
| 422 | 1. 检查仓库URL是否正确 | ||
| 423 | 2. 如果是私有仓库,需要添加SSH密钥或用户名/密码 | ||
| 424 | 3. 在Jenkins系统设置中配置凭证 | ||
| 425 | |||
| 426 | ### Q4: 报告文件找不到 | ||
| 427 | |||
| 428 | **解决**: | ||
| 429 | ```bash | ||
| 430 | # 检查reports目录 | ||
| 431 | ls -la /opt/api-test/reports/ | ||
| 432 | |||
| 433 | # 检查容器日志 | ||
| 434 | docker logs <container-id> | ||
| 435 | ``` | ||
| 436 | |||
| 437 | ### Q5: Jenkins无法访问 | ||
| 438 | |||
| 439 | **解决**: | ||
| 440 | ```bash | ||
| 441 | # 检查Jenkins是否运行 | ||
| 442 | ps aux | grep jenkins | ||
| 443 | |||
| 444 | # 检查端口8080是否打开 | ||
| 445 | netstat -tlnp | grep 8080 | ||
| 446 | |||
| 447 | # 检查防火墙 | ||
| 448 | sudo ufw allow 8080 | ||
| 449 | ``` | ||
| 450 | |||
| 451 | --- | ||
| 452 | |||
| 453 | ## 📞 需要帮助? | ||
| 454 | |||
| 455 | 1. **查看详细文档**: | ||
| 456 | - README.md - 项目介绍 | ||
| 457 | - DEPLOYMENT_GUIDE.md - 部署详情 | ||
| 458 | - DOCKER_GUIDE.md - Docker详情 | ||
| 459 | - USER_GUIDE.md - 使用指南 | ||
| 460 | |||
| 461 | 2. **查看日志**: | ||
| 462 | - Docker日志:`docker logs <container-id>` | ||
| 463 | - Jenkins日志:Jenkins Web界面 → Console Output | ||
| 464 | - 服务器日志:`/var/log/` | ||
| 465 | |||
| 466 | 3. **测试连通性**: | ||
| 467 | ```bash | ||
| 468 | # 测试Git连接 | ||
| 469 | git clone <url> | ||
| 470 | |||
| 471 | # 测试Docker运行 | ||
| 472 | docker run hello-world | ||
| 473 | |||
| 474 | # 测试Jenkins API | ||
| 475 | curl http://localhost:8080 | ||
| 476 | ``` | ||
| 477 | |||
| 478 | --- | ||
| 479 | |||
| 480 | ## 📊 部署架构 | ||
| 481 | |||
| 482 | ``` | ||
| 483 | ┌─────────────────────────────────────────────┐ | ||
| 484 | │ GitHub/GitLab 仓库 │ | ||
| 485 | │ (代码托管) │ | ||
| 486 | └─────────────────────────────────────────────┘ | ||
| 487 | ↓ git clone/pull | ||
| 488 | ┌─────────────────────────────────────────────┐ | ||
| 489 | │ Linux 服务器 /opt/api-test │ | ||
| 490 | │ ┌─────────────────────────────────────┐ │ | ||
| 491 | │ │ Docker 容器 │ │ | ||
| 492 | │ │ ┌───────────────────────────────┐ │ │ | ||
| 493 | │ │ │ Python 环境 │ │ │ | ||
| 494 | │ │ │ - api_test.py │ │ │ | ||
| 495 | │ │ │ - env_config.py │ │ │ | ||
| 496 | │ │ │ - requirements.txt │ │ │ | ||
| 497 | │ │ └───────────────────────────────┘ │ │ | ||
| 498 | │ └─────────────────────────────────────┘ │ | ||
| 499 | └─────────────────────────────────────────────┘ | ||
| 500 | ↑ 触发 | ||
| 501 | ┌─────────────────────────────────────────────┐ | ||
| 502 | │ Jenkins (CI/CD) │ | ||
| 503 | │ - 参数化Build(环境选择) │ | ||
| 504 | │ - 定时执行(Cron) │ | ||
| 505 | │ - 报告发布 │ | ||
| 506 | └─────────────────────────────────────────────┘ | ||
| 507 | ``` | ||
| 508 | |||
| 509 | --- | ||
| 510 | |||
| 511 | ## ✨ 下一步 | ||
| 512 | |||
| 513 | 部署完成后,你可以: | ||
| 514 | |||
| 515 | 1. **配置邮件通知**:当测试失败时,Jenkins自动发送邮件 | ||
| 516 | 2. **集成钉钉通知**:实时推送测试结果 | ||
| 517 | 3. **配置测试报告展示**:在Jenkins界面展示HTML报告 | ||
| 518 | 4. **设置性能监控**:监控测试响应时间趋势 | ||
| 519 | |||
| 520 | 详见各个技术文档。 | ||
| 521 | |||
| 522 | --- | ||
| 523 | |||
| 524 | **🎉 恭喜!你已经完成了完整的CI/CD部署!** | ||
| 525 | |||
| 526 | 现在你可以: | ||
| 527 | - ✅ 本地开发,推送代码到Git | ||
| 528 | - ✅ Git自动触发Jenkins构建 | ||
| 529 | - ✅ Jenkins自动构建Docker镜像 | ||
| 530 | - ✅ Docker自动运行测试 | ||
| 531 | - ✅ 自动生成并展示测试报告 | ||
| 532 | |||
| 533 | **Happy Testing! 🚀** |
README.md
0 → 100644
| 1 | # 🚀 API 自动化测试框架 | ||
| 2 | |||
| 3 | ## 📌 项目简介 | ||
| 4 | |||
| 5 | 一套完整的API自动化测试框架,支持**多环境执行**(test/uat/prod)、Excel驱动测试、灵活的用例筛选和详细的测试报告生成。 | ||
| 6 | |||
| 7 | **核心特性**: | ||
| 8 | - ✅ **多环境支持**:一套代码运行在test/uat/prod三个环境 | ||
| 9 | - ✅ **Excel驱动**:用例配置在Excel中,易于维护 | ||
| 10 | - ✅ **灵活筛选**:通过env_scope字段控制各环境的执行用例 | ||
| 11 | - ✅ **丰富的验证**:支持多种关系运算符($gt、$contains、$regex等) | ||
| 12 | - ✅ **详细报告**:HTML和Excel双格式测试报告 | ||
| 13 | - ✅ **Jenkins集成**:参数化执行,支持定时任务 | ||
| 14 | - ✅ **Docker支持**:容器化部署 | ||
| 15 | |||
| 16 | --- | ||
| 17 | |||
| 18 | ## 🎯 快速开始 | ||
| 19 | |||
| 20 | ### 需求 | ||
| 21 | |||
| 22 | - Python 3.8+ | ||
| 23 | - pip 和 venv | ||
| 24 | - 依赖库(详见requirements.txt) | ||
| 25 | |||
| 26 | ### 1️⃣ 安装依赖 | ||
| 27 | |||
| 28 | ```bash | ||
| 29 | # 创建虚拟环境 | ||
| 30 | python3 -m venv venv | ||
| 31 | |||
| 32 | # 激活虚拟环境 | ||
| 33 | source venv/bin/activate # Linux/Mac | ||
| 34 | # 或 | ||
| 35 | .\venv\Scripts\activate # Windows | ||
| 36 | |||
| 37 | # 安装依赖 | ||
| 38 | pip install -r requirements.txt | ||
| 39 | ``` | ||
| 40 | |||
| 41 | ### 2️⃣ 准备测试用例 | ||
| 42 | |||
| 43 | 编辑 `api_cases.xlsx`,在表格中添加新列 `env_scope`,标记每个用例的执行范围: | ||
| 44 | |||
| 45 | ``` | ||
| 46 | env_scope 值说明: | ||
| 47 | - all 或留空 → 所有环境执行 | ||
| 48 | - test → 仅测试环境执行 | ||
| 49 | - uat → 仅UAT环境执行 | ||
| 50 | - prod → 仅生产环境执行 | ||
| 51 | - test,uat → 测试和UAT执行,生产跳过 | ||
| 52 | ``` | ||
| 53 | |||
| 54 | ### 3️⃣ 运行测试 | ||
| 55 | |||
| 56 | ```bash | ||
| 57 | # 测试环境 | ||
| 58 | export DEPLOY_ENV=test | ||
| 59 | python3 api_test.py | ||
| 60 | |||
| 61 | # UAT环境 | ||
| 62 | export DEPLOY_ENV=uat | ||
| 63 | python3 api_test.py | ||
| 64 | |||
| 65 | # 生产环境 | ||
| 66 | export DEPLOY_ENV=prod | ||
| 67 | python3 api_test.py | ||
| 68 | ``` | ||
| 69 | |||
| 70 | ### 4️⃣ 查看报告 | ||
| 71 | |||
| 72 | 执行完成后,生成的报告文件: | ||
| 73 | - `api_test_report_*_test_*.html` - 测试环境HTML报告 | ||
| 74 | - `api_test_report_*_uat_*.html` - UAT环境HTML报告 | ||
| 75 | - `api_test_report_*_prod_*.html` - 生产环境HTML报告 | ||
| 76 | - `api_test_report_*.xlsx` - Excel格式报告 | ||
| 77 | |||
| 78 | --- | ||
| 79 | |||
| 80 | ## 🐳 Docker 快速开始 | ||
| 81 | |||
| 82 | ### 前提条件 | ||
| 83 | - Docker 已安装 | ||
| 84 | - docker-compose 已安装 | ||
| 85 | |||
| 86 | ### 运行命令 | ||
| 87 | |||
| 88 | **测试环境**: | ||
| 89 | ```bash | ||
| 90 | docker-compose run --rm -e DEPLOY_ENV=test api-test-runner | ||
| 91 | ``` | ||
| 92 | |||
| 93 | **UAT环境**: | ||
| 94 | ```bash | ||
| 95 | docker-compose run --rm -e DEPLOY_ENV=uat api-test-runner | ||
| 96 | ``` | ||
| 97 | |||
| 98 | **生产环境**: | ||
| 99 | ```bash | ||
| 100 | docker-compose run --rm -e DEPLOY_ENV=prod api-test-runner | ||
| 101 | ``` | ||
| 102 | |||
| 103 | 报告文件保存在 `./reports` 目录 | ||
| 104 | |||
| 105 | 详细说明见 [DOCKER_GUIDE.md](./DOCKER_GUIDE.md) | ||
| 106 | |||
| 107 | --- | ||
| 108 | |||
| 109 | ## 📖 文档导航 | ||
| 110 | |||
| 111 | | 文档 | 说明 | 适用场景 | | ||
| 112 | |------|------|---------| | ||
| 113 | | **[USER_GUIDE.md](./USER_GUIDE.md)** | 详细使用指南 | 如何使用框架、多环境配置、常见问题 | | ||
| 114 | | **[DEPLOYMENT_GUIDE.md](./DEPLOYMENT_GUIDE.md)** | 部署安装指南 | Linux服务器部署、Jenkins配置 | | ||
| 115 | | **[DOCKER_GUIDE.md](./DOCKER_GUIDE.md)** | Docker部署指南 | Docker容器化、docker-compose、Jenkins集成 | | ||
| 116 | | **[OPERATOR_GUIDE.md](./OPERATOR_GUIDE.md)** | 运维指南 | 关系运算符使用 | | ||
| 117 | | **[OPTIMIZATION_REQUIREMENTS.md](./OPTIMIZATION_REQUIREMENTS.md)** | 优化需求 | 性能优化、增强建议 | | ||
| 118 | |||
| 119 | --- | ||
| 120 | |||
| 121 | ## 🏗️ 项目结构 | ||
| 122 | |||
| 123 | ``` | ||
| 124 | interface_test/ | ||
| 125 | ├── 📄 README.md # 项目介绍(本文件) | ||
| 126 | ├── 📖 USER_GUIDE.md # 使用指南 | ||
| 127 | ├── 🚀 DEPLOYMENT_GUIDE.md # Linux部署指南 | ||
| 128 | ├── 🐳 DOCKER_GUIDE.md # Docker部署指南 | ||
| 129 | ├── 🔧 OPERATOR_GUIDE.md # 运维指南 | ||
| 130 | ├── 📈 OPTIMIZATION_REQUIREMENTS.md # 优化需求 | ||
| 131 | │ | ||
| 132 | ├── 💻 api_test.py # 主测试脚本 | ||
| 133 | ├── ⚙️ env_config.py # 环境配置(多环境IP) | ||
| 134 | ├── 📊 api_cases.xlsx # 测试用例(Excel) | ||
| 135 | ├── requirements.txt # Python依赖 | ||
| 136 | │ | ||
| 137 | ├── 🐳 Dockerfile # Docker镜像配置 | ||
| 138 | ├── 📦 docker-compose.yml # Docker容器编排 | ||
| 139 | ├── .dockerignore # Docker忽略文件 | ||
| 140 | │ | ||
| 141 | ├── Jenkinsfile # Jenkins标准配置(直接执行) | ||
| 142 | ├── Jenkinsfile.docker # Jenkins Docker配置(容器中执行) | ||
| 143 | │ | ||
| 144 | └── .venv/ # Python虚拟环境 | ||
| 145 | ``` | ||
| 146 | |||
| 147 | --- | ||
| 148 | |||
| 149 | ## ✨ 核心功能 | ||
| 150 | |||
| 151 | ### 1️⃣ 多环境执行 | ||
| 152 | |||
| 153 | **支持三个环境**: | ||
| 154 | |||
| 155 | | 环境 | IP_HOST | 用途 | | ||
| 156 | |------|---------|------| | ||
| 157 | | test | ai-test.hikoon.com | 测试环境 | | ||
| 158 | | uat | ai-uat.hikoon.com | UAT验收环境 | | ||
| 159 | | prod | api.hikoon.com | 生产环境 | | ||
| 160 | |||
| 161 | **环境选择**: | ||
| 162 | ```bash | ||
| 163 | export DEPLOY_ENV=test # 或 uat / prod | ||
| 164 | python3 api_test.py | ||
| 165 | ``` | ||
| 166 | |||
| 167 | ### 2️⃣ 用例筛选 | ||
| 168 | |||
| 169 | 通过 `env_scope` 列灵活控制用例执行范围: | ||
| 170 | |||
| 171 | ``` | ||
| 172 | env_scope = 'all' → 三环境都执行 | ||
| 173 | env_scope = 'test,uat' → 生产环境跳过(避免危险操作) | ||
| 174 | env_scope = 'prod' → 仅生产环境执行 | ||
| 175 | ``` | ||
| 176 | |||
| 177 | ### 3️⃣ 关系运算符 | ||
| 178 | |||
| 179 | `expected_response` 支持多种验证方式: | ||
| 180 | |||
| 181 | ```json | ||
| 182 | { | ||
| 183 | "code": {"$ne": 500}, // 不等于 | ||
| 184 | "count": {"$gt": 0}, // 大于 | ||
| 185 | "status": {"$in": ["ok", "done"]}, // 在列表中 | ||
| 186 | "msg": {"$contains": "success"}, // 字符串包含 | ||
| 187 | "email": {"$regex": "^.*@.*$"} // 正则匹配 | ||
| 188 | } | ||
| 189 | ``` | ||
| 190 | |||
| 191 | 详见 [OPERATOR_GUIDE.md](./OPERATOR_GUIDE.md) | ||
| 192 | |||
| 193 | ### 4️⃣ 详细报告 | ||
| 194 | |||
| 195 | - 📊 **统计信息**:总数、成功数、失败数、耗时 | ||
| 196 | - 🔍 **筛选功能**:按失败、响应时间筛选 | ||
| 197 | - 📋 **详细结果**:请求参数、期望值、实际值、错误信息 | ||
| 198 | - 📈 **性能分析**:响应时间分布 | ||
| 199 | - 🔖 **环境标识**:报告文件名包含运行环境 | ||
| 200 | |||
| 201 | --- | ||
| 202 | |||
| 203 | ## 🔧 环境配置 | ||
| 204 | |||
| 205 | ### env_config.py | ||
| 206 | |||
| 207 | 定义多个环境的IP和其他变量: | ||
| 208 | |||
| 209 | ```python | ||
| 210 | import os | ||
| 211 | |||
| 212 | # 多环境IP配置 | ||
| 213 | DEPLOY_ENV = os.getenv('DEPLOY_ENV', 'test') | ||
| 214 | IP_HOST_MAP = { | ||
| 215 | 'test': 'ai-test.hikoon.com', | ||
| 216 | 'uat': 'ai-uat.hikoon.com', | ||
| 217 | 'prod': 'api.hikoon.com' | ||
| 218 | } | ||
| 219 | IP_HOST = os.getenv('IP_HOST', IP_HOST_MAP.get(DEPLOY_ENV, 'ai-test.hikoon.com')) | ||
| 220 | |||
| 221 | # 当前日期(格式:YY-MM-DD) | ||
| 222 | CURRENT_DATE = datetime.now().strftime('%y-%m-%d') | ||
| 223 | |||
| 224 | # 其他环境变量 | ||
| 225 | RANDOM_EMAIL = gen_email() # 自动生成随机邮箱 | ||
| 226 | ``` | ||
| 227 | |||
| 228 | ### 自定义环境变量 | ||
| 229 | |||
| 230 | ```python | ||
| 231 | # env_config.py 中添加 | ||
| 232 | API_TOKEN = "your-token-here" | ||
| 233 | TIMEOUT = 60 | ||
| 234 | DEBUG = True | ||
| 235 | ``` | ||
| 236 | |||
| 237 | 然后在Excel用例中使用: | ||
| 238 | ``` | ||
| 239 | URL: {IP_HOST}/api/users | ||
| 240 | Headers: {"Authorization": "Bearer {API_TOKEN}"} | ||
| 241 | Params: {"timestamp": "{CURRENT_DATE}"} | ||
| 242 | ``` | ||
| 243 | |||
| 244 | --- | ||
| 245 | |||
| 246 | ## 📊 Excel测试用例格式 | ||
| 247 | |||
| 248 | | 字段 | 说明 | 示例 | | ||
| 249 | |------|------|------| | ||
| 250 | | case_id | 用例ID | 1 | | ||
| 251 | | api_name | 接口名称 | 用户注册 | | ||
| 252 | | is_run | 是否执行(1=跳过) | 0 | | ||
| 253 | | env_scope | 执行环围(新增) | all | | ||
| 254 | | method | 请求方法 | POST | | ||
| 255 | | ip_host / url | 基础URL | ai-test.hikoon.com | | ||
| 256 | | path | 路径 | /api/users/register | | ||
| 257 | | params | 请求参数(JSON) | {"email":"test@qq.com"} | | ||
| 258 | | headers | 请求头(JSON) | {"Content-Type":"application/json"} | | ||
| 259 | | expected_code | 期望状态码 | 200 | | ||
| 260 | | expected_response | 期望响应 | {"code":0} | | ||
| 261 | | extract_vars | 提取变量 | user_id=data.user_id | | ||
| 262 | |||
| 263 | --- | ||
| 264 | |||
| 265 | ## 🚀 Jenkins集成 | ||
| 266 | |||
| 267 | ### Build with Parameters | ||
| 268 | |||
| 269 | 在Jenkins中参数化执行,选择环境: | ||
| 270 | |||
| 271 | 1. 点击 **Build with Parameters** | ||
| 272 | 2. 选择 **DEPLOY_ENV**: test / uat / prod | ||
| 273 | 3. 点击 **Build** | ||
| 274 | |||
| 275 | ### 自动环境映射 | ||
| 276 | |||
| 277 | ```groovy | ||
| 278 | environment { | ||
| 279 | DEPLOY_ENV = "${params.DEPLOY_ENV ?: 'test'}" | ||
| 280 | IP_HOST_TEST = 'ai-test.hikoon.com' | ||
| 281 | IP_HOST_UAT = 'ai-uat.hikoon.com' | ||
| 282 | IP_HOST_PROD = 'api.hikoon.com' | ||
| 283 | } | ||
| 284 | ``` | ||
| 285 | |||
| 286 | ### 执行流程 | ||
| 287 | |||
| 288 | ``` | ||
| 289 | Jenkins参数选择 → DEPLOY_ENV环境变量 → IP_HOST自动映射 → 用例执行 → 报告生成 | ||
| 290 | ``` | ||
| 291 | |||
| 292 | 详见 [DEPLOYMENT_GUIDE.md](./DEPLOYMENT_GUIDE.md) | ||
| 293 | |||
| 294 | --- | ||
| 295 | |||
| 296 | ## 📝 常见用例场景 | ||
| 297 | |||
| 298 | ### 场景1:通用接口测试(所有环境) | ||
| 299 | |||
| 300 | ``` | ||
| 301 | env_scope = 'all' | ||
| 302 | 用例:登录、查询用户信息、获取列表等 | ||
| 303 | 结果:test✅ / uat✅ / prod✅ | ||
| 304 | ``` | ||
| 305 | |||
| 306 | ### 场景2:跳过生产的危险操作 | ||
| 307 | |||
| 308 | ``` | ||
| 309 | env_scope = 'test,uat' | ||
| 310 | 用例:删除数据、清理数据库、重置密码等 | ||
| 311 | 结果:test✅ / uat✅ / prod⏭️(跳过) | ||
| 312 | ``` | ||
| 313 | |||
| 314 | ### 场景3:仅生产执行 | ||
| 315 | |||
| 316 | ``` | ||
| 317 | env_scope = 'prod' | ||
| 318 | 用例:导出报表、数据备份等(基于真实数据) | ||
| 319 | 结果:test⏭️ / uat⏭️ / prod✅ | ||
| 320 | ``` | ||
| 321 | |||
| 322 | --- | ||
| 323 | |||
| 324 | ## 🆘 常见问题 | ||
| 325 | |||
| 326 | ### Q: 如何新增环境? | ||
| 327 | |||
| 328 | 编辑 `env_config.py` 和 `Jenkinsfile` 的IP配置。详见 [USER_GUIDE.md](./USER_GUIDE.md) 的Q&A部分 | ||
| 329 | |||
| 330 | ### Q: env_scope 支持哪些值? | ||
| 331 | |||
| 332 | 支持:all / test / uat / prod 及其组合(用逗号分隔)。详见 [USER_GUIDE.md](./USER_GUIDE.md) | ||
| 333 | |||
| 334 | ### Q: 如何禁用某个用例? | ||
| 335 | |||
| 336 | 设置 `is_run = 1` 或 `env_scope` 不包含当前环境。详见 [USER_GUIDE.md](./USER_GUIDE.md) | ||
| 337 | |||
| 338 | ### Q: 生产环境执行失败怎么办? | ||
| 339 | |||
| 340 | 检查 `env_scope` 是否正确、IP_HOST是否可访问。详见 [DEPLOYMENT_GUIDE.md](./DEPLOYMENT_GUIDE.md) | ||
| 341 | |||
| 342 | --- | ||
| 343 | |||
| 344 | ## 📞 获取帮助 | ||
| 345 | |||
| 346 | - **使用问题** → [USER_GUIDE.md](./USER_GUIDE.md) | ||
| 347 | - **部署问题** → [DEPLOYMENT_GUIDE.md](./DEPLOYMENT_GUIDE.md) | ||
| 348 | - **Docker问题** → [DOCKER_GUIDE.md](./DOCKER_GUIDE.md) | ||
| 349 | - **验证方式** → [OPERATOR_GUIDE.md](./OPERATOR_GUIDE.md) | ||
| 350 | - **优化建议** → [OPTIMIZATION_REQUIREMENTS.md](./OPTIMIZATION_REQUIREMENTS.md) | ||
| 351 | |||
| 352 | --- | ||
| 353 | |||
| 354 | ## 📈 最近更新 | ||
| 355 | |||
| 356 | ### v1.0.0 (2026-01-21) | ||
| 357 | |||
| 358 | ✅ 完整框架实现 | ||
| 359 | - ✅ 多环境执行支持(test/uat/prod) | ||
| 360 | - ✅ env_scope字段用于用例筛选 | ||
| 361 | - ✅ 关系运算符验证($eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $contains, $regex, $range等) | ||
| 362 | - ✅ 报告文件名包含环境标识 | ||
| 363 | - ✅ Docker容器化支持 | ||
| 364 | - ✅ 参数化Jenkins配置 | ||
| 365 | - ✅ 完整文档整合 | ||
| 366 | |||
| 367 | --- | ||
| 368 | |||
| 369 | ## 📄 许可证 | ||
| 370 | |||
| 371 | 项目所有者:百音引擎项目 | ||
| 372 | |||
| 373 | --- | ||
| 374 | |||
| 375 | **🎉 祝你使用愉快!如有问题,请查看对应的文档或联系项目维护者。** |
USER_GUIDE.md
0 → 100644
| 1 | # 📖 用户使用指南 | ||
| 2 | |||
| 3 | ## 目录 | ||
| 4 | |||
| 5 | - [多环境执行](#多环境执行) | ||
| 6 | - [用例管理](#用例管理) | ||
| 7 | - [响应验证](#响应验证) | ||
| 8 | - [本地运行](#本地运行) | ||
| 9 | - [Jenkins执行](#jenkins执行) | ||
| 10 | - [常见问题](#常见问题) | ||
| 11 | - [最佳实践](#最佳实践) | ||
| 12 | |||
| 13 | --- | ||
| 14 | |||
| 15 | ## 🌍 多环境执行 | ||
| 16 | |||
| 17 | ### 支持的环境 | ||
| 18 | |||
| 19 | | 环境 | 标识 | IP | 用途 | | ||
| 20 | |------|------|------|------| | ||
| 21 | | **测试** | test | ai-test.hikoon.com | 开发和初始测试 | | ||
| 22 | | **UAT** | uat | ai-uat.hikoon.com | 用户验收测试 | | ||
| 23 | | **生产** | prod | api.hikoon.com | 生产环境验证 | | ||
| 24 | |||
| 25 | ### 如何在不同环境运行 | ||
| 26 | |||
| 27 | **设置环境变量 DEPLOY_ENV**,然后运行测试: | ||
| 28 | |||
| 29 | ```bash | ||
| 30 | # 测试环境 | ||
| 31 | export DEPLOY_ENV=test | ||
| 32 | python3 api_test.py | ||
| 33 | |||
| 34 | # UAT环境 | ||
| 35 | export DEPLOY_ENV=uat | ||
| 36 | python3 api_test.py | ||
| 37 | |||
| 38 | # 生产环境 | ||
| 39 | export DEPLOY_ENV=prod | ||
| 40 | python3 api_test.py | ||
| 41 | ``` | ||
| 42 | |||
| 43 | **说明**: | ||
| 44 | - 默认环境为 `test`(如不设置 DEPLOY_ENV) | ||
| 45 | - IP_HOST 会根据环境从 `env_config.py` 中自动选择 | ||
| 46 | - 可通过环境变量 `IP_HOST` 覆盖配置的地址 | ||
| 47 | |||
| 48 | --- | ||
| 49 | |||
| 50 | ## 📊 用例管理 | ||
| 51 | |||
| 52 | ### env_scope 列说明 | ||
| 53 | |||
| 54 | 每个用例都有一个 `env_scope` 字段,用于控制在哪些环境中执行该用例。 | ||
| 55 | |||
| 56 | #### env_scope 取值 | ||
| 57 | |||
| 58 | | 值 | 含义 | 执行结果 | | ||
| 59 | |----|------|---------| | ||
| 60 | | 留空 或 `all` | 所有环境执行 | test✅ / uat✅ / prod✅ | | ||
| 61 | | `test` | 仅测试环境 | test✅ / uat⏭️ / prod⏭️ | | ||
| 62 | | `uat` | 仅UAT环境 | test⏭️ / uat✅ / prod⏭️ | | ||
| 63 | | `prod` | 仅生产环境 | test⏭️ / uat⏭️ / prod✅ | | ||
| 64 | | `test,uat` | 测试+UAT | test✅ / uat✅ / prod⏭️ | | ||
| 65 | | `test,prod` | 测试+生产 | test✅ / uat⏭️ / prod✅ | | ||
| 66 | |||
| 67 | #### 添加 env_scope 列 | ||
| 68 | |||
| 69 | **首次使用**,需要在 Excel 表格中手动添加 `env_scope` 列: | ||
| 70 | |||
| 71 | 1. 打开 `api_cases.xlsx` | ||
| 72 | 2. 在最后一列后添加新列 | ||
| 73 | 3. 列头命名为:`env_scope` | ||
| 74 | 4. 为所有数据行填入相应的值 | ||
| 75 | 5. 保存文件 | ||
| 76 | |||
| 77 | #### 编辑 env_scope 值 | ||
| 78 | |||
| 79 | 根据用例特性设置相应的环境范围 | ||
| 80 | |||
| 81 | ### is_run 字段 | ||
| 82 | |||
| 83 | 原有的 `is_run` 字段仍然保留: | ||
| 84 | - `is_run = 0` 或**留空** → 执行该用例(受env_scope影响) | ||
| 85 | - `is_run = 1` → 在**所有环境**都跳过该用例 | ||
| 86 | |||
| 87 | **执行优先级**:`is_run = 1` → 跳过 → 检查 env_scope → 决定是否执行 | ||
| 88 | |||
| 89 | --- | ||
| 90 | |||
| 91 | ## ✅ 响应验证 | ||
| 92 | |||
| 93 | ### expected_response 字段 | ||
| 94 | |||
| 95 | 支持多种关系运算符,提供灵活的响应验证。 | ||
| 96 | |||
| 97 | #### 支持的运算符 | ||
| 98 | |||
| 99 | - `$eq`: 等于 | ||
| 100 | - `$ne`: 不等于 | ||
| 101 | - `$gt`: 大于 | ||
| 102 | - `$gte`: 大于等于 | ||
| 103 | - `$lt`: 小于 | ||
| 104 | - `$lte`: 小于等于 | ||
| 105 | - `$in`: 值在列表中 | ||
| 106 | - `$nin`: 值不在列表中 | ||
| 107 | - `$contains`: 字符串包含 | ||
| 108 | - `$not_contains`: 字符串不包含 | ||
| 109 | - `$regex`: 正则表达式匹配 | ||
| 110 | - `$range`: 范围判断 [min, max] | ||
| 111 | |||
| 112 | #### 使用示例 | ||
| 113 | |||
| 114 | ```json | ||
| 115 | { | ||
| 116 | "code": {"$ne": 500}, | ||
| 117 | "count": {"$gt": 0}, | ||
| 118 | "status": {"$in": ["ok", "done"]}, | ||
| 119 | "msg": {"$contains": "success"}, | ||
| 120 | "email": {"$regex": "^.*@.*$"}, | ||
| 121 | "age": {"$range": [18, 100]} | ||
| 122 | } | ||
| 123 | ``` | ||
| 124 | |||
| 125 | --- | ||
| 126 | |||
| 127 | ## 🏃 本地运行 | ||
| 128 | |||
| 129 | ### 基础运行 | ||
| 130 | |||
| 131 | ```bash | ||
| 132 | # 1. 激活虚拟环境 | ||
| 133 | source venv/bin/activate # Linux/Mac | ||
| 134 | # 或 | ||
| 135 | .\venv\Scripts\activate # Windows | ||
| 136 | |||
| 137 | # 2. 设置环境(可选,默认test) | ||
| 138 | export DEPLOY_ENV=test | ||
| 139 | |||
| 140 | # 3. 运行测试 | ||
| 141 | python3 api_test.py | ||
| 142 | ``` | ||
| 143 | |||
| 144 | --- | ||
| 145 | |||
| 146 | ## 🔗 Jenkins执行 | ||
| 147 | |||
| 148 | ### 参数化Build | ||
| 149 | |||
| 150 | Jenkins中已配置参数化执行,选择不同的环境: | ||
| 151 | |||
| 152 | 1. 进入Jenkins Job页面 | ||
| 153 | 2. 点击 **Build with Parameters** | ||
| 154 | 3. 选择 **DEPLOY_ENV**: test / uat / prod | ||
| 155 | 4. 点击 **Build** 开始执行 | ||
| 156 | |||
| 157 | --- | ||
| 158 | |||
| 159 | ## 🆘 常见问题 | ||
| 160 | |||
| 161 | ### Q1: 所有用例都被跳过了 | ||
| 162 | |||
| 163 | **原因**:DEPLOY_ENV 未设置或 env_scope 不匹配 | ||
| 164 | |||
| 165 | **解决**: | ||
| 166 | ```bash | ||
| 167 | export DEPLOY_ENV=test | ||
| 168 | python3 api_test.py | ||
| 169 | ``` | ||
| 170 | |||
| 171 | ### Q2: 如何在生产环境禁用某些用例 | ||
| 172 | |||
| 173 | 设置 `env_scope = test,uat`(只在测试和UAT执行) | ||
| 174 | |||
| 175 | ### Q3: 支持哪些 env_scope 值的组合 | ||
| 176 | |||
| 177 | 支持:all / test / uat / prod 及其组合(用逗号分隔) | ||
| 178 | |||
| 179 | ### Q4: is_run 和 env_scope 的关系 | ||
| 180 | |||
| 181 | `is_run=1` 时总是跳过;`is_run=0` 时检查 `env_scope` | ||
| 182 | |||
| 183 | ### Q5: 如何提取变量供后续用例使用 | ||
| 184 | |||
| 185 | 在 `extract_vars` 列中定义:`user_id=data.user_id` | ||
| 186 | |||
| 187 | --- | ||
| 188 | |||
| 189 | ## 🎯 最佳实践 | ||
| 190 | |||
| 191 | ### ✅ 推荐做法 | ||
| 192 | |||
| 193 | 1. **明确标记env_scope**:all / test / uat / prod(避免留空) | ||
| 194 | 2. **生产环境谨慎操作**:危险操作设置为 `test,uat` | ||
| 195 | 3. **定期审查配置**:检查过期的env_scope设置 | ||
| 196 | 4. **使用关系运算符**:灵活的验证而非硬编码 | ||
| 197 | |||
| 198 | ### ❌ 避免做法 | ||
| 199 | |||
| 200 | 1. **混乱地使用 is_run 和 env_scope** | ||
| 201 | 2. **在生产执行数据修改操作** | ||
| 202 | 3. **忘记为新增用例设置 env_scope** | ||
| 203 | 4. **大小写混乱的值** | ||
| 204 | |||
| 205 | --- | ||
| 206 | |||
| 207 | **祝你使用愉快!** 🎉 |
api_cases.xlsx
0 → 100644
No preview for this file type
api_test.py
0 → 100644
| 1 | import requests | ||
| 2 | import openpyxl | ||
| 3 | import json | ||
| 4 | from datetime import datetime | ||
| 5 | import time | ||
| 6 | import os | ||
| 7 | import re | ||
| 8 | import importlib.util | ||
| 9 | from jsonpath_ng import parse | ||
| 10 | import ast | ||
| 11 | |||
| 12 | def get_deploy_env(): | ||
| 13 | """获取当前部署环境,默认为test""" | ||
| 14 | return os.getenv('DEPLOY_ENV', 'test') | ||
| 15 | |||
| 16 | def should_run_in_env(env_scope, current_env): | ||
| 17 | """ | ||
| 18 | 判断用例是否应在当前环境执行 | ||
| 19 | |||
| 20 | env_scope 格式: | ||
| 21 | - 空 或 'all' → 所有环境执行 | ||
| 22 | - 'test' → 仅测试环境 | ||
| 23 | - 'uat' → 仅UAT环境 | ||
| 24 | - 'prod' → 仅生产环境 | ||
| 25 | - 'test,uat' → 测试和UAT环境 | ||
| 26 | - 'test,prod' → 测试和生产环境 | ||
| 27 | """ | ||
| 28 | if not env_scope or env_scope.strip() == '': | ||
| 29 | return True | ||
| 30 | if env_scope.strip().lower() == 'all': | ||
| 31 | return True | ||
| 32 | allowed_envs = [e.strip() for e in env_scope.split(',')] | ||
| 33 | return current_env in allowed_envs | ||
| 34 | |||
| 35 | def load_env_vars(): | ||
| 36 | env_vars = {} | ||
| 37 | # 优先读取env_config.py | ||
| 38 | if os.path.exists('env_config.py'): | ||
| 39 | spec = importlib.util.spec_from_file_location('env_config', 'env_config.py') | ||
| 40 | env = importlib.util.module_from_spec(spec) | ||
| 41 | spec.loader.exec_module(env) | ||
| 42 | for k in dir(env): | ||
| 43 | if not k.startswith('__'): | ||
| 44 | env_vars[k] = getattr(env, k) | ||
| 45 | elif os.path.exists('env.txt'): | ||
| 46 | with open('env.txt', encoding='utf-8') as f: | ||
| 47 | for line in f: | ||
| 48 | if '=' in line: | ||
| 49 | k, v = line.strip().split('=', 1) | ||
| 50 | env_vars[k.strip()] = v.strip() | ||
| 51 | return env_vars | ||
| 52 | |||
| 53 | def replace_vars(s, env_vars): | ||
| 54 | if not isinstance(s, str): | ||
| 55 | return s | ||
| 56 | prev = None | ||
| 57 | curr = s | ||
| 58 | # 最多递归5次,防止死循环 | ||
| 59 | for _ in range(5): | ||
| 60 | prev = curr | ||
| 61 | def repl(match): | ||
| 62 | var = match.group(1) | ||
| 63 | return str(env_vars.get(var, match.group(0))) | ||
| 64 | curr = re.sub(r'\{(\w+)\}', repl, prev) | ||
| 65 | if curr == prev: | ||
| 66 | break | ||
| 67 | return curr | ||
| 68 | |||
| 69 | def run_api_test(cases): | ||
| 70 | env_vars = load_env_vars() # 只初始化一次,所有用例共享 | ||
| 71 | current_env = get_deploy_env() # 获取当前环境 | ||
| 72 | results = [] | ||
| 73 | for case in cases: | ||
| 74 | # 新增:支持is_run字段,等于1跳过,等于0才执行 | ||
| 75 | is_run = str(case.get('is_run', '0')).strip() | ||
| 76 | if is_run == '1' or is_run == 1: | ||
| 77 | continue | ||
| 78 | |||
| 79 | # 新增:根据env_scope和当前环境判断是否执行 | ||
| 80 | env_scope = str(case.get('env_scope', '')).strip() | ||
| 81 | if not should_run_in_env(env_scope, current_env): | ||
| 82 | print(f"⏭️ 跳过用例 case_id: {case.get('case_id')},原因:不在{current_env}环境执行范围内 (env_scope: {env_scope})") | ||
| 83 | continue | ||
| 84 | # 变量替换,优先用ip_host+path拼接url | ||
| 85 | ip_host = replace_vars(case.get('ip_host', ''), env_vars) | ||
| 86 | path = replace_vars(case.get('path', ''), env_vars) | ||
| 87 | url = '' | ||
| 88 | url_error = '' | ||
| 89 | if ip_host and path: | ||
| 90 | if not ip_host.startswith('http://') and not ip_host.startswith('https://'): | ||
| 91 | ip_host = 'https://' + ip_host | ||
| 92 | url = ip_host.rstrip('/') + '/' + path.lstrip('/') | ||
| 93 | elif case.get('url', ''): | ||
| 94 | url = replace_vars(case.get('url', ''), env_vars) | ||
| 95 | else: | ||
| 96 | url_error = 'ip_host和path或url参数缺失' | ||
| 97 | method = case.get('method', '').upper() | ||
| 98 | params_str = replace_vars(case.get('params', ''), env_vars) | ||
| 99 | headers_str = replace_vars(case.get('headers', ''), env_vars) | ||
| 100 | print(f"case_id: {case.get('case_id')}, headers_str: {headers_str}, env_vars: {env_vars}") | ||
| 101 | expected_code = int(case.get('expected_code', 0) or 0) | ||
| 102 | expected_msg = case.get('expected_msg', '') if 'expected_msg' in case else '' | ||
| 103 | expected_response = replace_vars(case.get('expected_response', ''), env_vars) | ||
| 104 | api_name = case.get('api_name', '') | ||
| 105 | |||
| 106 | # 拼接后再统一加前缀,确保url一定带https:// | ||
| 107 | if url and not (url.startswith('http://') or url.startswith('https://')): | ||
| 108 | url = 'https://' + url.lstrip('/') | ||
| 109 | |||
| 110 | # 统一先定义result,保证后续except分支可用 | ||
| 111 | result = { | ||
| 112 | "case_id": case.get('case_id', ''), | ||
| 113 | "api_name": api_name, | ||
| 114 | "is_run": is_run, | ||
| 115 | "url": url, | ||
| 116 | "method": method or '参数不完整', | ||
| 117 | "params": params_str, | ||
| 118 | "headers": headers_str, | ||
| 119 | "expected_code": expected_code, | ||
| 120 | "expected_msg": expected_msg, | ||
| 121 | "expected_response": expected_response, | ||
| 122 | "actual_code": 'None', | ||
| 123 | "actual_response": 'None', | ||
| 124 | "code_pass": False, | ||
| 125 | "msg_pass": False, | ||
| 126 | "response_pass": False, | ||
| 127 | "test_result": "失败", | ||
| 128 | "error": url_error or "url或method参数缺失", | ||
| 129 | "duration_ms": None | ||
| 130 | } | ||
| 131 | |||
| 132 | # 调试输出每条用例的关键信息 | ||
| 133 | print(f"case_id: {case.get('case_id')}, is_run: {is_run}, url: {url}, method: {method}") | ||
| 134 | print('原始url:', case.get('url'), '替换后url:', url) | ||
| 135 | |||
| 136 | # 参数不完整,直接标记失败 | ||
| 137 | if url_error or not url or not method: | ||
| 138 | print(f"参数校验未通过,case_id: {case.get('case_id')}, url: {url}, method: {method}, url_error: {url_error}") | ||
| 139 | results.append(result) | ||
| 140 | continue | ||
| 141 | |||
| 142 | # 健壮性增强:params、headers解析异常时直接标记失败 | ||
| 143 | try: | ||
| 144 | params = json.loads(params_str) if params_str else {} | ||
| 145 | except Exception as e: | ||
| 146 | result["error"] = f"params字段不是合法JSON: {e}" | ||
| 147 | result["test_result"] = "失败" | ||
| 148 | results.append(result) | ||
| 149 | continue | ||
| 150 | try: | ||
| 151 | headers = json.loads(headers_str) if headers_str else {} | ||
| 152 | except Exception as e: | ||
| 153 | result["error"] = f"headers字段不是合法JSON: {e}" | ||
| 154 | result["test_result"] = "失败" | ||
| 155 | results.append(result) | ||
| 156 | continue | ||
| 157 | |||
| 158 | try: | ||
| 159 | start_time = time.time() | ||
| 160 | timeout_val = 60 # 超时时间60秒 | ||
| 161 | if method == 'GET': | ||
| 162 | resp = requests.get(url, params=params, headers=headers, timeout=timeout_val) | ||
| 163 | elif method == 'POST': | ||
| 164 | resp = requests.post(url, json=params, headers=headers, timeout=timeout_val) | ||
| 165 | elif method == 'PUT': | ||
| 166 | resp = requests.put(url, json=params, headers=headers, timeout=timeout_val) | ||
| 167 | elif method == 'DELETE': | ||
| 168 | resp = requests.delete(url, json=params, headers=headers, timeout=timeout_val) | ||
| 169 | else: | ||
| 170 | result["error"] = f"不支持的请求方法: {method}" | ||
| 171 | result["test_result"] = "失败" | ||
| 172 | results.append(result) | ||
| 173 | continue | ||
| 174 | end_time = time.time() | ||
| 175 | duration_ms = int((end_time - start_time) * 1000) | ||
| 176 | result["duration_ms"] = duration_ms | ||
| 177 | |||
| 178 | # 修正resp_json解析 | ||
| 179 | try: | ||
| 180 | resp_json = resp.json() | ||
| 181 | except Exception: | ||
| 182 | try: | ||
| 183 | resp_json = ast.literal_eval(resp.text) | ||
| 184 | except Exception: | ||
| 185 | resp_json = resp.text | ||
| 186 | result["actual_code"] = resp.status_code | ||
| 187 | result["actual_response"] = str(resp_json) | ||
| 188 | result["code_pass"] = resp.status_code == expected_code | ||
| 189 | result["msg_pass"] = expected_msg in str(resp_json) if expected_msg else None | ||
| 190 | |||
| 191 | # 部分字段校验返回内容 | ||
| 192 | if expected_response: | ||
| 193 | try: | ||
| 194 | expected_json = json.loads(expected_response) | ||
| 195 | actual_json = resp_json if isinstance(resp_json, dict) else json.loads(resp_json) | ||
| 196 | |||
| 197 | # 运算符检查函数 | ||
| 198 | def check_operator(expected, actual): | ||
| 199 | """检查是否满足各种关系运算符条件""" | ||
| 200 | if not isinstance(expected, dict): | ||
| 201 | return False | ||
| 202 | |||
| 203 | # 检查是否为运算符字典(至少有一个$前缀的key) | ||
| 204 | has_operator = any(k.startswith('$') for k in expected.keys()) | ||
| 205 | if not has_operator: | ||
| 206 | return False | ||
| 207 | |||
| 208 | for op, value in expected.items(): | ||
| 209 | if not op.startswith('$'): | ||
| 210 | continue | ||
| 211 | |||
| 212 | try: | ||
| 213 | if op == '$ne': # 不等于 | ||
| 214 | if actual == value: | ||
| 215 | return False | ||
| 216 | elif op == '$eq': # 等于 | ||
| 217 | if actual != value: | ||
| 218 | return False | ||
| 219 | elif op == '$gt': # 大于 | ||
| 220 | if not (actual > value): | ||
| 221 | return False | ||
| 222 | elif op == '$gte': # 大于等于 | ||
| 223 | if not (actual >= value): | ||
| 224 | return False | ||
| 225 | elif op == '$lt': # 小于 | ||
| 226 | if not (actual < value): | ||
| 227 | return False | ||
| 228 | elif op == '$lte': # 小于等于 | ||
| 229 | if not (actual <= value): | ||
| 230 | return False | ||
| 231 | elif op == '$in': # 包含(值在列表中) | ||
| 232 | if actual not in value: | ||
| 233 | return False | ||
| 234 | elif op == '$nin': # 不包含(值不在列表中) | ||
| 235 | if actual in value: | ||
| 236 | return False | ||
| 237 | elif op == '$regex': # 正则表达匹配 | ||
| 238 | if not re.search(value, str(actual)): | ||
| 239 | return False | ||
| 240 | elif op == '$contains': # 字符串包含 | ||
| 241 | if value not in str(actual): | ||
| 242 | return False | ||
| 243 | elif op == '$not_contains': # 字符串不包含 | ||
| 244 | if value in str(actual): | ||
| 245 | return False | ||
| 246 | elif op == '$range': # 范围判断 [min, max] | ||
| 247 | if not isinstance(value, list) or len(value) != 2: | ||
| 248 | return False | ||
| 249 | min_val, max_val = value | ||
| 250 | if not (min_val <= actual <= max_val): | ||
| 251 | return False | ||
| 252 | else: | ||
| 253 | # 未知运算符视为失败 | ||
| 254 | return False | ||
| 255 | except (TypeError, ValueError): | ||
| 256 | # 类型不匹配或运算失败 | ||
| 257 | return False | ||
| 258 | |||
| 259 | return True | ||
| 260 | |||
| 261 | # 优化check_partial,支持["__NOT_NULL__"]和运算符 | ||
| 262 | def check_partial(expected, actual): | ||
| 263 | if expected == "__NOT_NULL__": | ||
| 264 | return actual not in [None, "", [], {}] | ||
| 265 | if isinstance(expected, list): | ||
| 266 | # 只要期望是["__NOT_NULL__"],实际list非空即可 | ||
| 267 | if len(expected) == 1 and expected[0] == "__NOT_NULL__": | ||
| 268 | return isinstance(actual, list) and len(actual) > 0 | ||
| 269 | # 否则递归比对每个元素 | ||
| 270 | if not isinstance(actual, list) or len(expected) != len(actual): | ||
| 271 | return False | ||
| 272 | return all(check_partial(e, a) for e, a in zip(expected, actual)) | ||
| 273 | if isinstance(expected, dict) and isinstance(actual, dict): | ||
| 274 | # 检查是否为运算符字典 | ||
| 275 | has_operator = any(k.startswith('$') for k in expected.keys()) | ||
| 276 | if has_operator: | ||
| 277 | return check_operator(expected, actual) | ||
| 278 | # 普通字典递归检查 | ||
| 279 | for k, v in expected.items(): | ||
| 280 | if k not in actual or not check_partial(v, actual[k]): | ||
| 281 | return False | ||
| 282 | return True | ||
| 283 | # 如果期望是运算符字典但actual不是字典,尝试直接用operator检查 | ||
| 284 | if isinstance(expected, dict) and any(k.startswith('$') for k in expected.keys()): | ||
| 285 | return check_operator(expected, actual) | ||
| 286 | return expected == actual | ||
| 287 | result["response_pass"] = check_partial(expected_json, actual_json) | ||
| 288 | except Exception as e: | ||
| 289 | result["response_pass"] = False | ||
| 290 | result["error"] += f" | 返回内容或期望内容不是合法JSON: {e}" | ||
| 291 | else: | ||
| 292 | result["response_pass"] = None | ||
| 293 | |||
| 294 | # 执行后提取变量(支持多个,分号或换行分隔,支持split/regex表达式) | ||
| 295 | extract_vars = case.get('extract_vars', '') | ||
| 296 | if extract_vars and result['actual_response']: | ||
| 297 | try: | ||
| 298 | actual_json = None | ||
| 299 | try: | ||
| 300 | actual_json = json.loads(result['actual_response']) | ||
| 301 | except Exception: | ||
| 302 | try: | ||
| 303 | actual_json = ast.literal_eval(result['actual_response']) | ||
| 304 | except Exception: | ||
| 305 | pass | ||
| 306 | if actual_json: | ||
| 307 | for line in extract_vars.replace('\n', ';').split(';'): | ||
| 308 | if '=' in line: | ||
| 309 | var, path_expr = line.split('=', 1) | ||
| 310 | var = var.strip() | ||
| 311 | path_expr = path_expr.strip() | ||
| 312 | if not var or not path_expr: | ||
| 313 | continue | ||
| 314 | # 支持表达式:jsonpath|split('?')[0] 或 |regex(...) | ||
| 315 | if '|' in path_expr: | ||
| 316 | path, expr = path_expr.split('|', 1) | ||
| 317 | path = path.strip() | ||
| 318 | expr = expr.strip() | ||
| 319 | else: | ||
| 320 | path, expr = path_expr, None | ||
| 321 | matches = parse(path).find(actual_json) | ||
| 322 | if matches: | ||
| 323 | val = matches[0].value | ||
| 324 | # split表达式 | ||
| 325 | if expr and expr.startswith('split('): | ||
| 326 | import re | ||
| 327 | m = re.match(r"split\(['\"](.*?)['\"]\)\[(\d+)\]", expr) | ||
| 328 | if m: | ||
| 329 | sep, idx = m.group(1), int(m.group(2)) | ||
| 330 | val = str(val).split(sep)[idx] if sep in str(val) else str(val) | ||
| 331 | # regex表达式 | ||
| 332 | elif expr and expr.startswith('regex('): | ||
| 333 | import re | ||
| 334 | m = re.match(r"regex\((.*)\)", expr) | ||
| 335 | if m: | ||
| 336 | pattern = m.group(1) | ||
| 337 | reg = re.compile(pattern) | ||
| 338 | reg_match = reg.search(str(val)) | ||
| 339 | if reg_match: | ||
| 340 | val = reg_match.group(1) if reg_match.groups() else reg_match.group(0) | ||
| 341 | env_vars[var] = val | ||
| 342 | except Exception as e: | ||
| 343 | result['error'] += f' | 提取变量失败: {e}' | ||
| 344 | |||
| 345 | # 总体通过条件和error信息优化 | ||
| 346 | error_msgs = [] | ||
| 347 | if not result["code_pass"]: | ||
| 348 | error_msgs.append(f"实际返回码与期望不符(expected:{expected_code}, actual:{result['actual_code']})") | ||
| 349 | if expected_response and result["response_pass"] is False: | ||
| 350 | error_msgs.append("实际返回内容与期望内容不符") | ||
| 351 | if result["code_pass"] and (result["response_pass"] in [True, None]): | ||
| 352 | result["test_result"] = "通过" | ||
| 353 | result["error"] = "" # 用例通过时error置空 | ||
| 354 | else: | ||
| 355 | result["test_result"] = "失败" | ||
| 356 | # 只保留本次判定的error信息,前面初始化的url或method参数缺失等只在参数校验未通过时保留 | ||
| 357 | if error_msgs: | ||
| 358 | result["error"] = " | ".join(error_msgs) | ||
| 359 | results.append(result) | ||
| 360 | except requests.Timeout: | ||
| 361 | result["error"] = "执行超时" | ||
| 362 | result["test_result"] = "失败" | ||
| 363 | result["duration_ms"] = 60000 | ||
| 364 | results.append(result) | ||
| 365 | continue | ||
| 366 | except Exception as e: | ||
| 367 | result["error"] = str(e) | ||
| 368 | result["test_result"] = "异常" | ||
| 369 | results.append(result) | ||
| 370 | continue | ||
| 371 | return results | ||
| 372 | |||
| 373 | def write_report_to_excel(results, report_path): | ||
| 374 | from openpyxl.styles import Font, Alignment, PatternFill | ||
| 375 | wb = openpyxl.Workbook() | ||
| 376 | ws = wb.active | ||
| 377 | ws.title = "测试报告" | ||
| 378 | headers = [ | ||
| 379 | "case_id", "api_name", "is_run", "method", "url", "params", "headers", "expected_code", "expected_msg", "expected_response", | ||
| 380 | "actual_code", "actual_response", "code_pass", "msg_pass", "response_pass", "test_result", "duration", "error" | ||
| 381 | ] | ||
| 382 | ws.append(headers) | ||
| 383 | # 设置表头样式 | ||
| 384 | for col in range(1, len(headers)+1): | ||
| 385 | cell = ws.cell(row=1, column=col) | ||
| 386 | cell.font = Font(bold=True, color="FFFFFF") | ||
| 387 | cell.fill = PatternFill("solid", fgColor="2D3E50") | ||
| 388 | cell.alignment = Alignment(horizontal="center", vertical="center") | ||
| 389 | # 写入内容 | ||
| 390 | for i, r in enumerate(results, 2): | ||
| 391 | row = [r.get(h, "") for h in headers] | ||
| 392 | ws.append(row) | ||
| 393 | # 自动换行 | ||
| 394 | for col in range(1, len(headers)+1): | ||
| 395 | ws.cell(row=i, column=col).alignment = Alignment(wrap_text=True, vertical="center") | ||
| 396 | # 失败用例整行标红 | ||
| 397 | if r.get('test_result') == '失败': | ||
| 398 | for col in range(1, len(headers)+1): | ||
| 399 | ws.cell(row=i, column=col).fill = PatternFill("solid", fgColor="FFF0F0") | ||
| 400 | wb.save(report_path) | ||
| 401 | print(f"测试报告已生成:{report_path}") | ||
| 402 | |||
| 403 | def write_report_to_html(results, html_path): | ||
| 404 | headers = [ | ||
| 405 | "序号", "接口名称", "method", "url", "headers", "params", "expected_code", "expected_response", "actual_code", "actual_response", "duration", "error" | ||
| 406 | ] | ||
| 407 | total = len(results) | ||
| 408 | passed = sum(1 for r in results if r.get('test_result') == '通过') | ||
| 409 | failed = sum(1 for r in results if r.get('test_result') == '失败' or r.get('test_result') == '异常') | ||
| 410 | total_duration = sum(float(r.get('duration_ms', 0) or 0) for r in results if r.get('duration_ms') is not None) | ||
| 411 | total_seconds = total_duration / 1000 | ||
| 412 | if total_seconds < 60: | ||
| 413 | duration_str = f"{total_seconds:.2f}秒" | ||
| 414 | else: | ||
| 415 | duration_str = f"{total_seconds/60:.2f}分钟" | ||
| 416 | over_1s_count = sum(1 for r in results if r.get('duration_ms') is not None and float(r.get('duration_ms') or 0) > 1000) | ||
| 417 | over_3s_count = sum(1 for r in results if r.get('duration_ms') is not None and float(r.get('duration_ms') or 0) > 3000) | ||
| 418 | task_result = "执行成功" if failed == 0 else "执行失败" | ||
| 419 | task_color = "green" if failed == 0 else "red" | ||
| 420 | html = [ | ||
| 421 | '<html><head><meta charset="utf-8"><title>接口自动化测试报告</title>', | ||
| 422 | '<style>\nbody{font-family:Segoe UI,Arial,sans-serif;background:#f8f9fa;}\nh2{text-align:center;margin-top:24px;}\n.summary{text-align:center;margin-bottom:18px;font-size:17px;}\ntable{border-collapse:collapse;margin:auto;background:#fff;box-shadow:0 2px 8px #eee;width:98vw;table-layout:fixed;}\nth,td{border:1px solid #ccc;padding:6px 8px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px;position:relative;}\nth:nth-child(1),td:nth-child(1){width:1.5vw;min-width:24px;}\nth:nth-child(7),td:nth-child(7){width:2vw;min-width:32px;}\nth:nth-child(9),td:nth-child(9){width:2vw;min-width:32px;}\nth:nth-child(11),td:nth-child(11){width:2vw;min-width:32px;}\nth:nth-child(8),td:nth-child(8){width:15vw;min-width:180px;}\nth:nth-child(10),td:nth-child(10){width:15vw;min-width:180px;}\nth:nth-child(2),td:nth-child(2){width:8vw;min-width:90px;}\ntd.api-name-cell,td.url-cell,td.headers-cell,td.error-cell{white-space:normal !important;word-break:break-all;overflow:auto;max-height:8em;}\ntd.scroll-cell > div, td.url-cell > div, td.headers-cell > div, td.api-name-cell > div {max-height:8em;overflow:auto;white-space:pre-wrap;word-break:break-all;}\ntd.scroll-cell{max-height:8em;min-width:120px;max-width:600px;overflow:auto;white-space:pre-wrap;word-break:break-all;position:relative;}\nbutton.copy-btn{margin-left:6px;padding:2px 10px;font-size:12px;cursor:pointer;background:#409eff;border:none;border-radius:3px;color:#fff;transition:background 0.2s;}\nbutton.copy-btn:hover{background:#66b1ff;}\ntr.failrow{background:#ffeaea !important;}\n.resizer{position:absolute;right:0;top:0;width:6px;height:100%;cursor:col-resize;user-select:none;z-index:2;}\n.duration-warn{color:#e74c3c;}\n.duration-strong{color:#e74c3c;font-weight:bold;}\n.filter-bar{margin:10px 0 0 0;text-align:left;width:98vw;max-width:1800px;}\n.filter-btn{display:inline-block;margin:0 8px 8px 0;padding:4px 18px;font-size:13px;border:1px solid #409eff;background:#fff;color:#409eff;border-radius:4px;cursor:pointer;transition:all 0.2s;outline:none;}\n.filter-btn.active,.filter-btn:hover{background:#409eff;color:#fff;}\n.back-to-top-btn{position:fixed;right:32px;bottom:32px;z-index:9999;width:48px;height:48px;border-radius:50%;background:rgba(200,200,200,0.45);color:#333;display:flex;align-items:center;justify-content:center;font-size:22px;box-shadow:0 2px 8px #ccc;cursor:pointer;transition:background 0.2s,border 0.2s;border:1px solid #e0e0e0;opacity:0.85;}\n.back-to-top-btn:hover{background:rgba(180,180,180,0.7);}\n</style>', | ||
| 423 | '<script>\nfunction makeResizable(tableId){\n var table = document.getElementById(tableId);\n if(!table) return;\n var ths = table.querySelectorAll("th");\n ths.forEach(function(th,i){\n var resizer = document.createElement("div");\n resizer.className = "resizer";\n th.appendChild(resizer);\n resizer.addEventListener("mousedown", function(e){\n var startX = e.pageX;\n var startWidth = th.offsetWidth;\n function onMove(e2){\n var newWidth = startWidth + (e2.pageX - startX);\n th.style.width = newWidth+"px";\n }\n function onUp(){\n document.removeEventListener("mousemove",onMove);\n document.removeEventListener("mouseup",onUp);\n }\n document.addEventListener("mousemove",onMove);\n document.addEventListener("mouseup",onUp);\n });\n });\n}\nwindow.onload=function(){makeResizable("report-table");filterTable("all");};\nfunction copyText(id){var t=document.getElementById(id).getAttribute("data-full");navigator.clipboard.writeText(t);alert("已复制全部内容");}\nfunction filterTable(type){\n var trs=document.querySelectorAll("#report-table tbody tr");\n for(var i=0;i<trs.length;i++){\n var tr=trs[i];\n var fail=tr.getAttribute("data-fail");\n var dur=Number(tr.getAttribute("data-duration"));\n if(type=="all"){tr.style.display="";}\n else if(type=="fail"){tr.style.display=fail=="1"?"":"none";}\n else if(type=="d1"){tr.style.display=dur>1000?"":"none";}\n else if(type=="d3"){tr.style.display=dur>3000?"":"none";}\n }\n var btns=document.querySelectorAll(".filter-btn");\n for(var j=0;j<btns.length;j++){btns[j].classList.remove("active");}\n document.getElementById("btn-"+type).classList.add("active");\n}\nwindow.onload=function(){filterTable("all");};\nfunction scrollToTop(){window.scrollTo({top:0,behavior:"smooth"});}\n</script>', | ||
| 424 | '</head><body>', | ||
| 425 | '<h2 style="text-align:center;margin-top:24px;margin-bottom:10px;font-size:2.2em;">接口自动化测试报告</h2>', | ||
| 426 | f'<div class="summary"><b style="color:{task_color}">{task_result}</b>,共执行用例:<b>{total}</b>,成功:<b style="color:green">{passed}</b>,失败:<b style="color:red">{failed}</b></div>', | ||
| 427 | f'<div class="summary">执行用时:<b>{duration_str}</b>,响应请求大于1S的接口有:<b style="color:red">{over_1s_count}</b>,大于3S的接口有:<b style="color:red">{over_3s_count}</b></div>', | ||
| 428 | '<div class="filter-bar"><button class="filter-btn active" id="btn-all" onclick="filterTable(\'all\')">全部</button><button class="filter-btn" id="btn-fail" onclick="filterTable(\'fail\')">失败用例</button><button class="filter-btn" id="btn-d1" onclick="filterTable(\'d1\')">请求大于1S</button><button class="filter-btn" id="btn-d3" onclick="filterTable(\'d3\')">请求大于3S</button></div>', | ||
| 429 | '<table id="report-table">' | ||
| 430 | ] | ||
| 431 | html.append('<thead>') | ||
| 432 | html.append('<tr>' + ''.join(f'<th>{h}</th>' for h in headers) + '</tr>') | ||
| 433 | html.append('</thead>') | ||
| 434 | html.append('<tbody>') | ||
| 435 | for idx, r in enumerate(results, 1): | ||
| 436 | is_fail = r.get('test_result') == '失败' or r.get('test_result') == '异常' | ||
| 437 | tr_class = 'failrow' if is_fail else '' | ||
| 438 | duration_val = r.get('duration', r.get('duration_ms', 0)) | ||
| 439 | try: | ||
| 440 | duration_val = int(duration_val) | ||
| 441 | except: | ||
| 442 | duration_val = 0 | ||
| 443 | data_fail = '1' if is_fail else '0' | ||
| 444 | row = [f'<td>{idx}</td>'] | ||
| 445 | row.append(f'<td class="api-name-cell"><div>{r.get("api_name", "")}</div></td>') | ||
| 446 | row.append(f'<td>{r.get("method", "")}</td>') | ||
| 447 | row.append(f'<td class="url-cell"><div>{r.get("url", "")}</div></td>') | ||
| 448 | row.append(f'<td class="headers-cell"><div>{r.get("headers", "")}</div></td>') | ||
| 449 | row.append(f'<td class="scroll-cell"><div>{r.get("params", "")}</div></td>') | ||
| 450 | row.append(f'<td>{r.get("expected_code", "")}</td>') | ||
| 451 | row.append(f'<td class="scroll-cell"><div>{r.get("expected_response", "")}</div></td>') | ||
| 452 | row.append(f'<td>{r.get("actual_code", "")}</td>') | ||
| 453 | row.append(f'<td class="scroll-cell"><div>{r.get("actual_response", "")}</div></td>') | ||
| 454 | duration_class = "duration-strong" if duration_val > 3000 else ("duration-warn" if duration_val > 1000 else "") | ||
| 455 | row.append(f'<td class="{duration_class}">{duration_val}</td>') | ||
| 456 | row.append(f'<td class="error-cell"><div>{r.get("error", "")}</div></td>') | ||
| 457 | html.append(f'<tr class="{tr_class}" data-fail="{data_fail}" data-duration="{duration_val}">' + ''.join(row) + '</tr>') | ||
| 458 | html.append('</tbody>') | ||
| 459 | html.append('</table>') | ||
| 460 | html.append('<div class="back-to-top-btn" onclick="scrollToTop()" title="回到顶部">↑</div>') | ||
| 461 | html.append('</body></html>') | ||
| 462 | with open(html_path, 'w', encoding='utf-8') as f: | ||
| 463 | f.write('\n'.join(html)) | ||
| 464 | print(f"HTML测试报告已生成:{html_path}") | ||
| 465 | |||
| 466 | def read_cases_from_excel(file_path, sheet_name='Sheet1'): | ||
| 467 | wb = openpyxl.load_workbook(file_path) | ||
| 468 | ws = wb[sheet_name] | ||
| 469 | cases = [] | ||
| 470 | headers = [cell.value for cell in ws[1]] | ||
| 471 | for row in ws.iter_rows(min_row=2, values_only=True): | ||
| 472 | case = dict(zip(headers, row)) | ||
| 473 | cases.append(case) | ||
| 474 | return cases | ||
| 475 | |||
| 476 | if __name__ == "__main__": | ||
| 477 | import sys | ||
| 478 | |||
| 479 | # 删除旧的测试报告 | ||
| 480 | for f in os.listdir('.'): | ||
| 481 | if (f.startswith('api_test_report_') and f.endswith('.xlsx')) or (f.startswith('api_test_report_') and f.endswith('.html')): | ||
| 482 | try: | ||
| 483 | os.remove(f) | ||
| 484 | except Exception: | ||
| 485 | pass | ||
| 486 | |||
| 487 | # 支持多个Excel文件 | ||
| 488 | excel_files = [f for f in os.listdir('.') if f.startswith('api_cases') and f.endswith('.xlsx')] | ||
| 489 | all_passed = True # 标记所有用例是否都通过 | ||
| 490 | |||
| 491 | for excel_path in excel_files: | ||
| 492 | deploy_env = get_deploy_env() | ||
| 493 | report_path = f"api_test_report_{excel_path.replace('.xlsx', '')}_{deploy_env}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx" | ||
| 494 | html_path = report_path.replace('.xlsx', '.html') | ||
| 495 | cases = read_cases_from_excel(excel_path) | ||
| 496 | print(f'读取到的用例({excel_path}):', cases) | ||
| 497 | results = run_api_test(cases) | ||
| 498 | print(f'最终results({excel_path}):', results) | ||
| 499 | write_report_to_excel(results, report_path) | ||
| 500 | write_report_to_html(results, html_path) | ||
| 501 | |||
| 502 | # 检查是否有失败用例 | ||
| 503 | failed_count = sum(1 for r in results if r.get('test_result') in ['失败', '异常']) | ||
| 504 | if failed_count > 0: | ||
| 505 | all_passed = False | ||
| 506 | print(f"\n⚠️ {excel_path}: 有 {failed_count} 个用例执行失败") | ||
| 507 | |||
| 508 | # 根据结果返回退出码 | ||
| 509 | if all_passed: | ||
| 510 | print("\n✅ 所有测试用例运行成功!") | ||
| 511 | sys.exit(0) # Jenkins: 执行成功 | ||
| 512 | else: | ||
| 513 | print("\n❌ 有测试用例执行失败,请查看报告详情!") | ||
| 514 | sys.exit(1) # Jenkins: 执行失败 |
docker-compose.yml
0 → 100644
| 1 | version: '3.8' | ||
| 2 | |||
| 3 | services: | ||
| 4 | api-test-runner: | ||
| 5 | build: | ||
| 6 | context: . | ||
| 7 | dockerfile: Dockerfile | ||
| 8 | container_name: api-test-${DEPLOY_ENV:-test} | ||
| 9 | environment: | ||
| 10 | - DEPLOY_ENV=${DEPLOY_ENV:-test} | ||
| 11 | - IP_HOST=${IP_HOST:-} | ||
| 12 | - PYTHONUNBUFFERED=1 | ||
| 13 | volumes: | ||
| 14 | - ./api_cases.xlsx:/app/api_cases.xlsx:ro | ||
| 15 | - ./env_config.py:/app/env_config.py:ro | ||
| 16 | - ./reports:/app | ||
| 17 | networks: | ||
| 18 | - api-test-network | ||
| 19 | deploy: | ||
| 20 | resources: | ||
| 21 | limits: | ||
| 22 | cpus: '1' | ||
| 23 | memory: 512M | ||
| 24 | reservations: | ||
| 25 | cpus: '0.5' | ||
| 26 | memory: 256M | ||
| 27 | restart: no | ||
| 28 | logging: | ||
| 29 | driver: "json-file" | ||
| 30 | options: | ||
| 31 | max-size: "10m" | ||
| 32 | max-file: "3" | ||
| 33 | |||
| 34 | networks: | ||
| 35 | api-test-network: | ||
| 36 | driver: bridge |
env_config.py
0 → 100644
| 1 | import os | ||
| 2 | import random | ||
| 3 | import string | ||
| 4 | from datetime import datetime | ||
| 5 | |||
| 6 | # 多环境IP配置 | ||
| 7 | DEPLOY_ENV = os.getenv('DEPLOY_ENV', 'test') | ||
| 8 | IP_HOST_MAP = { | ||
| 9 | 'test': 'ai-test.hikoon.com', | ||
| 10 | 'uat': 'ai-uat.hikoon.com', | ||
| 11 | 'prod': 'api.hikoon.com' | ||
| 12 | } | ||
| 13 | IP_HOST = os.getenv('IP_HOST', IP_HOST_MAP.get(DEPLOY_ENV, 'ai-test.hikoon.com')) | ||
| 14 | |||
| 15 | # 当前日期(格式:YY-MM-DD) | ||
| 16 | CURRENT_DATE = datetime.now().strftime('%y-%m-%d') | ||
| 17 | |||
| 18 | def gen_email(): | ||
| 19 | prefix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=8)) | ||
| 20 | domain = random.choice(['@qq.com', '@163.com', '@gmail.com', '@test.com']) | ||
| 21 | return prefix + domain | ||
| 22 | |||
| 23 | RANDOM_EMAIL = gen_email() |
requirements.txt
0 → 100644
-
Please register or sign in to post a comment