@xulthekl/team-flow 0.32.2 → 0.34.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/.claude/always/phase-guard.md +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/marketplace.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/.github/plugin/marketplace.json +2 -2
- package/AGENTS.md +2 -0
- package/CHANGELOG.md +56 -0
- package/CONTRIBUTING.md +44 -0
- package/GEMINI.md +1 -1
- package/INSTALL.md +1 -1
- package/README.md +1 -1
- package/agents/architecture-design.md +1 -34
- package/agents/architecture-reviewer.md +1 -42
- package/agents/bug-investigator.md +1 -37
- package/agents/build-executor.md +1 -22
- package/agents/change-split-auditor.md +1 -42
- package/agents/code-reviewer.md +1 -42
- package/agents/contract-builder.md +1 -22
- package/agents/cross-change-consistency-checker.md +2 -43
- package/agents/need-explorer.md +1 -22
- package/agents/prd-completeness-reviewer.md +1 -47
- package/agents/prototype-builder.md +1 -41
- package/agents/prototype-env-scout.md +1 -26
- package/agents/prototype-reviewer.md +1 -41
- package/agents/release-archivist.md +1 -22
- package/agents/spec-writer.md +1 -22
- package/docs/README_en.md +1 -1
- package/docs/solutions/INDEX.md +1 -0
- package/docs/solutions/cross-phase/2026-08-04-no-summary.md +17 -0
- package/gemini-extension.json +1 -1
- package/hooks/session-start +2 -2
- package/llms.txt +1 -1
- package/package.json +5 -4
- package/plugin.json +1 -1
- package/scripts/lib/conventions-generator.mjs +350 -0
- package/scripts/lib/test-record.mjs +65 -2
- package/skills/e2e/SKILL.md +1 -1
- package/skills/test-strategy/SKILL.md +38 -1
- package/skills/test-strategy/references/integration-test-contracts.md +237 -0
- package/skills/test-strategy/references/integration-test-isolation.md +346 -0
- package/skills/test-strategy/references/test-quality-rules.md +292 -0
- package/skills/workflow-bootstrap/SKILL.md +40 -3
- package/templates/agent-template.md +41 -0
- package/templates/conventions/_manifest.json +39 -0
- package/templates/conventions/glaf4-compliant/java-testing.md +367 -0
- package/templates/conventions/glaf4-compliant/spring-patterns.md +415 -0
- package/templates/conventions/js-testing.md +261 -0
- package/templates/conventions/python-testing.md +333 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: python-testing
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
updated_at: 2026-08-04
|
|
5
|
+
source: team-flow plugin
|
|
6
|
+
description: Python 测试规范(Pytest)
|
|
7
|
+
tech_stack: [python, pytest]
|
|
8
|
+
glaf4_compliant: false
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Python 测试规范
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1. 测试结构
|
|
16
|
+
|
|
17
|
+
### Pytest
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
class TestUserService:
|
|
21
|
+
def test_get_user_returns_user_when_id_exists(self):
|
|
22
|
+
# Arrange
|
|
23
|
+
user_id = 1
|
|
24
|
+
expected_user = {"id": 1, "name": "John"}
|
|
25
|
+
mock_user_repository.find_by_id.return_value = expected_user
|
|
26
|
+
|
|
27
|
+
# Act
|
|
28
|
+
user = user_service.get_user(user_id)
|
|
29
|
+
|
|
30
|
+
# Assert
|
|
31
|
+
assert user == expected_user
|
|
32
|
+
mock_user_repository.find_by_id.assert_called_once_with(user_id)
|
|
33
|
+
|
|
34
|
+
def test_get_user_raises_error_when_id_not_found(self):
|
|
35
|
+
# Arrange
|
|
36
|
+
user_id = 999
|
|
37
|
+
mock_user_repository.find_by_id.return_value = None
|
|
38
|
+
|
|
39
|
+
# Act & Assert
|
|
40
|
+
with pytest.raises(UserNotFoundError, match="User not found"):
|
|
41
|
+
user_service.get_user(user_id)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 命名规范
|
|
45
|
+
|
|
46
|
+
- 测试文件:`test_[module].py`(如 `test_user_service.py`)
|
|
47
|
+
- 测试类:`Test[ClassName]`(如 `TestUserService`)
|
|
48
|
+
- 测试方法:`test_[method]_[scenario]`(如 `test_get_user_returns_user`)
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 2. Mock 规范
|
|
53
|
+
|
|
54
|
+
### unittest.mock
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from unittest.mock import Mock, patch, MagicMock
|
|
58
|
+
|
|
59
|
+
# 创建 Mock
|
|
60
|
+
mock_user_repository = Mock()
|
|
61
|
+
mock_user_repository.find_by_id.return_value = {"id": 1, "name": "John"}
|
|
62
|
+
|
|
63
|
+
# 验证调用
|
|
64
|
+
mock_user_repository.find_by_id.assert_called_once_with(1)
|
|
65
|
+
mock_user_repository.find_by_id.assert_called()
|
|
66
|
+
|
|
67
|
+
# patch 装饰器
|
|
68
|
+
@patch('app.services.user_service.UserRepository')
|
|
69
|
+
def test_get_user(mock_repo_class):
|
|
70
|
+
mock_repo = mock_repo_class.return_value
|
|
71
|
+
mock_repo.find_by_id.return_value = {"id": 1, "name": "John"}
|
|
72
|
+
|
|
73
|
+
user = user_service.get_user(1)
|
|
74
|
+
|
|
75
|
+
assert user == {"id": 1, "name": "John"}
|
|
76
|
+
mock_repo.find_by_id.assert_called_once_with(1)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### pytest-mock
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
def test_get_user(mocker):
|
|
83
|
+
mock_repo = mocker.patch('app.services.user_service.UserRepository')
|
|
84
|
+
mock_repo.find_by_id.return_value = {"id": 1, "name": "John"}
|
|
85
|
+
|
|
86
|
+
user = user_service.get_user(1)
|
|
87
|
+
|
|
88
|
+
assert user == {"id": 1, "name": "John"}
|
|
89
|
+
mock_repo.find_by_id.assert_called_once_with(1)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 3. 断言规范
|
|
95
|
+
|
|
96
|
+
### assert 常用方法
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
# 相等
|
|
100
|
+
assert value == expected
|
|
101
|
+
assert value != unexpected
|
|
102
|
+
|
|
103
|
+
# 真假
|
|
104
|
+
assert value is True
|
|
105
|
+
assert value is False
|
|
106
|
+
assert value is None
|
|
107
|
+
assert value is not None
|
|
108
|
+
|
|
109
|
+
# 包含
|
|
110
|
+
assert item in collection
|
|
111
|
+
assert substring in string
|
|
112
|
+
|
|
113
|
+
# 异常
|
|
114
|
+
with pytest.raises(ExceptionType, match="message"):
|
|
115
|
+
function_that_raises()
|
|
116
|
+
|
|
117
|
+
# 近似
|
|
118
|
+
assert value == pytest.approx(expected, rel=1e-2)
|
|
119
|
+
|
|
120
|
+
# 类型
|
|
121
|
+
assert isinstance(value, str)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### 禁止无意义断言
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
# ❌ 违规
|
|
128
|
+
def test_something():
|
|
129
|
+
assert True
|
|
130
|
+
|
|
131
|
+
# ✅ 正确
|
|
132
|
+
def test_get_user():
|
|
133
|
+
user = get_user(1)
|
|
134
|
+
assert user == {"id": 1, "name": "John"}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## 4. Fixture
|
|
140
|
+
|
|
141
|
+
### 定义 Fixture
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
import pytest
|
|
145
|
+
|
|
146
|
+
@pytest.fixture
|
|
147
|
+
def user_repository():
|
|
148
|
+
return Mock()
|
|
149
|
+
|
|
150
|
+
@pytest.fixture
|
|
151
|
+
def user_service(user_repository):
|
|
152
|
+
return UserService(user_repository)
|
|
153
|
+
|
|
154
|
+
@pytest.fixture
|
|
155
|
+
def sample_user():
|
|
156
|
+
return {"id": 1, "name": "John", "email": "john@example.com"}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### 使用 Fixture
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
def test_get_user(user_service, user_repository, sample_user):
|
|
163
|
+
user_repository.find_by_id.return_value = sample_user
|
|
164
|
+
|
|
165
|
+
user = user_service.get_user(1)
|
|
166
|
+
|
|
167
|
+
assert user == sample_user
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Fixture 作用域
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
@pytest.fixture(scope="session")
|
|
174
|
+
def database():
|
|
175
|
+
# 整个测试会话只创建一次
|
|
176
|
+
db = create_test_database()
|
|
177
|
+
yield db
|
|
178
|
+
db.drop_all()
|
|
179
|
+
|
|
180
|
+
@pytest.fixture(scope="function")
|
|
181
|
+
def user_service(user_repository):
|
|
182
|
+
# 每个测试函数创建一次
|
|
183
|
+
return UserService(user_repository)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## 5. 参数化测试
|
|
189
|
+
|
|
190
|
+
### @pytest.mark.parametrize
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
@pytest.mark.parametrize("input,expected", [
|
|
194
|
+
(1, "one"),
|
|
195
|
+
(2, "two"),
|
|
196
|
+
(3, "three"),
|
|
197
|
+
])
|
|
198
|
+
def test_number_to_word(input, expected):
|
|
199
|
+
assert number_to_word(input) == expected
|
|
200
|
+
|
|
201
|
+
@pytest.mark.parametrize("user_id,expected_name", [
|
|
202
|
+
(1, "John"),
|
|
203
|
+
(2, "Jane"),
|
|
204
|
+
(3, "Bob"),
|
|
205
|
+
])
|
|
206
|
+
def test_get_user(user_id, expected_name, user_service, user_repository):
|
|
207
|
+
user_repository.find_by_id.return_value = {"id": user_id, "name": expected_name}
|
|
208
|
+
|
|
209
|
+
user = user_service.get_user(user_id)
|
|
210
|
+
|
|
211
|
+
assert user["name"] == expected_name
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## 6. 异步测试
|
|
217
|
+
|
|
218
|
+
### pytest-asyncio
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
import pytest
|
|
222
|
+
|
|
223
|
+
@pytest.mark.asyncio
|
|
224
|
+
async def test_get_user_async():
|
|
225
|
+
user = await user_service.get_user_async(1)
|
|
226
|
+
assert user == {"id": 1, "name": "John"}
|
|
227
|
+
|
|
228
|
+
@pytest.mark.asyncio
|
|
229
|
+
async def test_get_user_async_raises_error():
|
|
230
|
+
with pytest.raises(UserNotFoundError):
|
|
231
|
+
await user_service.get_user_async(999)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## 7. 测试数据
|
|
237
|
+
|
|
238
|
+
### Factory 模式
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
import factory
|
|
242
|
+
|
|
243
|
+
class UserFactory(factory.Factory):
|
|
244
|
+
class Meta:
|
|
245
|
+
model = User
|
|
246
|
+
|
|
247
|
+
id = factory.Sequence(lambda n: n + 1)
|
|
248
|
+
name = factory.Faker('name')
|
|
249
|
+
email = factory.Faker('email')
|
|
250
|
+
|
|
251
|
+
# 使用
|
|
252
|
+
def test_get_user():
|
|
253
|
+
user = UserFactory(name="John")
|
|
254
|
+
assert user.name == "John"
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Fixture
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
@pytest.fixture
|
|
261
|
+
def sample_user():
|
|
262
|
+
return User(id=1, name="John", email="john@example.com")
|
|
263
|
+
|
|
264
|
+
@pytest.fixture
|
|
265
|
+
def sample_users():
|
|
266
|
+
return [
|
|
267
|
+
User(id=1, name="John", email="john@example.com"),
|
|
268
|
+
User(id=2, name="Jane", email="jane@example.com"),
|
|
269
|
+
]
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## 8. 测试隔离
|
|
275
|
+
|
|
276
|
+
### 数据库测试
|
|
277
|
+
|
|
278
|
+
```python
|
|
279
|
+
@pytest.fixture
|
|
280
|
+
def database():
|
|
281
|
+
# 创建测试数据库
|
|
282
|
+
db = create_test_database()
|
|
283
|
+
yield db
|
|
284
|
+
# 清理
|
|
285
|
+
db.drop_all()
|
|
286
|
+
|
|
287
|
+
@pytest.fixture
|
|
288
|
+
def session(database):
|
|
289
|
+
# 创建事务
|
|
290
|
+
session = database.session()
|
|
291
|
+
session.begin()
|
|
292
|
+
yield session
|
|
293
|
+
# 回滚
|
|
294
|
+
session.rollback()
|
|
295
|
+
|
|
296
|
+
def test_create_user(session, user_repository):
|
|
297
|
+
user = User(name="John", email="john@example.com")
|
|
298
|
+
user_repository.save(user)
|
|
299
|
+
|
|
300
|
+
found = user_repository.find_by_id(user.id)
|
|
301
|
+
assert found.name == "John"
|
|
302
|
+
# 测试结束后自动回滚
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### 外部服务 Mock
|
|
306
|
+
|
|
307
|
+
```python
|
|
308
|
+
@pytest.fixture
|
|
309
|
+
def mock_external_api(mocker):
|
|
310
|
+
mock = mocker.patch('app.services.external_api_client.ExternalApiClient')
|
|
311
|
+
mock.get_data.return_value = {"status": "ok"}
|
|
312
|
+
return mock
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## 9. 测试质量规则
|
|
318
|
+
|
|
319
|
+
参见 `references/test-quality-rules.md`,主要包括:
|
|
320
|
+
|
|
321
|
+
1. 断言质量规则(missing-meaningful-assertion、weak-assertion-only)
|
|
322
|
+
2. 调试代码残留规则(print)
|
|
323
|
+
3. 测试状态规则(pytest.mark.skip、pytest.mark.xfail)
|
|
324
|
+
4. 测试数据规则(hardcoded-sample-like-value、real-external-url)
|
|
325
|
+
5. 测试结构规则(large-test-file、generic-test-name)
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## 变更记录
|
|
330
|
+
|
|
331
|
+
| 日期 | 版本 | 变更内容 |
|
|
332
|
+
|------|------|---------|
|
|
333
|
+
| 2026-08-04 | v1.0 | 初始版本 |
|