Commit 3d6805eb 3d6805ebda4475c4fa68e849e073070c90739446 by chenjing

init commit:API自动化测试

0 parents
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
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
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 **🎉 部署完成!**
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配置完成!**
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"]
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 }
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 }
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**
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 **🚀 不断优化,持续改进!**
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! 🚀**
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 **🎉 祝你使用愉快!如有问题,请查看对应的文档或联系项目维护者。**
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 **祝你使用愉快!** 🎉
No preview for this file type
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: 执行失败
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
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()
1 requests==2.31.0
2 openpyxl==3.10.10
3 jsonpath-ng==1.6.0