jsharness 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.harness/README.md +199 -0
- package/.harness/agents/code-reviewer/contract.yaml +64 -0
- package/.harness/agents/developer/contract.yaml +72 -0
- package/.harness/agents/gate-controller/contract.yaml +64 -0
- package/.harness/agents/project-manager/contract.yaml +77 -0
- package/.harness/agents/prompt-templates.md +352 -0
- package/.harness/agents/requirements-analyst/contract.yaml +64 -0
- package/.harness/agents/solution-designer/contract.yaml +75 -0
- package/.harness/agents/tester/contract.yaml +92 -0
- package/.harness/config/models.yaml +67 -0
- package/.harness/dev-map/backend/api-definition.md +131 -0
- package/.harness/dev-map/backend/auth-security.md +131 -0
- package/.harness/dev-map/backend/conventions-java.md +471 -0
- package/.harness/dev-map/backend/conventions.md +192 -0
- package/.harness/dev-map/backend/database.md +106 -0
- package/.harness/dev-map/backend/structure.md +140 -0
- package/.harness/dev-map/decisions.md +275 -0
- package/.harness/dev-map/frontend/api-integration.md +139 -0
- package/.harness/dev-map/frontend/components.md +178 -0
- package/.harness/dev-map/frontend/conventions.md +416 -0
- package/.harness/dev-map/frontend/state-management.md +170 -0
- package/.harness/dev-map/frontend/structure.md +103 -0
- package/.harness/dev-map/overview.md +267 -0
- package/.harness/docs/integration-test-plan.md +248 -0
- package/.harness/docs/team-guidelines/README.md +161 -0
- package/.harness/docs/team-guidelines/arch-team.md +811 -0
- package/.harness/docs/team-guidelines/collaboration.md +556 -0
- package/.harness/docs/team-guidelines/pm-team.md +337 -0
- package/.harness/docs/team-guidelines/qa-team.md +562 -0
- package/.harness/docs/team-guidelines/rd-team.md +714 -0
- package/.harness/docs/training-materials.md +280 -0
- package/.harness/gate/baseline.js +220 -0
- package/.harness/gate/checks/build-gates-frontend.js +152 -0
- package/.harness/gate/checks/build-gates-java.js +155 -0
- package/.harness/gate/checks/build-gates.js +119 -0
- package/.harness/gate/checks/engineering-consistency.js +138 -0
- package/.harness/gate/checks/security-quality.js +129 -0
- package/.harness/gate/checks/static-compliance.js +313 -0
- package/.harness/gate/checks/test-compliance.js +114 -0
- package/.harness/gate/index.js +315 -0
- package/.harness/mcp/config.yaml +435 -0
- package/.harness/rules/global/coding-standard.md +232 -0
- package/.harness/rules/global/commit-convention.md +165 -0
- package/.harness/rules/global/process-discipline.md +192 -0
- package/.harness/rules/global/security-baseline.md +306 -0
- package/.harness/rules/project/frontend-vue3.md +293 -0
- package/.harness/rules/project/java-backend.md +460 -0
- package/.harness/rules/project/web-specific.md +231 -0
- package/.harness/skills/build.md +192 -0
- package/.harness/skills/code-review.md +251 -0
- package/.harness/skills/docker-build.md +227 -0
- package/.harness/skills/docs-update.md +164 -0
- package/.harness/skills/java-build.md +261 -0
- package/.harness/skills/lint-check.md +482 -0
- package/.harness/skills/task-board-maintenance.md +105 -0
- package/.harness/skills/test-api.md +461 -0
- package/.harness/skills/test-e2e.md +431 -0
- package/.harness/skills/test-unit.md +649 -0
- package/.harness/skills/vue-frontend-build.md +344 -0
- package/.harness/specs/quality-feedback/implementation-guide.md +350 -0
- package/.harness/task-board.md +121 -0
- package/.harness/workflow/definition.yaml +504 -0
- package/.harness/workflow/validate.js +320 -0
- package/.harness/workflow/variants.yaml +253 -0
- package/README.md +237 -0
- package/bin/jsharness.js +53 -0
- package/lib/index.mjs +778 -0
- package/package.json +1 -0
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
# API 测试技能 (test-api)
|
|
2
|
+
|
|
3
|
+
> **执行角色**: 测试验证 Agent / CI Pipeline
|
|
4
|
+
> **触发时机**: API 变更后、PR 提交前、每日定时运行
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## API 集成测试执行
|
|
9
|
+
|
|
10
|
+
### 执行命令
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
# 运行 API 集成测试
|
|
14
|
+
npm run test:api
|
|
15
|
+
|
|
16
|
+
# 或使用指定框架
|
|
17
|
+
npx jest --config jest.api.config.js
|
|
18
|
+
npx vitest run --config vitest.api.config.ts
|
|
19
|
+
|
|
20
|
+
# 带环境变量启动
|
|
21
|
+
DATABASE_URL=test://localhost/testdb npm run test:api
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### 环境准备
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# 启动测试依赖服务
|
|
28
|
+
docker compose up -d db redis
|
|
29
|
+
|
|
30
|
+
# 执行数据库迁移(使用测试数据库)
|
|
31
|
+
npm run migrate:test
|
|
32
|
+
|
|
33
|
+
# 播种测试数据
|
|
34
|
+
npm run seed:test
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 测试数据库原则
|
|
38
|
+
|
|
39
|
+
- **隔离性**:每个测试套件使用独立事务,测试结束后回滚
|
|
40
|
+
- **确定性**:种子数据固定,测试结果可重复
|
|
41
|
+
- **轻量化**:只包含必要的关联数据
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 契约测试验证
|
|
46
|
+
|
|
47
|
+
### 目的
|
|
48
|
+
|
|
49
|
+
确保前后端接口约定不被破坏。即使内部实现改变,API 契约保持稳定。
|
|
50
|
+
|
|
51
|
+
### 工具选择
|
|
52
|
+
|
|
53
|
+
| 场景 | 推荐工具 |
|
|
54
|
+
|------|---------|
|
|
55
|
+
| REST API | Pact / Newman (Postman) |
|
|
56
|
+
| GraphQL | Apollo Schema Checking |
|
|
57
|
+
| OpenAPI 规范 | Dredd / Spector |
|
|
58
|
+
|
|
59
|
+
### 契约测试结构
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { pactum } from 'pactum';
|
|
63
|
+
|
|
64
|
+
describe('用户 API 契约测试', () => {
|
|
65
|
+
const baseUrl = 'http://localhost:3000/api';
|
|
66
|
+
|
|
67
|
+
before(() => {
|
|
68
|
+
pactum.request.setBaseUrl(baseUrl);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('GET /api/v1/users/:id 应符合响应契约', async () => {
|
|
72
|
+
await pactum.spec()
|
|
73
|
+
.get('/users/{id}')
|
|
74
|
+
.withPathParams('id', 'user-001')
|
|
75
|
+
.expectStatus(200)
|
|
76
|
+
.expectHeaderContains('content-type', 'application/json')
|
|
77
|
+
.expectJsonMatchStrict({
|
|
78
|
+
code: 0,
|
|
79
|
+
data: {
|
|
80
|
+
id: 'user-001',
|
|
81
|
+
name: pactum.string(), // 任意非空字符串
|
|
82
|
+
email: pactum.like('user@'), // 匹配模式
|
|
83
|
+
createdAt: pactum.isoDateTime(), // ISO 时间格式
|
|
84
|
+
},
|
|
85
|
+
message: pactum.string(),
|
|
86
|
+
timestamp: pactum.r('^\\d+$'), // 正则匹配
|
|
87
|
+
})
|
|
88
|
+
.toss(); // 发送请求并验证
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('POST /api/v1/users 应校验必填字段', async () => {
|
|
92
|
+
await pactum.spec()
|
|
93
|
+
.post('/users')
|
|
94
|
+
.withJson({ name: '' }) // 缺少 email
|
|
95
|
+
.expectStatus(400)
|
|
96
|
+
.expectJsonLike({
|
|
97
|
+
code: 10001, // 参数错误码
|
|
98
|
+
message: 'email is required',
|
|
99
|
+
})
|
|
100
|
+
.toss();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 必须验证的契约维度
|
|
106
|
+
|
|
107
|
+
| 维度 | 验证内容 |
|
|
108
|
+
|------|----------|
|
|
109
|
+
| **HTTP 方法** | GET/POST/PUT/DELETE 使用正确 |
|
|
110
|
+
| **状态码** | 成功(200/201)、客户端错误(4xx)、服务端错误(5xx) |
|
|
111
|
+
| **Content-Type** | 响应格式正确(application/json) |
|
|
112
|
+
| **Response Schema** | 字段名、类型、嵌套结构符合定义 |
|
|
113
|
+
| **分页参数** | page/pageSize/total/count 字段齐全 |
|
|
114
|
+
| **错误格式** | 统一错误响应结构 |
|
|
115
|
+
| **认证头** | Bearer Token 格式正确 |
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## API 测试用例清单模板
|
|
120
|
+
|
|
121
|
+
### 每个 API 端点必须覆盖
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
端点: POST /api/v1/users
|
|
125
|
+
|
|
126
|
+
✅ 正常用例:
|
|
127
|
+
[ ] 所有必填字段齐全 → 201 Created
|
|
128
|
+
[ ] 所有可选字段都提供 → 完整对象返回
|
|
129
|
+
[ ] 含特殊字符(中文/emoji)→ 正确存储
|
|
130
|
+
|
|
131
|
+
❌ 异常用例:
|
|
132
|
+
[ ] 缺少必填字段 → 400 + 明确的错误信息
|
|
133
|
+
[ ] 字段类型错误(字符串传数字)→ 400
|
|
134
|
+
[ ] 字段长度超限 → 400
|
|
135
|
+
[ ] 邮箱格式无效 → 400
|
|
136
|
+
[ ] 重复的唯一字段(如邮箱)→ 409 Conflict
|
|
137
|
+
[ ] 未认证访问 → 401 Unauthorized
|
|
138
|
+
[ ] 权限不足 → 403 Forbidden
|
|
139
|
+
[ ] 请求体非 JSON → 415 Unsupported Media Type
|
|
140
|
+
[ ] 超大 payload → 413 Payload Too Large
|
|
141
|
+
|
|
142
|
+
🔒 安全用例:
|
|
143
|
+
[ ] SQL 注入尝试 → 400(被拦截)
|
|
144
|
+
[ ] XSS payload → 安全存储(转义后)
|
|
145
|
+
[ ] 越权操作(A 用户操作 B 的资源)→ 403
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## API 测试结果输出模板
|
|
149
|
+
|
|
150
|
+
```yaml
|
|
151
|
+
test_result_api:
|
|
152
|
+
timestamp: "2026-05-20T22:00:00Z"
|
|
153
|
+
framework: "pactum" | "jest" | "newman"
|
|
154
|
+
status: pass | fail
|
|
155
|
+
|
|
156
|
+
endpoints_tested: 24
|
|
157
|
+
total_assertions: 342
|
|
158
|
+
|
|
159
|
+
summary:
|
|
160
|
+
passed: 338
|
|
161
|
+
failed: 4
|
|
162
|
+
|
|
163
|
+
contract_compliance:
|
|
164
|
+
breaking_changes: 0 # 破坏性变更数量(门禁关键项)
|
|
165
|
+
new_fields: 2 # 新增字段(向后兼容)
|
|
166
|
+
removed_fields: 0 # 删除字段(阻塞项)
|
|
167
|
+
|
|
168
|
+
failed_assertions:
|
|
169
|
+
- endpoint: "PATCH /api/v1/users/:id/profile"
|
|
170
|
+
assertion: "expect response phone to match pattern"
|
|
171
|
+
actual: '"13800138000"'
|
|
172
|
+
expected: 'pattern: /^1[3-9]\d{9}$/'
|
|
173
|
+
|
|
174
|
+
performance_p95:
|
|
175
|
+
avg_response_time_ms: 85
|
|
176
|
+
max_response_time_ms: 320
|
|
177
|
+
slow_endpoints:
|
|
178
|
+
- path: "/api/v1/reports/export"
|
|
179
|
+
avg_ms: 280
|
|
180
|
+
|
|
181
|
+
baseline_comparison:
|
|
182
|
+
regression: false
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
# ════════════════════════════════════════════════════════
|
|
188
|
+
# Java 后端 API 集成测试(REST Assured + WireMock)
|
|
189
|
+
# ════════════════════════════════════════════════════════
|
|
190
|
+
|
|
191
|
+
> **适用技术栈**: Spring Boot / JDK21
|
|
192
|
+
> **参考规则**: `rules/project/java-backend.md` §4 Controller + §5 Service
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## REST Assured — Java HTTP 测试 DSL
|
|
197
|
+
|
|
198
|
+
### Maven 依赖
|
|
199
|
+
|
|
200
|
+
```xml
|
|
201
|
+
<!-- pom.xml -->
|
|
202
|
+
<dependency>
|
|
203
|
+
<groupId>io.rest-assured</groupId>
|
|
204
|
+
<artifactId>rest-assured</artifactId>
|
|
205
|
+
<version>5.4.0</version>
|
|
206
|
+
<scope>test</scope>
|
|
207
|
+
</dependency>
|
|
208
|
+
<dependency>
|
|
209
|
+
<groupId>io.rest-assured</groupId>
|
|
210
|
+
<artifactId>spring-mock-mvc-module</artifactId>
|
|
211
|
+
<version>5.4.0</version>
|
|
212
|
+
<scope>test</scope>
|
|
213
|
+
</dependency>
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### 核心测试结构
|
|
217
|
+
|
|
218
|
+
```java
|
|
219
|
+
@SpringBootTest
|
|
220
|
+
@AutoConfigureMockMvc
|
|
221
|
+
class UserApiIntegrationTest {
|
|
222
|
+
|
|
223
|
+
@Autowired
|
|
224
|
+
private MockMvc mockMvc;
|
|
225
|
+
|
|
226
|
+
@MockBean
|
|
227
|
+
private UserService userService;
|
|
228
|
+
|
|
229
|
+
@Autowired
|
|
230
|
+
private ObjectMapper objectMapper;
|
|
231
|
+
|
|
232
|
+
@Test
|
|
233
|
+
@DisplayName("POST /api/v1/users — 创建用户成功")
|
|
234
|
+
void shouldCreateUser() throws Exception {
|
|
235
|
+
// === GIVEN (准备) ===
|
|
236
|
+
UserReqVO req = UserReqVO.builder()
|
|
237
|
+
.phone("13800138000").name("张三").build();
|
|
238
|
+
|
|
239
|
+
User created = User.builder().id("user-001").name("张三").build();
|
|
240
|
+
when(userService.createUser(any(UserReqVO.class)))
|
|
241
|
+
.thenReturn(RespVO.success(created));
|
|
242
|
+
|
|
243
|
+
// === WHEN & THEN (执行 + 断言) ===
|
|
244
|
+
mockMvc.perform(post("/api/v1/users")
|
|
245
|
+
.contentType(MediaType.APPLICATION_JSON)
|
|
246
|
+
.content(objectMapper.writeValueAsString(req)))
|
|
247
|
+
.andExpect(status().isCreated()) // 201
|
|
248
|
+
.andExpect(jsonPath("$.code").value(0))
|
|
249
|
+
.andExpect(jsonPath("$.data.id").isNotEmpty())
|
|
250
|
+
.andExpect(jsonPath("$.data.name").value("张三"))
|
|
251
|
+
.andDo(print()); // 打印完整响应(调试用)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
@Test
|
|
255
|
+
@DisplayName("GET /api/v1/users/{id} — 用户存在时返回 200")
|
|
256
|
+
void shouldReturnUserWhenExists() throws Exception {
|
|
257
|
+
// GIVEN
|
|
258
|
+
when(userService.getUserById("user-001"))
|
|
259
|
+
.thenReturn(RespVO.success(
|
|
260
|
+
UserVO.builder().id("user-001").name("李四")
|
|
261
|
+
.status("ACTIVE").build()));
|
|
262
|
+
|
|
263
|
+
// WHEN & THEN
|
|
264
|
+
mockMvc.perform(get("/api/v1/users/user-001"))
|
|
265
|
+
.andExpect(status().isOk())
|
|
266
|
+
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
|
|
267
|
+
.andExpect(jsonPath("$.data.name").value("李四"));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
@Test
|
|
271
|
+
@DisplayName("POST /api/v1/users — 缺少必填字段返回 400")
|
|
272
|
+
void shouldReturn400WhenMissingFields() throws Exception {
|
|
273
|
+
// 缺少 phone 字段
|
|
274
|
+
String invalidJson = "{\"name\":\"\"}";
|
|
275
|
+
|
|
276
|
+
mockMvc.perform(post("/api/v1/users")
|
|
277
|
+
.contentType(MediaType.APPLICATION_JSON)
|
|
278
|
+
.content(invalidJson))
|
|
279
|
+
.andExpect(status().isBadRequest())
|
|
280
|
+
.andExpect(jsonPath("$.code").value(10001)) // 参数错误码
|
|
281
|
+
.andExpect(jsonPath("$.message").isNotEmpty());
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### 契约测试维度检查清单
|
|
287
|
+
|
|
288
|
+
| 维度 | 验证方式 | 示例断言 |
|
|
289
|
+
|------|---------|---------|
|
|
290
|
+
| **HTTP 方法** | `.getMethod()` 匹配 | `.request().method(HttpMethod.POST)` |
|
|
291
|
+
| **状态码** | `.andExpect(status())` | `isOk()` / `isCreated()` / `isBadRequest()` / `isUnauthorized()` |
|
|
292
|
+
| **Content-Type** | header 检查 | `.contentType(MediaType.APPLICATION_JSON)` |
|
|
293
|
+
| **Response Schema** | jsonPath 字段存在性 | `jsonPath("$.data.id").exists()` |
|
|
294
|
+
| **分页结构** | 分页字段完整性 | `jsonPath("$.data.list")` + `jsonPath("$.total")` |
|
|
295
|
+
| **错误格式** | 统一错误响应 | `jsonPath("$.code").isNumber()` + `jsonPath("$.message")` |
|
|
296
|
+
| **认证头** | 未携带 Token 时 | `isUnauthorized()` 或 `isForbidden()` |
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## WireMock — 服务端 Mock
|
|
301
|
+
|
|
302
|
+
### Maven 依赖
|
|
303
|
+
|
|
304
|
+
```xml
|
|
305
|
+
<dependency>
|
|
306
|
+
<groupId>org.wiremock</groupId>
|
|
307
|
+
<artifactId>wiremock-standalone</artifactId>
|
|
308
|
+
<version>3.3.1</version>
|
|
309
|
+
<scope>test</scope>
|
|
310
|
+
</dependency>
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Mock 外部服务
|
|
314
|
+
|
|
315
|
+
```java
|
|
316
|
+
@ExtendWith(WireMockExtension.class)
|
|
317
|
+
@WireMockConfig(wiremockPort = 9090)
|
|
318
|
+
class PaymentServiceIntegrationTest {
|
|
319
|
+
|
|
320
|
+
@RegisterExtension
|
|
321
|
+
static WireMockExtension wireMock = WireMockExtension.newInstance()
|
|
322
|
+
.options(wireMockConfig().port(9090))
|
|
323
|
+
.build();
|
|
324
|
+
|
|
325
|
+
@Test
|
|
326
|
+
@DisplayName("调用支付网关成功")
|
|
327
|
+
void shouldCallPaymentGateway() {
|
|
328
|
+
// Stub:模拟支付网关响应
|
|
329
|
+
stubFor(post(urlEqualTo("/api/pay"))
|
|
330
|
+
.willReturn(aResponse()
|
|
331
|
+
.withStatus(200)
|
|
332
|
+
.withHeader("Content-Type", "application/json")
|
|
333
|
+
.withBody("{\"code\":0,\"tradeNo\":\"T202605210001\"}")));
|
|
334
|
+
|
|
335
|
+
// 执行业务代码
|
|
336
|
+
PayResult result = paymentService.pay(PayRequest.builder()
|
|
337
|
+
.amount(new BigDecimal("100.00")).orderId("ORD-001").build());
|
|
338
|
+
|
|
339
|
+
// 断言
|
|
340
|
+
assertThat(result.getTradeNo()).isEqualTo("T202605210001");
|
|
341
|
+
|
|
342
|
+
// 验证确实调用了外部服务
|
|
343
|
+
verify(postRequestedFor(urlEqualTo("/api/pay"))
|
|
344
|
+
.withRequestBody(containing("100.00")));
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## 数据库迁移测试
|
|
352
|
+
|
|
353
|
+
### Flyway 集成
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
# 迁移命令
|
|
357
|
+
mvn flyway:migrate # 执行所有待执行的迁移
|
|
358
|
+
mvn flyway:info # 查看迁移历史和状态
|
|
359
|
+
mvn flyway:validate # 校验迁移文件一致性
|
|
360
|
+
|
|
361
|
+
# 清理重建(仅开发环境)
|
|
362
|
+
mvn flyway:clean migrate # ⚠️ 会删除所有数据!仅本地使用
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### 迁移脚本规范
|
|
366
|
+
|
|
367
|
+
```sql
|
|
368
|
+
-- 文件命名格式: V{版本号}__{描述}.sql
|
|
369
|
+
-- 例: V20260521_001__add_user_phone_encryption.sql
|
|
370
|
+
|
|
371
|
+
-- ✅ 正确做法:增量 ALTER 脚本
|
|
372
|
+
ALTER TABLE t_user ADD COLUMN phone_encrypted VARCHAR(128) DEFAULT '';
|
|
373
|
+
CREATE INDEX idx_user_phone_encrypted ON t_user(phone_encrypted);
|
|
374
|
+
|
|
375
|
+
-- ❌ 错误做法:DROP 后重建(会丢失数据!)
|
|
376
|
+
-- DROP TABLE t_user;
|
|
377
|
+
-- CREATE TABLE t_user (...);
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## Postman Newman — API 回归测试
|
|
383
|
+
|
|
384
|
+
### 从 CI/CD 运行
|
|
385
|
+
|
|
386
|
+
```bash
|
|
387
|
+
# 安装 newman
|
|
388
|
+
npm install -g newman newman-reporter-htmlextra
|
|
389
|
+
|
|
390
|
+
# 运行集合
|
|
391
|
+
newman run postman_collections/backend-api.json \
|
|
392
|
+
-e postman_environments/staging.json \
|
|
393
|
+
--reporters cli,htmlextra \
|
|
394
|
+
--reporter-htmlextra-export api-test-report.html \
|
|
395
|
+
--bail # 第一个失败即停止
|
|
396
|
+
|
|
397
|
+
# Jenkins/GitLab CI 集成
|
|
398
|
+
# newman run ... --reporters junit --reporter-junit-export results.xml
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### 集合组织建议
|
|
402
|
+
|
|
403
|
+
```
|
|
404
|
+
backend-api.collection/
|
|
405
|
+
├── auth/
|
|
406
|
+
│ ├── POST_login.json
|
|
407
|
+
│ └── POST_refresh-token.json
|
|
408
|
+
├── user/
|
|
409
|
+
│ ├── GET_users-id.json
|
|
410
|
+
│ ├── POST_users.json
|
|
411
|
+
│ └── PUT_users-id-profile.json
|
|
412
|
+
├── order/
|
|
413
|
+
│ ├── GET_orders.json
|
|
414
|
+
│ └── POST_orders.json
|
|
415
|
+
└── health/
|
|
416
|
+
└── GET_health.json
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## Java API 测试结果输出模板
|
|
422
|
+
|
|
423
|
+
```yaml
|
|
424
|
+
test_result_api_java:
|
|
425
|
+
timestamp: "2026-05-20T22:00:00Z"
|
|
426
|
+
framework: "REST Assured (Spring MockMvc) + Newman"
|
|
427
|
+
status: pass | fail
|
|
428
|
+
|
|
429
|
+
endpoints_tested: 32
|
|
430
|
+
total_assertions: 486
|
|
431
|
+
|
|
432
|
+
summary:
|
|
433
|
+
passed: 482
|
|
434
|
+
failed: 4
|
|
435
|
+
|
|
436
|
+
contract_compliance:
|
|
437
|
+
breaking_changes: 0
|
|
438
|
+
new_fields: 1
|
|
439
|
+
removed_fields: 0
|
|
440
|
+
deprecated_fields: 0
|
|
441
|
+
|
|
442
|
+
failed_assertions:
|
|
443
|
+
- endpoint: "PUT /api/v1/users/:id/profile"
|
|
444
|
+
method: "PUT"
|
|
445
|
+
assertion: "expect response.phone to match regex ^1[3-9]\\d{9}$"
|
|
446
|
+
actual: '"13800138000"'
|
|
447
|
+
expected: 'pattern match'
|
|
448
|
+
|
|
449
|
+
security_checks:
|
|
450
|
+
sql_injection_attempt_blocked: true
|
|
451
|
+
xss_payload_sanitized: true
|
|
452
|
+
unauthorized_access_rejected: true
|
|
453
|
+
missing_auth_returns_401: true
|
|
454
|
+
|
|
455
|
+
performance_p95:
|
|
456
|
+
avg_response_time_ms: 65
|
|
457
|
+
max_response_time_ms: 280
|
|
458
|
+
|
|
459
|
+
baseline_comparison:
|
|
460
|
+
regression: false
|
|
461
|
+
```
|