Commit 3d6805eb 3d6805ebda4475c4fa68e849e073070c90739446 by chenjing

init commit:API自动化测试

0 parents
.git
.gitignore
__pycache__
*.py[cod]
*.so
build/
dist/
eggs/
.eggs/
lib/
.venv
venv/
ENV/
env/
.vscode
.idea
*.swp
*.swo
*~
.DS_Store
.pytest_cache/
.coverage
*.md
api_test_report_*.html
api_test_report_*.xlsx
.env
*.log
*.tmp
# Python
__pycache__/
*.py[cod]
*$py.class
.venv/
venv/
ENV/
env/
*.egg-info/
dist/
build/
# IDE
.vscode/
.idea/
*.swp
*.swo
*.iml
# Reports
api_test_report_*.html
api_test_report_*.xlsx
reports/
*.log
# OS
.DS_Store
Thumbs.db
.directory
# Excel
~$*
.~*
# Misc
.env
.env.local
nul
# 部署指南
本文档涵盖Linux服务器部署、Jenkins配置和多环境设置。
---
## 📝 多环境支持
该框架支持在**三个不同的环境**中运行:
| 环境 | DEPLOY_ENV | IP_HOST | 用途 |
|------|-----------|---------|------|
| 测试 | test | ai-test.hikoon.com | 开发测试 |
| UAT | uat | ai-uat.hikoon.com | 用户验收 |
| 生产 | prod | api.hikoon.com | 生产验证 |
### 环境选择方法
#### 本地运行
```bash
# 测试环境(默认)
export DEPLOY_ENV=test
python3 api_test.py
# UAT环境
export DEPLOY_ENV=uat
python3 api_test.py
# 生产环境
export DEPLOY_ENV=prod
python3 api_test.py
```
#### Jenkins中配置
已在Jenkinsfile中配置参数化支持,使用**Build with Parameters**选择环境。
---
## 🐳 使用 Docker(推荐)
### 快速开始
**构建镜像**
```bash
docker-compose build
```
**运行测试**
```bash
# 测试环境
docker-compose run --rm -e DEPLOY_ENV=test api-test-runner
# UAT环境
docker-compose run --rm -e DEPLOY_ENV=uat api-test-runner
# 生产环境
docker-compose run --rm -e DEPLOY_ENV=prod api-test-runner
```
详见 [DOCKER_GUIDE.md](./DOCKER_GUIDE.md)
---
## 🖥️ Linux 服务器部署(非Docker)
### 前提条件
- Python 3.8+
- pip 和 venv
- Git
### 部署步骤
#### 1️⃣ 克隆项目
```bash
git clone <repo-url> /opt/api_test
cd /opt/api_test
```
#### 2️⃣ 创建虚拟环境
```bash
python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
```
#### 3️⃣ 测试运行
```bash
export DEPLOY_ENV=test
python3 api_test.py
```
#### 4️⃣ 生成报告
报告文件位于项目目录:`api_test_report_*.html`
---
## 🔗 Jenkins 配置
### 前提条件
- Jenkins 已安装
- Git 插件已启用
- Python 环境已配置
### Jenkins 任务配置
#### 1️⃣ 创建新 Pipeline 任务
1. Jenkins → New Item
2. 输入任务名称
3. 选择 Pipeline
4. 点击 OK
#### 2️⃣ 配置 Pipeline
1. **Build Triggers** → 选择 Poll SCM → `0 10 * * *`(每天10点)
2. **Pipeline** → 选择 Pipeline script from SCM
3. **SCM** → Git
4. **Repository URL**`<your-repo-url>`
5. **Branch**`*/main`
6. **Script Path**`Jenkinsfile`
7. **Save**
### 3️⃣ 参数化执行
已在 Jenkinsfile 中配置参数化支持:
```groovy
parameters {
choice(
name: 'DEPLOY_ENV',
choices: ['test', 'uat', 'prod'],
description: '选择部署环境'
)
}
```
点击 **Build with Parameters** 选择环境执行
---
## 🔧 环境变量配置
### env_config.py
定义多个环境的IP配置:
```python
IP_HOST_MAP = {
'test': 'ai-test.hikoon.com',
'uat': 'ai-uat.hikoon.com',
'prod': 'api.hikoon.com'
}
```
### 添加新环境
**1. 编辑 env_config.py**
```python
IP_HOST_MAP = {
'test': '...',
'staging': 'ai-staging.hikoon.com', # 新增
'uat': '...',
'prod': '...'
}
```
**2. 编辑 Jenkinsfile**
```groovy
parameters {
choice(
name: 'DEPLOY_ENV',
choices: ['test', 'staging', 'uat', 'prod'], # 新增
description: '选择部署环境'
)
}
```
---
## 📊 查看测试报告
### 报告位置
- HTML报告:`api_test_report_api_cases_[env]_*.html`
- Excel报告:`api_test_report_api_cases_[env]_*.xlsx`
### 报告内容
- 📈 测试统计:总数、成功、失败、耗时
- 🔍 筛选功能:按失败、响应时间筛选
- 📋 详细结果:请求参数、响应数据、错误信息
---
## 🆘 常见问题
### 依赖安装失败
```bash
# 使用国内镜像
pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
```
### Python 版本不对
```bash
# 检查版本
python3 --version
# 如需指定版本
python3.8 -m venv venv
```
### 权限不足
```bash
# 添加执行权限
chmod +x api_test.py
# 修改目录权限
sudo chown -R $USER:$USER /opt/api_test
```
### 报告未生成
```bash
# 检查 api_cases.xlsx 是否存在
ls -la api_cases.xlsx
# 运行测试查看错误
python3 api_test.py
```
---
## 📞 获取帮助
- 详细步骤 → [README.md](./README.md)
- 使用问题 → [USER_GUIDE.md](./USER_GUIDE.md)
- Docker部署 → [DOCKER_GUIDE.md](./DOCKER_GUIDE.md)
- 验证方式 → [OPERATOR_GUIDE.md](./OPERATOR_GUIDE.md)
---
**🎉 部署完成!**
# Docker 配置指南
本文档说明如何使用Docker运行API自动化测试框架。
---
## 📋 快速开始
### 1️⃣ 构建镜像
```bash
# 使用docker-compose构建
docker-compose build
# 或使用docker构建
docker build -t api-test:latest .
```
### 2️⃣ 运行容器
**测试环境**
```bash
docker-compose run --rm -e DEPLOY_ENV=test api-test-runner
```
**UAT环境**
```bash
docker-compose run --rm -e DEPLOY_ENV=uat api-test-runner
```
**生产环境**
```bash
docker-compose run --rm -e DEPLOY_ENV=prod api-test-runner
```
### 3️⃣ 查看报告
报告文件保存在 `./reports` 目录:
```bash
ls -la reports/api_test_report_*.html
```
---
## 🐳 Docker命令
### 使用 docker-compose
```bash
# 运行指定环境的测试
DEPLOY_ENV=test docker-compose run --rm api-test-runner
# 后台运行
docker-compose up -d
# 查看日志
docker-compose logs -f api-test-runner
# 停止服务
docker-compose down
```
### 使用 docker 直接命令
```bash
# 构建镜像
docker build -t api-test:latest .
# 运行容器
docker run --rm -e DEPLOY_ENV=test api-test:latest
# 挂载卷
docker run --rm \
-e DEPLOY_ENV=test \
-v $(pwd)/reports:/app \
api-test:latest
# 交互式运行
docker run -it --rm \
-e DEPLOY_ENV=test \
-v $(pwd)/reports:/app \
api-test:latest /bin/bash
```
---
## 📂 文件说明
### Dockerfile
```dockerfile
FROM python:3.8-slim # 基础镜像
WORKDIR /app # 工作目录
COPY . /app/ # 复制项目
RUN pip install -r requirements.txt # 安装依赖
ENV DEPLOY_ENV=test # 默认环境
CMD ["python3", "api_test.py"] # 执行命令
```
### docker-compose.yml
关键配置:
- 自动环境变量传入
- 卷挂载:输入Excel、输出报告
- 资源限制:1核CPU、512MB内存
- 日志配置:json-file驱动
### .dockerignore
排除不必要文件,减小镜像体积
---
## 🔧 环境变量
### 支持的环境变量
| 变量 | 说明 | 默认值 |
|------|------|--------|
| `DEPLOY_ENV` | 执行环境 | test |
| `IP_HOST` | 覆盖IP地址 | 不设置 |
| `PYTHONUNBUFFERED` | 禁用缓冲 | 1 |
### 设置方式
**命令行**
```bash
docker-compose run --rm -e DEPLOY_ENV=test api-test-runner
```
**.env 文件**
```
DEPLOY_ENV=test
IP_HOST=ai-test.hikoon.com
```
---
## 🔗 与 Jenkins 集成
### Jenkinsfile 中的 Docker 配置
```groovy
stage('构建镜像') {
steps {
sh 'docker build -t api-test:${BUILD_NUMBER} .'
}
}
stage('执行测试') {
steps {
sh '''
docker run --rm \
-e DEPLOY_ENV=${DEPLOY_ENV} \
-v ${WORKSPACE}/reports:/app \
api-test:${BUILD_NUMBER}
'''
}
}
post {
always {
sh 'docker rmi api-test:${BUILD_NUMBER}'
}
}
```
---
## 🐛 常见问题
### Q1: 容器报错找不到api_cases.xlsx
```bash
docker run -v $(pwd)/api_cases.xlsx:/app/api_cases.xlsx:ro api-test:latest
```
### Q2: 报告文件生成在容器内无法访问
```bash
docker run -v $(pwd)/reports:/app api-test:latest
```
### Q3: 环境变量未传入
```bash
# 正确
docker run -e DEPLOY_ENV=test api-test:latest
# 错误
docker run api-test:latest -e DEPLOY_ENV=test
```
### Q4: 镜像体积过大
使用 `.dockerignore` 排除不必要文件
---
## 📞 获取帮助
- Docker官方文档:https://docs.docker.com/
- docker-compose:https://docs.docker.com/compose/
- Jenkins Docker:https://www.jenkins.io/doc/book/installing/docker/
---
**🎉 Docker配置完成!**
FROM python:3.8-slim
WORKDIR /app
RUN apt-get update && apt-get install -y git curl && rm -rf /var/lib/apt/lists/*
COPY . /app/
RUN pip install --no-cache-dir -r requirements.txt
ENV DEPLOY_ENV=test
ENV PYTHONUNBUFFERED=1
RUN mkdir -p /app/reports
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD python -c "import sys; sys.exit(0)" || exit 1
CMD ["python3", "api_test.py"]
pipeline {
agent any
parameters {
choice(
name: 'DEPLOY_ENV',
choices: ['test', 'uat', 'prod'],
description: '选择部署环境'
)
}
triggers {
cron('0 10 * * *')
}
environment {
PYTHON_VERSION = '3.8'
PROJECT_NAME = 'api_test'
DEPLOY_ENV = "${params.DEPLOY_ENV ?: 'test'}"
IP_HOST_TEST = 'ai-test.hikoon.com'
IP_HOST_UAT = 'ai-uat.hikoon.com'
IP_HOST_PROD = 'api.hikoon.com'
}
stages {
stage('检出代码') {
steps {
echo "开始检出代码..."
checkout scm
}
}
stage('环境准备') {
steps {
echo "准备Python环境..."
sh '''
python3 --version
if [ ! -d "venv" ]; then
python3 -m venv venv
fi
. venv/bin/activate
pip install --upgrade pip -q
pip install -r requirements.txt -q
echo "依赖安装完成"
'''
}
}
stage('执行测试') {
steps {
echo "开始执行API自动化测试 (环境: ${DEPLOY_ENV})..."
script {
def ipHost = 'ai-test.hikoon.com'
if (env.DEPLOY_ENV == 'uat') {
ipHost = env.IP_HOST_UAT
} else if (env.DEPLOY_ENV == 'prod') {
ipHost = env.IP_HOST_PROD
} else {
ipHost = env.IP_HOST_TEST
}
env.IP_HOST = ipHost
}
sh '''
. venv/bin/activate
export DEPLOY_ENV="${DEPLOY_ENV}"
export IP_HOST="${IP_HOST}"
echo "当前执行环境: ${DEPLOY_ENV}"
echo "IP_HOST: ${IP_HOST}"
python3 api_test.py
'''
}
}
stage('生成报告') {
steps {
echo "正在生成测试报告..."
sh '''
ls -la api_test_report_* 2>/dev/null || echo "未找到报告文件"
'''
}
}
}
post {
always {
echo "清理工作目录..."
sh 'echo "测试执行完成"'
}
success {
echo "✅ [${DEPLOY_ENV}环境] 所有测试用例运行成功!"
}
failure {
echo "❌ [${DEPLOY_ENV}环境] 有测试用例执行失败!"
}
}
}
// Docker 环境下的 Jenkins Pipeline 示例
pipeline {
agent any
parameters {
choice(
name: 'DEPLOY_ENV',
choices: ['test', 'uat', 'prod'],
description: '选择部署环境'
)
}
environment {
DEPLOY_ENV = "${params.DEPLOY_ENV ?: 'test'}"
IMAGE_NAME = "api-test"
IMAGE_TAG = "${BUILD_NUMBER}"
WORKSPACE_REPORTS = "${WORKSPACE}/reports"
}
stages {
stage('检出代码') {
steps {
echo "检出代码..."
checkout scm
}
}
stage('构建镜像') {
steps {
echo "构建Docker镜像..."
script {
sh '''
docker build -t ${IMAGE_NAME}:${IMAGE_TAG} .
docker tag ${IMAGE_NAME}:${IMAGE_TAG} ${IMAGE_NAME}:latest
'''
}
}
}
stage('执行测试') {
steps {
echo "执行API自动化测试 (环境: ${DEPLOY_ENV})..."
script {
sh '''
mkdir -p ${WORKSPACE_REPORTS}
docker run --rm \
-e DEPLOY_ENV=${DEPLOY_ENV} \
-v ${WORKSPACE_REPORTS}:/app \
${IMAGE_NAME}:${IMAGE_TAG}
'''
}
}
}
stage('生成报告') {
steps {
echo "正在生成测试报告..."
script {
sh '''
ls -la ${WORKSPACE_REPORTS}/api_test_report_* 2>/dev/null || echo "未找到报告文件"
'''
}
}
}
stage('发布HTML报告') {
when {
expression {
return fileExists("${WORKSPACE_REPORTS}/api_test_report_api_cases_${DEPLOY_ENV}*.html")
}
}
steps {
echo "发布HTML测试报告..."
publishHTML([
reportDir: 'reports',
reportFiles: "api_test_report_api_cases_${DEPLOY_ENV}*.html",
reportName: "API测试报告-${DEPLOY_ENV}环境",
keepAll: true,
alwaysLinkToLastBuild: true
])
}
}
}
post {
always {
echo "清理工作环境..."
script {
sh '''
docker container prune -f
echo "========== 镜像列表 =========="
docker images | grep ${IMAGE_NAME}
'''
}
}
success {
echo "✅ [${DEPLOY_ENV}环境] 所有测试用例运行成功!"
}
failure {
echo "❌ [${DEPLOY_ENV}环境] 有测试用例执行失败!"
}
}
}
# 关系运算符使用指南
## 概述
`expected_response` 字段支持多种关系运算符,可以进行更灵活的响应验证。
---
## 支持的运算符
### 1. 比较运算符
#### `$ne` - 不等于
```json
{"code": {"$ne": 500}}
```
检查 `code` 字段不等于 500。
#### `$eq` - 等于
```json
{"count": {"$eq": 10}}
```
检查 `count` 字段等于 10。
#### `$gt` - 大于
```json
{"count": {"$gt": 0}}
```
检查 `count` 字段大于 0。
#### `$gte` - 大于等于
```json
{"score": {"$gte": 60}}
```
#### `$lt` - 小于
```json
{"timeout": {"$lt": 1000}}
```
#### `$lte` - 小于等于
```json
{"age": {"$lte": 18}}
```
### 2. 包含运算符
#### `$in` - 值在列表中
```json
{"status": {"$in": ["pending", "done", "processing"]}}
```
#### `$nin` - 值不在列表中
```json
{"type": {"$nin": ["admin", "root", "system"]}}
```
### 3. 字符串运算符
#### `$contains` - 字符串包含
```json
{"msg": {"$contains": "success"}}
```
#### `$not_contains` - 字符串不包含
```json
{"error": {"$not_contains": "timeout"}}
```
#### `$regex` - 正则表达式匹配
```json
{"email": {"$regex": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"}}
```
### 4. 范围运算符
#### `$range` - 范围判断 [min, max]
```json
{"value": {"$range": [0, 100]}}
```
检查 `value` 在 0 到 100 之间(包含边界)。
---
## 使用示例
### 示例1:验证状态码
```json
{
"code": {"$ne": 500},
"msg": {"$contains": "成功"}
}
```
### 示例2:验证列表数据
```json
{
"code": 0,
"data": {
"count": {"$gt": 0},
"items": ["__NOT_NULL__"],
"status": {"$in": ["active", "inactive"]}
}
}
```
### 示例3:验证分页数据
```json
{
"code": 0,
"pageIndex": {"$gte": 1},
"pageSize": {"$range": [1, 1000]},
"total": {"$gte": 0}
}
```
### 示例4:验证邮箱和年龄
```json
{
"code": 0,
"email": {"$regex": "^.*@.*$"},
"age": {"$range": [18, 100]},
"roles": {"$in": ["user", "admin"]},
"status": {"$not_contains": "banned"}
}
```
---
## Excel中的使用
`expected_response` 列中填入 JSON 格式的期望响应:
```
case_id | api_name | expected_response
1 | 测试接口 | {"code": 0, "msg": "success"}
2 | 列表接口 | {"code": 0, "data": {"count": {"$gt": 0}}}
3 | 错误处理 | {"code": {"$ne": 200}}
```
---
## 特殊值
- `"__NOT_NULL__"` - 检查字段非空
- `["__NOT_NULL__"]` - 检查数组非空
---
## 注意事项
1. **类型匹配**:比较运算符(`$gt`, `$lt` 等)要求数据类型兼容
2. **正则表达式**:使用 `$regex` 时,值会被转换为字符串后进行匹配
3. **列表范围**`$range` 只支持两元素列表 `[min, max]`,两个值都是包含的
4. **错误处理**:运算符验证失败会记录在测试报告的 `error` 字段中
---
**更多示例请查看 USER_GUIDE.md**
# 优化需求与增强建议
## 📋 概述
本文档列出了项目的优化建议和潜在增强功能,供未来版本参考。
---
## 🚀 性能优化
### 1. 并发执行用例
**当前**:单线程串行执行用例
**优化方案**
```python
from concurrent.futures import ThreadPoolExecutor
# 使用线程池并发执行
with ThreadPoolExecutor(max_workers=5) as executor:
futures = [executor.submit(test_case, case) for case in cases]
results = [f.result() for f in futures]
```
**效果**:可将执行时间缩短 50-70%
### 2. 连接池复用
**当前**:每个请求创建新连接
**优化方案**
```python
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
session = requests.Session()
retry = Retry(connect=3, backoff_factor=0.5)
adapter = HTTPAdapter(max_retries=retry)
session.mount('http://', adapter)
session.mount('https://', adapter)
```
**效果**:减少网络开销,提升请求速度
### 3. Excel文件缓存
**当前**:每次运行都重新加载Excel
**优化方案**
- 使用内存缓存
- 增量读取
- 支持预编译
---
## 💡 功能增强
### 1. 数据库支持
**建议**:支持从数据库读取测试用例
```python
def read_cases_from_db(connection_string):
# 从MySQL、PostgreSQL等数据库读取
pass
```
### 2. 数据驱动测试(DDT)
**建议**:支持参数化测试
```python
# 使用 @ddt 装饰器
@ddt
class TestAPI(unittest.TestCase):
@data([{"param": 1}, {"param": 2}])
def test_api(self, case):
pass
```
### 3. 测试报告增强
**建议**
- 图表展示(ECharts)
- 趋势分析
- 性能对比
- 邮件通知
- 钉钉集成
### 4. 环境管理
**建议**
- 环境变量密钥管理
- 敏感信息加密
- 配置文件版本管理
- 动态环境切换
### 5. 测试数据管理
**建议**
- 测试数据工厂
- 数据清理机制
- 数据备份恢复
- 依赖数据预置
---
## 🔒 安全增强
### 1. 敏感信息保护
**建议**
```python
# 不存储明文密码
# 使用环境变量或密钥管理系统
import os
API_TOKEN = os.getenv('API_TOKEN')
# 打印前脱敏
def mask_sensitive(data):
# 隐藏密钥、密码等
pass
```
### 2. 证书管理
**建议**
- SSL/TLS 证书管理
- 证书过期预警
- 自动续期支持
---
## 📊 可观测性增强
### 1. 日志系统
**建议**
```python
import logging
# 结构化日志
logger = logging.getLogger(__name__)
logger.info("Test case executed", extra={
"case_id": case_id,
"env": env,
"duration": duration
})
```
### 2. 链路追踪
**建议**
- 使用 OpenTelemetry
- 支持分布式追踪
- 性能监控
### 3. 指标收集
**建议**
- Prometheus metrics
- 成功率、响应时间等指标
- 长期趋势分析
---
## 📦 架构优化
### 1. 模块化设计
**建议**
```
core/
├── request/
├── validation/
├── reporting/
└── storage/
plugins/
├── slack/
├── email/
└── dingding/
```
### 2. 插件系统
**建议**
- 支持自定义验证器
- 支持自定义报告生成器
- 支持自定义通知器
### 3. REST API
**建议**
- 提供 REST API
- Web UI 管理界面
- 远程执行能力
---
## 🧪 测试框架增强
### 1. 单元测试
**建议**
- 为核心模块增加单元测试
- 达到 80% 覆盖率
### 2. 集成测试
**建议**
- Docker 容器化测试
- 多环境集成测试
- CI/CD 流水线
### 3. 性能测试
**建议**
- 压力测试支持
- 基准测试
- 性能回归检测
---
## 🔄 工作流增强
### 1. 前置条件处理
**建议**
```python
def setup_test_data():
# 创建必要的测试数据
pass
def cleanup_test_data():
# 清理测试数据
pass
```
### 2. 后置处理
**建议**
- 自动生成测试报告
- 自动发送通知
- 自动更新测试矩阵
### 3. 失败重试
**建议**
```python
@retry(max_attempts=3, backoff_factor=2)
def execute_test(case):
pass
```
---
## 📱 跨平台支持
### 1. 移动端API测试
**建议**
- 支持 Mobile API 测试
- 支持 GraphQL
### 2. 跨平台兼容性
**建议**
- Windows / Linux / macOS
- Python 3.9+
- 更新依赖库
---
## 🤖 AI/ML 集成
### 1. 智能测试生成
**建议**
- 使用 AI 自动生成测试用例
- 使用 AI 进行断言生成
### 2. 异常检测
**建议**
- 使用 ML 检测异常行为
- 自动关联异常根因
---
## 📈 优先级
| 优化项 | 优先级 | 难度 | 工作量 |
|-------|--------|------|--------|
| 并发执行 | 高 | 中 | 2-3天 |
| 连接池 | 高 | 低 | 1天 |
| 报告增强 | 中 | 低 | 3-5天 |
| 数据库支持 | 中 | 中 | 1周 |
| 插件系统 | 低 | 高 | 2周 |
| REST API | 低 | 高 | 2周 |
---
## 🎯 建议实施顺序
1. **第一阶段**:并发执行、连接池、报告增强
2. **第二阶段**:数据库支持、前置/后置处理
3. **第三阶段**:REST API、插件系统、Web UI
4. **第四阶段**:AI/ML 集成、可观测性增强
---
## 📞 反馈
如有优化建议,欢迎提交 Issue 或 Pull Request。
---
**🚀 不断优化,持续改进!**
# 🚀 傻瓜式部署指南
**目标**:从零开始,把项目部署到Linux服务器,使用Docker运行,集成到Jenkins,代码托管在Git仓库
**预计时间**:30-60分钟
**难度**:⭐⭐(超简单)
---
## 📋 前置条件(必须有)
在开始之前,请确保你有:
- [ ] 一个GitHub/GitLab账户(用于托管代码)
- [ ] 一台Linux服务器(Ubuntu 20+ 或 CentOS 8+)
- [ ] Linux服务器可以上网
- [ ] 一个Jenkins服务器(已安装)
- [ ] 本地电脑有Git命令行工具
---
## 第一步:初始化本地Git仓库(5分钟)
### 1.1 在项目目录初始化Git
```bash
cd "D:\百音引擎项目\interface_test"
# Windows PowerShell 执行
git init
git config user.name "你的名字"
git config user.email "你的邮箱@example.com"
```
### 1.2 创建 .gitignore 文件
在项目根目录创建 `.gitignore` 文件(排除不需要的文件):
```bash
# 创建文件
cat > .gitignore << 'EOF'
# Python
__pycache__/
*.py[cod]
*$py.class
.venv/
venv/
ENV/
# IDE
.vscode/
.idea/
*.swp
*.swo
# Reports
api_test_report_*.html
api_test_report_*.xlsx
# OS
.DS_Store
Thumbs.db
# Logs
*.log
EOF
```
### 1.3 添加所有文件到Git
```bash
git add .
git commit -m "Initial commit: API自动化测试框架
- 多环境支持(test/uat/prod)
- Docker容器化
- Jenkins集成
- 详细文档"
```
---
## 第二步:创建远程Git仓库(5分钟)
### 2.1 在GitHub/GitLab创建新仓库
**GitHub步骤**
1. 访问 https://github.com/new
2. 输入仓库名:`api-test-framework`
3. 描述:`API自动化测试框架 - 支持多环境、Docker、Jenkins`
4. 选择 **Public**(如果是私有项目选Private)
5. 点击 **Create repository**
**获取仓库URL**
- HTTPS: `https://github.com/你的用户名/api-test-framework.git`
- SSH: `git@github.com:你的用户名/api-test-framework.git`
### 2.2 推送代码到远程仓库
```bash
# 添加远程仓库地址
git remote add origin https://github.com/你的用户名/api-test-framework.git
# 推送到远程(第一次)
git branch -M main
git push -u origin main
# 后续推送只需要
git push
```
---
## 第三步:Linux服务器准备(10分钟)
### 3.1 连接到Linux服务器
```bash
# 从本地电脑连接到服务器
ssh root@你的服务器IP
# 或
ssh ubuntu@你的服务器IP
```
### 3.2 检查并安装Docker
```bash
# 检查Docker是否已安装
docker --version
# 如果未安装,执行以下命令(Ubuntu)
sudo apt update
sudo apt install -y docker.io docker-compose
# 如果是CentOS
sudo yum install -y docker docker-compose
# 启动Docker
sudo systemctl start docker
sudo systemctl enable docker
# 验证安装
docker run hello-world
```
### 3.3 检查并安装Git
```bash
# 检查Git是否已安装
git --version
# 如果未安装
sudo apt install -y git # Ubuntu
# 或
sudo yum install -y git # CentOS
```
### 3.4 创建项目目录
```bash
# 创建项目目录
sudo mkdir -p /opt/api-test
sudo chown -R $USER:$USER /opt/api-test
cd /opt/api-test
```
---
## 第四步:克隆项目到服务器(5分钟)
```bash
cd /opt/api-test
# 克隆代码
git clone https://github.com/你的用户名/api-test-framework.git .
# 验证文件是否齐全
ls -la
```
**应该看到**
```
Dockerfile
docker-compose.yml
api_test.py
env_config.py
requirements.txt
README.md
等...
```
---
## 第五步:在Linux上测试Docker运行(10分钟)
### 5.1 构建Docker镜像
```bash
cd /opt/api-test
# 构建镜像(第一次会很慢,10-15分钟)
docker-compose build
```
### 5.2 运行第一次测试
```bash
# 创建报告目录
mkdir -p reports
# 测试环境运行
DEPLOY_ENV=test docker-compose run --rm api-test-runner
# 查看报告
ls -la reports/
```
**预期输出**
```
✅ 所有测试用例运行成功!
HTML测试报告已生成:api_test_report_api_cases_test_*.html
```
---
## 第六步:配置Jenkins(15分钟)
### 6.1 进入Jenkins Web界面
打开浏览器访问:
```
http://你的Jenkins服务器IP:8080
```
### 6.2 创建新Pipeline任务
1. 点击 **+ 新建Item**
2. 输入任务名称:`api-test-framework`
3. 选择 **Pipeline**
4. 点击 **OK**
### 6.3 配置Pipeline
**第一部分:General**
- 描述:`API自动化测试框架 - 多环境支持`
**第二部分:Parameters**
1. 点击 **Add Parameter**
2. 选择 **Choice Parameter**
3. 配置:
- Name: `DEPLOY_ENV`
- Choices:
```
test
uat
prod
```
**第三部分:Pipeline**
1. Definition: 选择 **Pipeline script from SCM**
2. SCM: 选择 **Git**
3. 配置Git:
- Repository URL: `https://github.com/你的用户名/api-test-framework.git`
- Credentials: 如需认证,点击 **Add** 添加用户名/密码
- Branch: `*/main`
- Script Path: `Jenkinsfile`
4. 点击 **Save**
### 6.4 首次运行Pipeline
1. 点击 **Build with Parameters**
2. 选择 **DEPLOY_ENV**: `test`
3. 点击 **Build**
**查看构建日志**:
- 点击左侧 **#1** 查看构建进度
- 点击 **Console Output** 查看详细日志
---
## 第七步:验证完整流程(5分钟)
### 7.1 验证Git仓库
```bash
# 确认远程仓库有代码
git remote -v
git log --oneline
```
### 7.2 验证Docker运行
```bash
cd /opt/api-test
# 再次运行测试
DEPLOY_ENV=uat docker-compose run --rm api-test-runner
# 检查报告
ls -la reports/api_test_report_*uat*
```
### 7.3 验证Jenkins任务
1. 打开Jenkins Web界面
2. 点击你创建的任务 `api-test-framework`
3. 点击 **Build History** 查看构建记录
4. 点击最新的构建号查看详情
5. 点击 **Console Output** 确认执行成功
---
## 第八步:配置定时任务(可选,5分钟)
### 8.1 在Jenkins中设置定时执行
1. 进入任务配置页面
2. 找到 **Build Triggers**
3. 勾选 **Poll SCM**
4. 输入 Cron 表达式(每天上午10点执行):
```
0 10 * * *
```
### 8.2 定时任务含义
```
分钟 小时 日 月 周几
0 10 * * * 每天上午10点执行
0 10 * * 1-5 工作日上午10点执行
0 */6 * * * 每6小时执行一次
0 2 * * * 每天凌晨2点执行
```
---
## ✅ 检查清单
完成以下所有步骤,确保部署成功:
- [ ] Git仓库已初始化,代码已推送到GitHub/GitLab
- [ ] Linux服务器已安装Docker和Git
- [ ] 项目代码已克隆到 `/opt/api-test`
- [ ] Docker镜像已成功构建
- [ ] 本地Docker测试已通过(能生成报告文件)
- [ ] Jenkins任务已创建
- [ ] Jenkins能够参数化执行(Build with Parameters)
- [ ] 至少成功运行过一次Pipeline
- [ ] 报告文件已生成并包含环境标识
---
## 🚀 快速命令参考
### 本地操作
```bash
# Git初始化和提交
git init
git add .
git commit -m "message"
git remote add origin <url>
git push -u origin main
# 后续更新
git add .
git commit -m "message"
git push
```
### 服务器操作
```bash
# 连接服务器
ssh root@IP
# Docker操作
docker-compose build
DEPLOY_ENV=test docker-compose run --rm api-test-runner
# Git更新代码
cd /opt/api-test
git pull
```
### Jenkins操作
```
Build with Parameters → 选择环境 → Build
```
---
## 🆘 常见问题速解
### Q1: Git提交报错"fatal: not a git repository"
**解决**:
```bash
cd 到项目目录
git init
```
### Q2: Docker镜像构建失败
**解决**:
```bash
# 检查网络
ping 8.8.8.8
# 重试构建
docker-compose build --no-cache
```
### Q3: Jenkins连接Git仓库失败
**解决**:
1. 检查仓库URL是否正确
2. 如果是私有仓库,需要添加SSH密钥或用户名/密码
3. 在Jenkins系统设置中配置凭证
### Q4: 报告文件找不到
**解决**:
```bash
# 检查reports目录
ls -la /opt/api-test/reports/
# 检查容器日志
docker logs <container-id>
```
### Q5: Jenkins无法访问
**解决**:
```bash
# 检查Jenkins是否运行
ps aux | grep jenkins
# 检查端口8080是否打开
netstat -tlnp | grep 8080
# 检查防火墙
sudo ufw allow 8080
```
---
## 📞 需要帮助?
1. **查看详细文档**:
- README.md - 项目介绍
- DEPLOYMENT_GUIDE.md - 部署详情
- DOCKER_GUIDE.md - Docker详情
- USER_GUIDE.md - 使用指南
2. **查看日志**:
- Docker日志:`docker logs <container-id>`
- Jenkins日志:Jenkins Web界面 → Console Output
- 服务器日志:`/var/log/`
3. **测试连通性**:
```bash
# 测试Git连接
git clone <url>
# 测试Docker运行
docker run hello-world
# 测试Jenkins API
curl http://localhost:8080
```
---
## 📊 部署架构
```
┌─────────────────────────────────────────────┐
│ GitHub/GitLab 仓库 │
│ (代码托管) │
└─────────────────────────────────────────────┘
↓ git clone/pull
┌─────────────────────────────────────────────┐
│ Linux 服务器 /opt/api-test │
│ ┌─────────────────────────────────────┐ │
│ │ Docker 容器 │ │
│ │ ┌───────────────────────────────┐ │ │
│ │ │ Python 环境 │ │ │
│ │ │ - api_test.py │ │ │
│ │ │ - env_config.py │ │ │
│ │ │ - requirements.txt │ │ │
│ │ └───────────────────────────────┘ │ │
│ └─────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
↑ 触发
┌─────────────────────────────────────────────┐
│ Jenkins (CI/CD) │
│ - 参数化Build(环境选择) │
│ - 定时执行(Cron) │
│ - 报告发布 │
└─────────────────────────────────────────────┘
```
---
## ✨ 下一步
部署完成后,你可以:
1. **配置邮件通知**:当测试失败时,Jenkins自动发送邮件
2. **集成钉钉通知**:实时推送测试结果
3. **配置测试报告展示**:在Jenkins界面展示HTML报告
4. **设置性能监控**:监控测试响应时间趋势
详见各个技术文档。
---
**🎉 恭喜!你已经完成了完整的CI/CD部署!**
现在你可以:
- ✅ 本地开发,推送代码到Git
- ✅ Git自动触发Jenkins构建
- ✅ Jenkins自动构建Docker镜像
- ✅ Docker自动运行测试
- ✅ 自动生成并展示测试报告
**Happy Testing! 🚀**
# 🚀 API 自动化测试框架
## 📌 项目简介
一套完整的API自动化测试框架,支持**多环境执行**(test/uat/prod)、Excel驱动测试、灵活的用例筛选和详细的测试报告生成。
**核心特性**
-**多环境支持**:一套代码运行在test/uat/prod三个环境
-**Excel驱动**:用例配置在Excel中,易于维护
-**灵活筛选**:通过env_scope字段控制各环境的执行用例
-**丰富的验证**:支持多种关系运算符($gt、$contains、$regex等)
-**详细报告**:HTML和Excel双格式测试报告
-**Jenkins集成**:参数化执行,支持定时任务
-**Docker支持**:容器化部署
---
## 🎯 快速开始
### 需求
- Python 3.8+
- pip 和 venv
- 依赖库(详见requirements.txt)
### 1️⃣ 安装依赖
```bash
# 创建虚拟环境
python3 -m venv venv
# 激活虚拟环境
source venv/bin/activate # Linux/Mac
# 或
.\venv\Scripts\activate # Windows
# 安装依赖
pip install -r requirements.txt
```
### 2️⃣ 准备测试用例
编辑 `api_cases.xlsx`,在表格中添加新列 `env_scope`,标记每个用例的执行范围:
```
env_scope 值说明:
- all 或留空 → 所有环境执行
- test → 仅测试环境执行
- uat → 仅UAT环境执行
- prod → 仅生产环境执行
- test,uat → 测试和UAT执行,生产跳过
```
### 3️⃣ 运行测试
```bash
# 测试环境
export DEPLOY_ENV=test
python3 api_test.py
# UAT环境
export DEPLOY_ENV=uat
python3 api_test.py
# 生产环境
export DEPLOY_ENV=prod
python3 api_test.py
```
### 4️⃣ 查看报告
执行完成后,生成的报告文件:
- `api_test_report_*_test_*.html` - 测试环境HTML报告
- `api_test_report_*_uat_*.html` - UAT环境HTML报告
- `api_test_report_*_prod_*.html` - 生产环境HTML报告
- `api_test_report_*.xlsx` - Excel格式报告
---
## 🐳 Docker 快速开始
### 前提条件
- Docker 已安装
- docker-compose 已安装
### 运行命令
**测试环境**
```bash
docker-compose run --rm -e DEPLOY_ENV=test api-test-runner
```
**UAT环境**
```bash
docker-compose run --rm -e DEPLOY_ENV=uat api-test-runner
```
**生产环境**
```bash
docker-compose run --rm -e DEPLOY_ENV=prod api-test-runner
```
报告文件保存在 `./reports` 目录
详细说明见 [DOCKER_GUIDE.md](./DOCKER_GUIDE.md)
---
## 📖 文档导航
| 文档 | 说明 | 适用场景 |
|------|------|---------|
| **[USER_GUIDE.md](./USER_GUIDE.md)** | 详细使用指南 | 如何使用框架、多环境配置、常见问题 |
| **[DEPLOYMENT_GUIDE.md](./DEPLOYMENT_GUIDE.md)** | 部署安装指南 | Linux服务器部署、Jenkins配置 |
| **[DOCKER_GUIDE.md](./DOCKER_GUIDE.md)** | Docker部署指南 | Docker容器化、docker-compose、Jenkins集成 |
| **[OPERATOR_GUIDE.md](./OPERATOR_GUIDE.md)** | 运维指南 | 关系运算符使用 |
| **[OPTIMIZATION_REQUIREMENTS.md](./OPTIMIZATION_REQUIREMENTS.md)** | 优化需求 | 性能优化、增强建议 |
---
## 🏗️ 项目结构
```
interface_test/
├── 📄 README.md # 项目介绍(本文件)
├── 📖 USER_GUIDE.md # 使用指南
├── 🚀 DEPLOYMENT_GUIDE.md # Linux部署指南
├── 🐳 DOCKER_GUIDE.md # Docker部署指南
├── 🔧 OPERATOR_GUIDE.md # 运维指南
├── 📈 OPTIMIZATION_REQUIREMENTS.md # 优化需求
├── 💻 api_test.py # 主测试脚本
├── ⚙️ env_config.py # 环境配置(多环境IP)
├── 📊 api_cases.xlsx # 测试用例(Excel)
├── requirements.txt # Python依赖
├── 🐳 Dockerfile # Docker镜像配置
├── 📦 docker-compose.yml # Docker容器编排
├── .dockerignore # Docker忽略文件
├── Jenkinsfile # Jenkins标准配置(直接执行)
├── Jenkinsfile.docker # Jenkins Docker配置(容器中执行)
└── .venv/ # Python虚拟环境
```
---
## ✨ 核心功能
### 1️⃣ 多环境执行
**支持三个环境**
| 环境 | IP_HOST | 用途 |
|------|---------|------|
| test | ai-test.hikoon.com | 测试环境 |
| uat | ai-uat.hikoon.com | UAT验收环境 |
| prod | api.hikoon.com | 生产环境 |
**环境选择**
```bash
export DEPLOY_ENV=test # 或 uat / prod
python3 api_test.py
```
### 2️⃣ 用例筛选
通过 `env_scope` 列灵活控制用例执行范围:
```
env_scope = 'all' → 三环境都执行
env_scope = 'test,uat' → 生产环境跳过(避免危险操作)
env_scope = 'prod' → 仅生产环境执行
```
### 3️⃣ 关系运算符
`expected_response` 支持多种验证方式:
```json
{
"code": {"$ne": 500}, // 不等于
"count": {"$gt": 0}, // 大于
"status": {"$in": ["ok", "done"]}, // 在列表中
"msg": {"$contains": "success"}, // 字符串包含
"email": {"$regex": "^.*@.*$"} // 正则匹配
}
```
详见 [OPERATOR_GUIDE.md](./OPERATOR_GUIDE.md)
### 4️⃣ 详细报告
- 📊 **统计信息**:总数、成功数、失败数、耗时
- 🔍 **筛选功能**:按失败、响应时间筛选
- 📋 **详细结果**:请求参数、期望值、实际值、错误信息
- 📈 **性能分析**:响应时间分布
- 🔖 **环境标识**:报告文件名包含运行环境
---
## 🔧 环境配置
### env_config.py
定义多个环境的IP和其他变量:
```python
import os
# 多环境IP配置
DEPLOY_ENV = os.getenv('DEPLOY_ENV', 'test')
IP_HOST_MAP = {
'test': 'ai-test.hikoon.com',
'uat': 'ai-uat.hikoon.com',
'prod': 'api.hikoon.com'
}
IP_HOST = os.getenv('IP_HOST', IP_HOST_MAP.get(DEPLOY_ENV, 'ai-test.hikoon.com'))
# 当前日期(格式:YY-MM-DD)
CURRENT_DATE = datetime.now().strftime('%y-%m-%d')
# 其他环境变量
RANDOM_EMAIL = gen_email() # 自动生成随机邮箱
```
### 自定义环境变量
```python
# env_config.py 中添加
API_TOKEN = "your-token-here"
TIMEOUT = 60
DEBUG = True
```
然后在Excel用例中使用:
```
URL: {IP_HOST}/api/users
Headers: {"Authorization": "Bearer {API_TOKEN}"}
Params: {"timestamp": "{CURRENT_DATE}"}
```
---
## 📊 Excel测试用例格式
| 字段 | 说明 | 示例 |
|------|------|------|
| case_id | 用例ID | 1 |
| api_name | 接口名称 | 用户注册 |
| is_run | 是否执行(1=跳过) | 0 |
| env_scope | 执行环围(新增) | all |
| method | 请求方法 | POST |
| ip_host / url | 基础URL | ai-test.hikoon.com |
| path | 路径 | /api/users/register |
| params | 请求参数(JSON) | {"email":"test@qq.com"} |
| headers | 请求头(JSON) | {"Content-Type":"application/json"} |
| expected_code | 期望状态码 | 200 |
| expected_response | 期望响应 | {"code":0} |
| extract_vars | 提取变量 | user_id=data.user_id |
---
## 🚀 Jenkins集成
### Build with Parameters
在Jenkins中参数化执行,选择环境:
1. 点击 **Build with Parameters**
2. 选择 **DEPLOY_ENV**: test / uat / prod
3. 点击 **Build**
### 自动环境映射
```groovy
environment {
DEPLOY_ENV = "${params.DEPLOY_ENV ?: 'test'}"
IP_HOST_TEST = 'ai-test.hikoon.com'
IP_HOST_UAT = 'ai-uat.hikoon.com'
IP_HOST_PROD = 'api.hikoon.com'
}
```
### 执行流程
```
Jenkins参数选择 → DEPLOY_ENV环境变量 → IP_HOST自动映射 → 用例执行 → 报告生成
```
详见 [DEPLOYMENT_GUIDE.md](./DEPLOYMENT_GUIDE.md)
---
## 📝 常见用例场景
### 场景1:通用接口测试(所有环境)
```
env_scope = 'all'
用例:登录、查询用户信息、获取列表等
结果:test✅ / uat✅ / prod✅
```
### 场景2:跳过生产的危险操作
```
env_scope = 'test,uat'
用例:删除数据、清理数据库、重置密码等
结果:test✅ / uat✅ / prod⏭️(跳过)
```
### 场景3:仅生产执行
```
env_scope = 'prod'
用例:导出报表、数据备份等(基于真实数据)
结果:test⏭️ / uat⏭️ / prod✅
```
---
## 🆘 常见问题
### Q: 如何新增环境?
编辑 `env_config.py``Jenkinsfile` 的IP配置。详见 [USER_GUIDE.md](./USER_GUIDE.md) 的Q&A部分
### Q: env_scope 支持哪些值?
支持:all / test / uat / prod 及其组合(用逗号分隔)。详见 [USER_GUIDE.md](./USER_GUIDE.md)
### Q: 如何禁用某个用例?
设置 `is_run = 1``env_scope` 不包含当前环境。详见 [USER_GUIDE.md](./USER_GUIDE.md)
### Q: 生产环境执行失败怎么办?
检查 `env_scope` 是否正确、IP_HOST是否可访问。详见 [DEPLOYMENT_GUIDE.md](./DEPLOYMENT_GUIDE.md)
---
## 📞 获取帮助
- **使用问题**[USER_GUIDE.md](./USER_GUIDE.md)
- **部署问题**[DEPLOYMENT_GUIDE.md](./DEPLOYMENT_GUIDE.md)
- **Docker问题**[DOCKER_GUIDE.md](./DOCKER_GUIDE.md)
- **验证方式**[OPERATOR_GUIDE.md](./OPERATOR_GUIDE.md)
- **优化建议**[OPTIMIZATION_REQUIREMENTS.md](./OPTIMIZATION_REQUIREMENTS.md)
---
## 📈 最近更新
### v1.0.0 (2026-01-21)
✅ 完整框架实现
- ✅ 多环境执行支持(test/uat/prod)
- ✅ env_scope字段用于用例筛选
- ✅ 关系运算符验证($eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $contains, $regex, $range等)
- ✅ 报告文件名包含环境标识
- ✅ Docker容器化支持
- ✅ 参数化Jenkins配置
- ✅ 完整文档整合
---
## 📄 许可证
项目所有者:百音引擎项目
---
**🎉 祝你使用愉快!如有问题,请查看对应的文档或联系项目维护者。**
# 📖 用户使用指南
## 目录
- [多环境执行](#多环境执行)
- [用例管理](#用例管理)
- [响应验证](#响应验证)
- [本地运行](#本地运行)
- [Jenkins执行](#jenkins执行)
- [常见问题](#常见问题)
- [最佳实践](#最佳实践)
---
## 🌍 多环境执行
### 支持的环境
| 环境 | 标识 | IP | 用途 |
|------|------|------|------|
| **测试** | test | ai-test.hikoon.com | 开发和初始测试 |
| **UAT** | uat | ai-uat.hikoon.com | 用户验收测试 |
| **生产** | prod | api.hikoon.com | 生产环境验证 |
### 如何在不同环境运行
**设置环境变量 DEPLOY_ENV**,然后运行测试:
```bash
# 测试环境
export DEPLOY_ENV=test
python3 api_test.py
# UAT环境
export DEPLOY_ENV=uat
python3 api_test.py
# 生产环境
export DEPLOY_ENV=prod
python3 api_test.py
```
**说明**
- 默认环境为 `test`(如不设置 DEPLOY_ENV)
- IP_HOST 会根据环境从 `env_config.py` 中自动选择
- 可通过环境变量 `IP_HOST` 覆盖配置的地址
---
## 📊 用例管理
### env_scope 列说明
每个用例都有一个 `env_scope` 字段,用于控制在哪些环境中执行该用例。
#### env_scope 取值
| 值 | 含义 | 执行结果 |
|----|------|---------|
| 留空 或 `all` | 所有环境执行 | test✅ / uat✅ / prod✅ |
| `test` | 仅测试环境 | test✅ / uat⏭️ / prod⏭️ |
| `uat` | 仅UAT环境 | test⏭️ / uat✅ / prod⏭️ |
| `prod` | 仅生产环境 | test⏭️ / uat⏭️ / prod✅ |
| `test,uat` | 测试+UAT | test✅ / uat✅ / prod⏭️ |
| `test,prod` | 测试+生产 | test✅ / uat⏭️ / prod✅ |
#### 添加 env_scope 列
**首次使用**,需要在 Excel 表格中手动添加 `env_scope` 列:
1. 打开 `api_cases.xlsx`
2. 在最后一列后添加新列
3. 列头命名为:`env_scope`
4. 为所有数据行填入相应的值
5. 保存文件
#### 编辑 env_scope 值
根据用例特性设置相应的环境范围
### is_run 字段
原有的 `is_run` 字段仍然保留:
- `is_run = 0`**留空** → 执行该用例(受env_scope影响)
- `is_run = 1` → 在**所有环境**都跳过该用例
**执行优先级**`is_run = 1` → 跳过 → 检查 env_scope → 决定是否执行
---
## ✅ 响应验证
### expected_response 字段
支持多种关系运算符,提供灵活的响应验证。
#### 支持的运算符
- `$eq`: 等于
- `$ne`: 不等于
- `$gt`: 大于
- `$gte`: 大于等于
- `$lt`: 小于
- `$lte`: 小于等于
- `$in`: 值在列表中
- `$nin`: 值不在列表中
- `$contains`: 字符串包含
- `$not_contains`: 字符串不包含
- `$regex`: 正则表达式匹配
- `$range`: 范围判断 [min, max]
#### 使用示例
```json
{
"code": {"$ne": 500},
"count": {"$gt": 0},
"status": {"$in": ["ok", "done"]},
"msg": {"$contains": "success"},
"email": {"$regex": "^.*@.*$"},
"age": {"$range": [18, 100]}
}
```
---
## 🏃 本地运行
### 基础运行
```bash
# 1. 激活虚拟环境
source venv/bin/activate # Linux/Mac
# 或
.\venv\Scripts\activate # Windows
# 2. 设置环境(可选,默认test)
export DEPLOY_ENV=test
# 3. 运行测试
python3 api_test.py
```
---
## 🔗 Jenkins执行
### 参数化Build
Jenkins中已配置参数化执行,选择不同的环境:
1. 进入Jenkins Job页面
2. 点击 **Build with Parameters**
3. 选择 **DEPLOY_ENV**: test / uat / prod
4. 点击 **Build** 开始执行
---
## 🆘 常见问题
### Q1: 所有用例都被跳过了
**原因**:DEPLOY_ENV 未设置或 env_scope 不匹配
**解决**
```bash
export DEPLOY_ENV=test
python3 api_test.py
```
### Q2: 如何在生产环境禁用某些用例
设置 `env_scope = test,uat`(只在测试和UAT执行)
### Q3: 支持哪些 env_scope 值的组合
支持:all / test / uat / prod 及其组合(用逗号分隔)
### Q4: is_run 和 env_scope 的关系
`is_run=1` 时总是跳过;`is_run=0` 时检查 `env_scope`
### Q5: 如何提取变量供后续用例使用
`extract_vars` 列中定义:`user_id=data.user_id`
---
## 🎯 最佳实践
### ✅ 推荐做法
1. **明确标记env_scope**:all / test / uat / prod(避免留空)
2. **生产环境谨慎操作**:危险操作设置为 `test,uat`
3. **定期审查配置**:检查过期的env_scope设置
4. **使用关系运算符**:灵活的验证而非硬编码
### ❌ 避免做法
1. **混乱地使用 is_run 和 env_scope**
2. **在生产执行数据修改操作**
3. **忘记为新增用例设置 env_scope**
4. **大小写混乱的值**
---
**祝你使用愉快!** 🎉
No preview for this file type
import requests
import openpyxl
import json
from datetime import datetime
import time
import os
import re
import importlib.util
from jsonpath_ng import parse
import ast
def get_deploy_env():
"""获取当前部署环境,默认为test"""
return os.getenv('DEPLOY_ENV', 'test')
def should_run_in_env(env_scope, current_env):
"""
判断用例是否应在当前环境执行
env_scope 格式:
- 空 或 'all' → 所有环境执行
- 'test' → 仅测试环境
- 'uat' → 仅UAT环境
- 'prod' → 仅生产环境
- 'test,uat' → 测试和UAT环境
- 'test,prod' → 测试和生产环境
"""
if not env_scope or env_scope.strip() == '':
return True
if env_scope.strip().lower() == 'all':
return True
allowed_envs = [e.strip() for e in env_scope.split(',')]
return current_env in allowed_envs
def load_env_vars():
env_vars = {}
# 优先读取env_config.py
if os.path.exists('env_config.py'):
spec = importlib.util.spec_from_file_location('env_config', 'env_config.py')
env = importlib.util.module_from_spec(spec)
spec.loader.exec_module(env)
for k in dir(env):
if not k.startswith('__'):
env_vars[k] = getattr(env, k)
elif os.path.exists('env.txt'):
with open('env.txt', encoding='utf-8') as f:
for line in f:
if '=' in line:
k, v = line.strip().split('=', 1)
env_vars[k.strip()] = v.strip()
return env_vars
def replace_vars(s, env_vars):
if not isinstance(s, str):
return s
prev = None
curr = s
# 最多递归5次,防止死循环
for _ in range(5):
prev = curr
def repl(match):
var = match.group(1)
return str(env_vars.get(var, match.group(0)))
curr = re.sub(r'\{(\w+)\}', repl, prev)
if curr == prev:
break
return curr
def run_api_test(cases):
env_vars = load_env_vars() # 只初始化一次,所有用例共享
current_env = get_deploy_env() # 获取当前环境
results = []
for case in cases:
# 新增:支持is_run字段,等于1跳过,等于0才执行
is_run = str(case.get('is_run', '0')).strip()
if is_run == '1' or is_run == 1:
continue
# 新增:根据env_scope和当前环境判断是否执行
env_scope = str(case.get('env_scope', '')).strip()
if not should_run_in_env(env_scope, current_env):
print(f"⏭️ 跳过用例 case_id: {case.get('case_id')},原因:不在{current_env}环境执行范围内 (env_scope: {env_scope})")
continue
# 变量替换,优先用ip_host+path拼接url
ip_host = replace_vars(case.get('ip_host', ''), env_vars)
path = replace_vars(case.get('path', ''), env_vars)
url = ''
url_error = ''
if ip_host and path:
if not ip_host.startswith('http://') and not ip_host.startswith('https://'):
ip_host = 'https://' + ip_host
url = ip_host.rstrip('/') + '/' + path.lstrip('/')
elif case.get('url', ''):
url = replace_vars(case.get('url', ''), env_vars)
else:
url_error = 'ip_host和path或url参数缺失'
method = case.get('method', '').upper()
params_str = replace_vars(case.get('params', ''), env_vars)
headers_str = replace_vars(case.get('headers', ''), env_vars)
print(f"case_id: {case.get('case_id')}, headers_str: {headers_str}, env_vars: {env_vars}")
expected_code = int(case.get('expected_code', 0) or 0)
expected_msg = case.get('expected_msg', '') if 'expected_msg' in case else ''
expected_response = replace_vars(case.get('expected_response', ''), env_vars)
api_name = case.get('api_name', '')
# 拼接后再统一加前缀,确保url一定带https://
if url and not (url.startswith('http://') or url.startswith('https://')):
url = 'https://' + url.lstrip('/')
# 统一先定义result,保证后续except分支可用
result = {
"case_id": case.get('case_id', ''),
"api_name": api_name,
"is_run": is_run,
"url": url,
"method": method or '参数不完整',
"params": params_str,
"headers": headers_str,
"expected_code": expected_code,
"expected_msg": expected_msg,
"expected_response": expected_response,
"actual_code": 'None',
"actual_response": 'None',
"code_pass": False,
"msg_pass": False,
"response_pass": False,
"test_result": "失败",
"error": url_error or "url或method参数缺失",
"duration_ms": None
}
# 调试输出每条用例的关键信息
print(f"case_id: {case.get('case_id')}, is_run: {is_run}, url: {url}, method: {method}")
print('原始url:', case.get('url'), '替换后url:', url)
# 参数不完整,直接标记失败
if url_error or not url or not method:
print(f"参数校验未通过,case_id: {case.get('case_id')}, url: {url}, method: {method}, url_error: {url_error}")
results.append(result)
continue
# 健壮性增强:params、headers解析异常时直接标记失败
try:
params = json.loads(params_str) if params_str else {}
except Exception as e:
result["error"] = f"params字段不是合法JSON: {e}"
result["test_result"] = "失败"
results.append(result)
continue
try:
headers = json.loads(headers_str) if headers_str else {}
except Exception as e:
result["error"] = f"headers字段不是合法JSON: {e}"
result["test_result"] = "失败"
results.append(result)
continue
try:
start_time = time.time()
timeout_val = 60 # 超时时间60秒
if method == 'GET':
resp = requests.get(url, params=params, headers=headers, timeout=timeout_val)
elif method == 'POST':
resp = requests.post(url, json=params, headers=headers, timeout=timeout_val)
elif method == 'PUT':
resp = requests.put(url, json=params, headers=headers, timeout=timeout_val)
elif method == 'DELETE':
resp = requests.delete(url, json=params, headers=headers, timeout=timeout_val)
else:
result["error"] = f"不支持的请求方法: {method}"
result["test_result"] = "失败"
results.append(result)
continue
end_time = time.time()
duration_ms = int((end_time - start_time) * 1000)
result["duration_ms"] = duration_ms
# 修正resp_json解析
try:
resp_json = resp.json()
except Exception:
try:
resp_json = ast.literal_eval(resp.text)
except Exception:
resp_json = resp.text
result["actual_code"] = resp.status_code
result["actual_response"] = str(resp_json)
result["code_pass"] = resp.status_code == expected_code
result["msg_pass"] = expected_msg in str(resp_json) if expected_msg else None
# 部分字段校验返回内容
if expected_response:
try:
expected_json = json.loads(expected_response)
actual_json = resp_json if isinstance(resp_json, dict) else json.loads(resp_json)
# 运算符检查函数
def check_operator(expected, actual):
"""检查是否满足各种关系运算符条件"""
if not isinstance(expected, dict):
return False
# 检查是否为运算符字典(至少有一个$前缀的key)
has_operator = any(k.startswith('$') for k in expected.keys())
if not has_operator:
return False
for op, value in expected.items():
if not op.startswith('$'):
continue
try:
if op == '$ne': # 不等于
if actual == value:
return False
elif op == '$eq': # 等于
if actual != value:
return False
elif op == '$gt': # 大于
if not (actual > value):
return False
elif op == '$gte': # 大于等于
if not (actual >= value):
return False
elif op == '$lt': # 小于
if not (actual < value):
return False
elif op == '$lte': # 小于等于
if not (actual <= value):
return False
elif op == '$in': # 包含(值在列表中)
if actual not in value:
return False
elif op == '$nin': # 不包含(值不在列表中)
if actual in value:
return False
elif op == '$regex': # 正则表达匹配
if not re.search(value, str(actual)):
return False
elif op == '$contains': # 字符串包含
if value not in str(actual):
return False
elif op == '$not_contains': # 字符串不包含
if value in str(actual):
return False
elif op == '$range': # 范围判断 [min, max]
if not isinstance(value, list) or len(value) != 2:
return False
min_val, max_val = value
if not (min_val <= actual <= max_val):
return False
else:
# 未知运算符视为失败
return False
except (TypeError, ValueError):
# 类型不匹配或运算失败
return False
return True
# 优化check_partial,支持["__NOT_NULL__"]和运算符
def check_partial(expected, actual):
if expected == "__NOT_NULL__":
return actual not in [None, "", [], {}]
if isinstance(expected, list):
# 只要期望是["__NOT_NULL__"],实际list非空即可
if len(expected) == 1 and expected[0] == "__NOT_NULL__":
return isinstance(actual, list) and len(actual) > 0
# 否则递归比对每个元素
if not isinstance(actual, list) or len(expected) != len(actual):
return False
return all(check_partial(e, a) for e, a in zip(expected, actual))
if isinstance(expected, dict) and isinstance(actual, dict):
# 检查是否为运算符字典
has_operator = any(k.startswith('$') for k in expected.keys())
if has_operator:
return check_operator(expected, actual)
# 普通字典递归检查
for k, v in expected.items():
if k not in actual or not check_partial(v, actual[k]):
return False
return True
# 如果期望是运算符字典但actual不是字典,尝试直接用operator检查
if isinstance(expected, dict) and any(k.startswith('$') for k in expected.keys()):
return check_operator(expected, actual)
return expected == actual
result["response_pass"] = check_partial(expected_json, actual_json)
except Exception as e:
result["response_pass"] = False
result["error"] += f" | 返回内容或期望内容不是合法JSON: {e}"
else:
result["response_pass"] = None
# 执行后提取变量(支持多个,分号或换行分隔,支持split/regex表达式)
extract_vars = case.get('extract_vars', '')
if extract_vars and result['actual_response']:
try:
actual_json = None
try:
actual_json = json.loads(result['actual_response'])
except Exception:
try:
actual_json = ast.literal_eval(result['actual_response'])
except Exception:
pass
if actual_json:
for line in extract_vars.replace('\n', ';').split(';'):
if '=' in line:
var, path_expr = line.split('=', 1)
var = var.strip()
path_expr = path_expr.strip()
if not var or not path_expr:
continue
# 支持表达式:jsonpath|split('?')[0] 或 |regex(...)
if '|' in path_expr:
path, expr = path_expr.split('|', 1)
path = path.strip()
expr = expr.strip()
else:
path, expr = path_expr, None
matches = parse(path).find(actual_json)
if matches:
val = matches[0].value
# split表达式
if expr and expr.startswith('split('):
import re
m = re.match(r"split\(['\"](.*?)['\"]\)\[(\d+)\]", expr)
if m:
sep, idx = m.group(1), int(m.group(2))
val = str(val).split(sep)[idx] if sep in str(val) else str(val)
# regex表达式
elif expr and expr.startswith('regex('):
import re
m = re.match(r"regex\((.*)\)", expr)
if m:
pattern = m.group(1)
reg = re.compile(pattern)
reg_match = reg.search(str(val))
if reg_match:
val = reg_match.group(1) if reg_match.groups() else reg_match.group(0)
env_vars[var] = val
except Exception as e:
result['error'] += f' | 提取变量失败: {e}'
# 总体通过条件和error信息优化
error_msgs = []
if not result["code_pass"]:
error_msgs.append(f"实际返回码与期望不符(expected:{expected_code}, actual:{result['actual_code']})")
if expected_response and result["response_pass"] is False:
error_msgs.append("实际返回内容与期望内容不符")
if result["code_pass"] and (result["response_pass"] in [True, None]):
result["test_result"] = "通过"
result["error"] = "" # 用例通过时error置空
else:
result["test_result"] = "失败"
# 只保留本次判定的error信息,前面初始化的url或method参数缺失等只在参数校验未通过时保留
if error_msgs:
result["error"] = " | ".join(error_msgs)
results.append(result)
except requests.Timeout:
result["error"] = "执行超时"
result["test_result"] = "失败"
result["duration_ms"] = 60000
results.append(result)
continue
except Exception as e:
result["error"] = str(e)
result["test_result"] = "异常"
results.append(result)
continue
return results
def write_report_to_excel(results, report_path):
from openpyxl.styles import Font, Alignment, PatternFill
wb = openpyxl.Workbook()
ws = wb.active
ws.title = "测试报告"
headers = [
"case_id", "api_name", "is_run", "method", "url", "params", "headers", "expected_code", "expected_msg", "expected_response",
"actual_code", "actual_response", "code_pass", "msg_pass", "response_pass", "test_result", "duration", "error"
]
ws.append(headers)
# 设置表头样式
for col in range(1, len(headers)+1):
cell = ws.cell(row=1, column=col)
cell.font = Font(bold=True, color="FFFFFF")
cell.fill = PatternFill("solid", fgColor="2D3E50")
cell.alignment = Alignment(horizontal="center", vertical="center")
# 写入内容
for i, r in enumerate(results, 2):
row = [r.get(h, "") for h in headers]
ws.append(row)
# 自动换行
for col in range(1, len(headers)+1):
ws.cell(row=i, column=col).alignment = Alignment(wrap_text=True, vertical="center")
# 失败用例整行标红
if r.get('test_result') == '失败':
for col in range(1, len(headers)+1):
ws.cell(row=i, column=col).fill = PatternFill("solid", fgColor="FFF0F0")
wb.save(report_path)
print(f"测试报告已生成:{report_path}")
def write_report_to_html(results, html_path):
headers = [
"序号", "接口名称", "method", "url", "headers", "params", "expected_code", "expected_response", "actual_code", "actual_response", "duration", "error"
]
total = len(results)
passed = sum(1 for r in results if r.get('test_result') == '通过')
failed = sum(1 for r in results if r.get('test_result') == '失败' or r.get('test_result') == '异常')
total_duration = sum(float(r.get('duration_ms', 0) or 0) for r in results if r.get('duration_ms') is not None)
total_seconds = total_duration / 1000
if total_seconds < 60:
duration_str = f"{total_seconds:.2f}秒"
else:
duration_str = f"{total_seconds/60:.2f}分钟"
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)
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)
task_result = "执行成功" if failed == 0 else "执行失败"
task_color = "green" if failed == 0 else "red"
html = [
'<html><head><meta charset="utf-8"><title>接口自动化测试报告</title>',
'<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>',
'<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>',
'</head><body>',
'<h2 style="text-align:center;margin-top:24px;margin-bottom:10px;font-size:2.2em;">接口自动化测试报告</h2>',
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>',
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>',
'<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>',
'<table id="report-table">'
]
html.append('<thead>')
html.append('<tr>' + ''.join(f'<th>{h}</th>' for h in headers) + '</tr>')
html.append('</thead>')
html.append('<tbody>')
for idx, r in enumerate(results, 1):
is_fail = r.get('test_result') == '失败' or r.get('test_result') == '异常'
tr_class = 'failrow' if is_fail else ''
duration_val = r.get('duration', r.get('duration_ms', 0))
try:
duration_val = int(duration_val)
except:
duration_val = 0
data_fail = '1' if is_fail else '0'
row = [f'<td>{idx}</td>']
row.append(f'<td class="api-name-cell"><div>{r.get("api_name", "")}</div></td>')
row.append(f'<td>{r.get("method", "")}</td>')
row.append(f'<td class="url-cell"><div>{r.get("url", "")}</div></td>')
row.append(f'<td class="headers-cell"><div>{r.get("headers", "")}</div></td>')
row.append(f'<td class="scroll-cell"><div>{r.get("params", "")}</div></td>')
row.append(f'<td>{r.get("expected_code", "")}</td>')
row.append(f'<td class="scroll-cell"><div>{r.get("expected_response", "")}</div></td>')
row.append(f'<td>{r.get("actual_code", "")}</td>')
row.append(f'<td class="scroll-cell"><div>{r.get("actual_response", "")}</div></td>')
duration_class = "duration-strong" if duration_val > 3000 else ("duration-warn" if duration_val > 1000 else "")
row.append(f'<td class="{duration_class}">{duration_val}</td>')
row.append(f'<td class="error-cell"><div>{r.get("error", "")}</div></td>')
html.append(f'<tr class="{tr_class}" data-fail="{data_fail}" data-duration="{duration_val}">' + ''.join(row) + '</tr>')
html.append('</tbody>')
html.append('</table>')
html.append('<div class="back-to-top-btn" onclick="scrollToTop()" title="回到顶部">↑</div>')
html.append('</body></html>')
with open(html_path, 'w', encoding='utf-8') as f:
f.write('\n'.join(html))
print(f"HTML测试报告已生成:{html_path}")
def read_cases_from_excel(file_path, sheet_name='Sheet1'):
wb = openpyxl.load_workbook(file_path)
ws = wb[sheet_name]
cases = []
headers = [cell.value for cell in ws[1]]
for row in ws.iter_rows(min_row=2, values_only=True):
case = dict(zip(headers, row))
cases.append(case)
return cases
if __name__ == "__main__":
import sys
# 删除旧的测试报告
for f in os.listdir('.'):
if (f.startswith('api_test_report_') and f.endswith('.xlsx')) or (f.startswith('api_test_report_') and f.endswith('.html')):
try:
os.remove(f)
except Exception:
pass
# 支持多个Excel文件
excel_files = [f for f in os.listdir('.') if f.startswith('api_cases') and f.endswith('.xlsx')]
all_passed = True # 标记所有用例是否都通过
for excel_path in excel_files:
deploy_env = get_deploy_env()
report_path = f"api_test_report_{excel_path.replace('.xlsx', '')}_{deploy_env}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
html_path = report_path.replace('.xlsx', '.html')
cases = read_cases_from_excel(excel_path)
print(f'读取到的用例({excel_path}):', cases)
results = run_api_test(cases)
print(f'最终results({excel_path}):', results)
write_report_to_excel(results, report_path)
write_report_to_html(results, html_path)
# 检查是否有失败用例
failed_count = sum(1 for r in results if r.get('test_result') in ['失败', '异常'])
if failed_count > 0:
all_passed = False
print(f"\n⚠️ {excel_path}: 有 {failed_count} 个用例执行失败")
# 根据结果返回退出码
if all_passed:
print("\n✅ 所有测试用例运行成功!")
sys.exit(0) # Jenkins: 执行成功
else:
print("\n❌ 有测试用例执行失败,请查看报告详情!")
sys.exit(1) # Jenkins: 执行失败
version: '3.8'
services:
api-test-runner:
build:
context: .
dockerfile: Dockerfile
container_name: api-test-${DEPLOY_ENV:-test}
environment:
- DEPLOY_ENV=${DEPLOY_ENV:-test}
- IP_HOST=${IP_HOST:-}
- PYTHONUNBUFFERED=1
volumes:
- ./api_cases.xlsx:/app/api_cases.xlsx:ro
- ./env_config.py:/app/env_config.py:ro
- ./reports:/app
networks:
- api-test-network
deploy:
resources:
limits:
cpus: '1'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
restart: no
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
networks:
api-test-network:
driver: bridge
import os
import random
import string
from datetime import datetime
# 多环境IP配置
DEPLOY_ENV = os.getenv('DEPLOY_ENV', 'test')
IP_HOST_MAP = {
'test': 'ai-test.hikoon.com',
'uat': 'ai-uat.hikoon.com',
'prod': 'api.hikoon.com'
}
IP_HOST = os.getenv('IP_HOST', IP_HOST_MAP.get(DEPLOY_ENV, 'ai-test.hikoon.com'))
# 当前日期(格式:YY-MM-DD)
CURRENT_DATE = datetime.now().strftime('%y-%m-%d')
def gen_email():
prefix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=8))
domain = random.choice(['@qq.com', '@163.com', '@gmail.com', '@test.com'])
return prefix + domain
RANDOM_EMAIL = gen_email()
requests==2.31.0
openpyxl==3.10.10
jsonpath-ng==1.6.0