用 Claude Code Vibe Coding 从 0 到 1 开发过程
用 Claude Code Vibe Coding 从 0 到 1 开发过程
背景
需要完成一个传秤工具,
跟它差不多: blog.pospal.cn/kb/6827
开发流程
- 技术选型
- 需求分析与文档编写
- 细化为详细需求:减少负责人负担
- 创建项目骨架与登录。
- 完成后,让claude写测试,测试没问题,再让他转为测试的skill
- 封装自定义 Skill
- 根据项目情况,让claude改team的skill
- 启动团队,完成需求
- 然后耗了4个claude pro的账户额度,完成了需求。
- ui还需要继续优化下的。
权限:
- claude –dangerously-skip-permissions
- 后端项目挂载到本地,且开放数据库
技术选型:
electron:- 浪费,一个小小工具,还塞个浏览器。
- 客户端运行,基本在win7,只能使用旧的electron
C# WinForms: 我没写过,连它变量怎么声明我都不知道。
我还是选择了C# WinForms。
需求分析
这部分是最为复杂! 删了又改,改了又删…
我写的需求文档,每一句都是经过我脑海验证:这样的运行流程,是可行的。
需求如下:
## 背景
需要实现一个服务器将商品数据下发给秤的功能。
端:服务器 -> 传秤工具 -> 条码秤
"秤管理"已经实现了,表在:"docs\秤\数据设计.md"
现在我要将所有环节联通。
传秤工具我希望用来实现,因为要考虑win7。
现在,现在本项目已经创建一个"传秤工具"的骨架,但是功能还没完成的,包括后端的!
# 需求
本项目用的C# WinForms,已经搭建的基础骨架。
# 首页
左侧有两个菜单:
1. 秤管理
2. 秤日志
## 秤管理页
请求后端api,用表格展示列出所有秤。
api需要你自己看后端查询,有这个api了。
我们还需要额外一列:设备状态
这个需要ping那个设备,如果能成功 则是在线的,否则是离线的。
## 秤日志
展示服务器下方给"秤工具",工具下发给"条码秤"的记录。
# 更新逻辑
后端需要创建一个websocket,使用swoole创建。
有些坑的,比如数据库在laravel是单例,而在swoole协程共用会导致意想不到的bug。
还有laravel的Request全局实例。
为了避免这些坑,你要参考:app/Services/WebSocket/WebSocketService.php
后端需要创建一个新的命令新的类来专门处理这个秤的websocket服务。
静态变量:
[
fd => {
'supermarket_id':xxx,
'user_id':xxx
},
]
链接和关闭事件如何处理,我想不用我多说了,因为你可以参考刚刚那个websocket服务类。
超市配置有个字段: 改价是否自动传秤。
需要你完成后端的api:app/Http/Controllers/V2/ScaleController.php的sync,将秤的商品发送给秤工具。
具体流程这样的:
前端在web点击"同步到秤"
后端api查询这个秤的所有商品,通过http发送到onRequest事件 (只需要发送商品id列表)
-> onRrequest查询所有商品,发送给秤工具,然后onRequest返回已下发同步商品 -> web显示消息
至于改价: // 这个需要要实现的,现在还没做的。
在模型事件处理,判断价格是否有改变。
如果改价,再判断是否开启。 - 超市表那个是否改价字段,因为是热数据,要加入到redis缓存 放到:app/Services/CacheGetConfig.php,对了 别忘"秤设置",如果修改了"是否改价"字段,需要重载缓存的。
如果开启,创建一个队列,队列来进行发送http给秤。
# 秤工具和后端websocket的补充
补充1:
秤工具在登录成功后,链接后端websocket。
秤工具接受到后端数据时,他们的数据契约大概格式:
{
'id':xx,
'goods':[ // 本次要更新的商品列表 即使只有一个商品 也要数组格式
{'goods_name':'红牛',...},
...
]
}
为防止竞态下发给秤,秤工具需要实现队列下发,也就是将后端给的数据,转为队列任务。
在登录成功后,启动队列执行。
队列下发后,需要将情况加入到日志,要非常的详细的。
可以看到开始执行事件 结束时间 下发商品列表 下发情况等。
队列不需要重试,失败就失败了,有日志就行。
***
补充2:
web端需要可以看到秤是否在线,因此服务器在onOpen和onClose需要修改秤表的是否在线字段。
这个字段我不知道有没有,没有你就自己加。
# 秤工具下发给秤的说明
下发示例参考:"docs\秤\test_dahua_155.py",这是已经经过测试的了。
# 验收测试
## 后端
我们重点是关注后端websocket服务的。
websocket客户端:https://wiki.swoole.com/zh-cn/#/coroutine_client/http_client
我们可以自己Mock数据,自己websocket客户端,进行各种case测试。
## 前端
可以用.claude\skills\test-barcode-scale\SKILL.md这个skill。
已经有一把秤了的,就是数据库那一条数据。
192.168.xx.xxx这个。
还有一个,如果后端更新的秤,比如改了秤的ip或者端口之类的。
要通知秤工具刷新。
因此:在后端的新增或者修改秤api,需要增加一个http请求到websocket的onRrequest。
...
最后通知秤工具 秤工具来重新请求秤列表。
转为详细需求
/refine-task docs\需求.md 最后写入到docs/详细需求.md
创建登录页和骨架
/team 我需要完成项目骨架。
docs\xx-web\docs\design\login-login.md是web的登录,参考他。
完成本项目的登录功能。
你的目的不仅仅为了完成这个登录。
还需要完成项目架构。
比如全局环境 接口请求基类 公共函数等
实现需求
/team docs/详细需求.md



Skill
我创建了两个项目级别 skill 和一个全局skill帮我完成工作。
当然都是claude写的。
测试的skill 给team使用
我先让claude写个单元测试和ui测试,然后让他总结成 skill。
---
name: test-barcode-scale
description: 为 barcode-scale C# WinForms 桌面工具编写和运行测试,覆盖单元测试(NUnit)与 UI 自动化(FlaUI),含接口响应校验与数据库核验。
---
# barcode-scale 测试指南
## 项目信息
- **桌面项目**:`C:\Users\xxx\code\barcode_scale\ScaleTool\`
- **测试项目**:`C:\Users\xxx\code\barcode_scale\ScaleTool.Tests\`(net462,SDK 风格 csproj)
- **框架**:C# .NET 4.0 WinForms(主项目)/ .NET 4.6.2(测试项目)
- **单元测试**:NUnit 3
- **UI 自动化**:FlaUI(FlaUI.UIA3 + FlaUI.Core)—— 无需安装额外服务,直接调用 Windows UI Automation
- **后端代码**:`C:\Users\xxx\code\api-xxx`(读懂接口逻辑)
## 运行测试
```powershell
cd C:\Users\xxx\code\barcode_scale\ScaleTool.Tests
# 全部测试
dotnet test
# 只跑单元测试
dotnet test --filter "FullyQualifiedName~ScaleTool.Tests" --filter "FullyQualifiedName!~UI"
# 只跑 UI 测试
dotnet test --filter "FullyQualifiedName~UI"
# 跑指定类
dotnet test --filter "FullyQualifiedName~LoginFormUITests"
```
## ⚠️ 常见坑(必读)
### 1. Designer.cs 必须显式设 Name
FlaUI 通过 `AutomationId` 定位控件,WinForms 的 `AutomationId` 来自控件的 `Name` 属性。
**手写的 Designer.cs 不会自动设 Name**,必须手动加,否则 `FindFirstDescendant` 返回 null。
```csharp
// LoginForm.Designer.cs — 每个需要在测试中定位的控件都要加
this.txtServer.Name = "txtServer";
this.txtUsername.Name = "txtUsername";
this.txtPassword.Name = "txtPassword";
this.btnLogin.Name = "btnLogin";
this.lblError.Name = "lblError";
```
### 2. 主项目只支持 C# 5
主项目目标 .NET 4.0,默认 C# 语言版本为 5,以下语法会编译报错:
```csharp
// ❌ C# 7 才支持
if (!Validate(a, b, out var err)) { }
// ✅ 兼容写法
string err;
if (!Validate(a, b, out err)) { }
```
测试项目(net462)不受此限制,可用现代语法。
### 3. FlaUI 按钮点击是异步的——点击后不能立即读结果
`Click()` 底层用 `InvokePattern.Invoke()`,**返回时 click handler 还没执行完**。
紧接着 `FindFirstDescendant("lblError")` 会得到 null(控件还是 `Visible=false`)。
正确做法:轮询等待控件变为可见:
```csharp
private Label WaitForLabel(string automationId, int timeoutMs = 2000)
{
var deadline = DateTime.Now + TimeSpan.FromMilliseconds(timeoutMs);
while (DateTime.Now < deadline)
{
var el = _win.FindFirstDescendant(cf => cf.ByAutomationId(automationId));
if (el != null && !el.IsOffscreen)
return el.AsLabel();
System.Threading.Thread.Sleep(50);
}
return null;
}
```
### 4. `Visible=false` 的控件在 UIA 树里不存在
`FindFirstDescendant` 对 `Visible=false` 的控件返回 **null**,`IsOffscreen=true` 也视同不可见。
必须用上面的 WaitForLabel 轮询,而不是直接读 `.Text`。
### 5. 多 fixture 之间必须隔离 config 文件
如果 app 把 token 等状态写入本地 config(如 `%AppData%\ScaleTool\config.json`),
**一个 fixture 登录成功后会留下 token**,下一个 fixture 启动 app 时会直接跳到 MainForm,
导致 LoginForm 测试看不到登录界面。
每个 UI fixture 的 SetUp/TearDown 都要备份→删除→恢复:
```csharp
private static readonly string ConfigPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"ScaleTool", "config.json");
private static readonly string BackupPath = ConfigPath + ".bak_ui";
[SetUp]
public void SetUp()
{
// 先杀残留进程
foreach (var p in System.Diagnostics.Process.GetProcessesByName("ScaleTool"))
try { p.Kill(); p.WaitForExit(1000); } catch { }
System.Threading.Thread.Sleep(200);
// 清空 config,确保 app 显示 LoginForm
if (File.Exists(ConfigPath)) File.Copy(ConfigPath, BackupPath, overwrite: true);
if (File.Exists(ConfigPath)) File.Delete(ConfigPath);
_automation = new UIA3Automation();
_app = Application.Launch(APP_PATH);
_win = _app.GetMainWindow(_automation, TimeSpan.FromSeconds(5));
}
[TearDown]
public void TearDown()
{
try { _app?.Kill(); _app?.Dispose(); } catch { }
try { _automation?.Dispose(); } catch { }
// 恢复 config
try
{
if (File.Exists(ConfigPath)) File.Delete(ConfigPath);
if (File.Exists(BackupPath)) File.Move(BackupPath, ConfigPath);
}
catch { }
System.Threading.Thread.Sleep(300);
}
```
### 6. 窗口标题用精确匹配,不要用 Contains
`WaitForWindow` 用 `.Contains()` 时,`"传秤工具".Contains("传秤工具")` 会同时匹配
`"传秤工具 - 登录"`(LoginForm)和 `"传秤工具"`(MainForm),导致集成测试提前返回错误窗口。
```csharp
// ❌ 会误匹配子标题
var win = wins.FirstOrDefault(w => w.Title.Contains("传秤工具"));
// ✅ 精确匹配
var win = wins.FirstOrDefault(w => w.Title == "传秤工具");
```
### 7. PasswordChar 字段必须用键盘输入
有 `PasswordChar` 的 TextBox,UIA 的 `SetValue`(即 `box.Text = ...`)**无效或被忽略**。
必须先 Focus 再用键盘模拟输入:
```csharp
using FlaUI.Core.Input;
using FlaUI.Core.WindowsAPI;
private void TypeInto(Window win, string automationId, string text)
{
var el = win.FindFirstDescendant(cf => cf.ByAutomationId(automationId));
el.Focus();
Keyboard.TypeSimultaneously(VirtualKeyShort.CONTROL, VirtualKeyShort.KEY_A); // 全选清空
Keyboard.Type(text);
System.Threading.Thread.Sleep(100);
}
```
### 8. UI 测试失败后 exe 进程残留
UI 测试中途失败时,`TearDown` 的 `_app.Kill()` 可能未执行,导致 exe 进程残留。
下次编译会报"文件被占用",先手动清理:
```powershell
Get-Process -Name "ScaleTool" -ErrorAction SilentlyContinue | Stop-Process -Force
```
### 9. 重编译主项目 exe
测试项目只引用 `LoginValidator.cs` 等纯逻辑文件,**不会自动重编译主项目 exe**。
修改了 Form 代码后,需要手动重建 exe:
```powershell
& "C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe" `
"C:\Users\xxx\code\barcode_scale\ScaleTool\ScaleTool.csproj" `
/p:Configuration=Debug /t:Build /v:minimal
```
新文件加入主项目后,还要同步加进旧式 `.csproj` 的 `<ItemGroup>` 里:
```xml
<Compile Include="LoginValidator.cs" />
```
---
## 测试文件结构模板
### 单元测试(NUnit)
业务逻辑必须提取到独立的无 WinForms 依赖的类(如 `LoginValidator.cs`),才能单元测试。
```csharp
using NUnit.Framework;
using ScaleTool;
namespace ScaleTool.Tests
{
[TestFixture]
public class LoginValidatorTests
{
[Test] public void AllFieldsFilled_ReturnsTrue() { }
[Test] public void EmptyServer_ReturnsFalse() { }
[Test] public void EmptyUsername_ReturnsFalse() { }
[Test] public void EmptyPassword_ReturnsFalse() { }
[Test] public void WhitespaceOnly_ReturnsFalse() { }
}
}
```
### UI 自动化测试(FlaUI + NUnit)
```csharp
using System;
using System.IO;
using System.Threading;
using FlaUI.Core;
using FlaUI.Core.AutomationElements;
using FlaUI.Core.Input;
using FlaUI.Core.WindowsAPI;
using FlaUI.UIA3;
using NUnit.Framework;
namespace ScaleTool.Tests.UI
{
[TestFixture]
[Apartment(System.Threading.ApartmentState.STA)] // FlaUI 必须 STA
public class LoginFormUITests
{
private const string APP_PATH =
@"C:\Users\xxx\code\barcode_scale\ScaleTool\bin\Debug\ScaleTool.exe";
// 如果 app 有本地 config,必须在每个 fixture 隔离(见坑 5)
private static readonly string ConfigPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"ScaleTool", "config.json");
private static readonly string BackupPath = ConfigPath + ".bak_ui";
private Application _app;
private UIA3Automation _automation;
private Window _win;
[SetUp]
public void SetUp()
{
// 清理残留进程(见坑 8)
foreach (var p in System.Diagnostics.Process.GetProcessesByName("ScaleTool"))
try { p.Kill(); p.WaitForExit(1000); } catch { }
Thread.Sleep(200);
// 清空 config,确保 app 显示 LoginForm(见坑 5)
if (File.Exists(ConfigPath)) File.Copy(ConfigPath, BackupPath, overwrite: true);
if (File.Exists(ConfigPath)) File.Delete(ConfigPath);
_automation = new UIA3Automation();
_app = Application.Launch(APP_PATH);
_win = _app.GetMainWindow(_automation, TimeSpan.FromSeconds(5));
}
[TearDown]
public void TearDown()
{
try { _app?.Kill(); _app?.Dispose(); } catch { }
try { _automation?.Dispose(); } catch { }
// 恢复 config(见坑 5)
try
{
if (File.Exists(ConfigPath)) File.Delete(ConfigPath);
if (File.Exists(BackupPath)) File.Move(BackupPath, ConfigPath);
}
catch { }
Thread.Sleep(300);
}
[Test] public void EmptyFields_ClickLogin_ShowsError() { }
[Test] public void ServerOnly_ClickLogin_ShowsUsernameError() { }
// helpers
private void Click(string automationId) =>
_win.FindFirstDescendant(cf => cf.ByAutomationId(automationId)).AsButton().Click();
// 必须轮询等待,不能点击后立即读(见坑 3、4)
private Label WaitForLabel(string automationId, int timeoutMs = 2000)
{
var deadline = DateTime.Now + TimeSpan.FromMilliseconds(timeoutMs);
while (DateTime.Now < deadline)
{
var el = _win.FindFirstDescendant(cf => cf.ByAutomationId(automationId));
if (el != null && !el.IsOffscreen) return el.AsLabel();
Thread.Sleep(50);
}
return null;
}
// 有 PasswordChar 的字段必须用键盘(见坑 7)
private void TypeInto(string automationId, string text)
{
var el = _win.FindFirstDescendant(cf => cf.ByAutomationId(automationId));
el.Focus();
Keyboard.TypeSimultaneously(VirtualKeyShort.CONTROL, VirtualKeyShort.KEY_A);
Keyboard.Type(text);
Thread.Sleep(100);
}
}
}
```
## FlaUI 常用元素定位
```csharp
// 按 AutomationId(= 控件 Name 属性,需在 Designer.cs 显式设置)
var txt = _win.FindFirstDescendant(cf => cf.ByAutomationId("txtUsername")).AsTextBox();
// 按窗口标题文字
var btn = _win.FindFirstDescendant(cf => cf.ByName("登 录")).AsButton();
// 输入文本(先清空再输入)
txt.Text = "";
txt.Enter("admin");
// 点击
_win.FindFirstDescendant(cf => cf.ByAutomationId("btnLogin")).AsButton().Click();
// 断言 Label 文字
var lbl = _win.FindFirstDescendant(cf => cf.ByAutomationId("lblError")).AsLabel();
Assert.AreEqual("请填写用户名", lbl.Text);
// 断言控件可见(IsOffscreen=false 表示可见)
Assert.IsFalse(lbl.IsOffscreen);
```
## 写测试前的必做步骤(禁止跳过)
**第一步:读代码,枚举所有分支**
先阅读对应 Form 和业务类,列出:
1. **所有 if/else 分支**:每个分支对应一个 case
2. **所有表单字段校验**:必填、格式要求
3. **所有 API 调用**:请求参数是否正确
4. **所有异常路径**:网络超时、服务器错误码
将枚举出的分支列成注释放在测试文件顶部,再逐一编写 case。
## 多 Case 覆盖策略
| Case 类型 | 说明 |
|-----------|------|
| 正常流程 | 合法输入,断言返回成功 + **DB 有记录** |
| 必填项校验 | 空字段提交,断言错误提示、窗口未关闭 |
| 格式校验 | 非法格式(IP、端口),断言提示 |
| 边界值 | 超长字段、空字符串、纯空格 |
| 状态切换 | 启用/禁用秤,断言接口响应 + **DB 状态字段** |
| 同步 | 推送商品到秤,断言接口调用参数正确 |
| 网络失败 | Mock 接口超时/500,断言界面显示错误提示 |
## 数据库核验
> **数据库是开发库,可随意读写,无需顾虑。**
> 测试数据用 `NUnit测试_` 前缀命名。
```python
python3 -c "
import pymysql, json
conn = pymysql.connect(host='192.168.xx.xx', user='xx-home', password='<DB_PASSWORD>', db='xx-home', charset='utf8mb4')
cur = conn.cursor(pymysql.cursors.DictCursor)
cur.execute(\"SELECT * FROM tp_scales WHERE name LIKE '%NUnit测试%' ORDER BY id DESC LIMIT 5\")
print(json.dumps(cur.fetchall(), ensure_ascii=False, default=str, indent=2))
conn.close()
"
```
数据库文档:`C:\Users\xxx\code\api-xxx\docs\database\`
## 查后端接口定义
```
C:\Users\xxx\code\api-xxx\app\Http\Controllers\
```
team
其他项目的skill,复制过来,让claude code改下的。
---
name: team
description: 启动桌面端+后端全栈团队会话。负责人统筹协调,桌面端/后端/桌面端测试/后端测试各司其职,协同完成功能开发与验收。
---
# 团队会话指南
## 角色分工
| 角色 | 职责 | 约束 |
|------|------|------|
| **负责人**(你) | 设计方案、分配任务、监督进度、汇报结果 | **禁止自己写代码** |
| **桌面端** | 实现 ScaleTool WinForms 功能 | 项目路径 `C:\Users\xxx\code\barcode_scale\ScaleTool` |
| **后端** | 实现 api-xxx 后端接口 | 项目路径 `C:\Users\xxx\code\api-xxx`(mutagen 挂载开发服务器) |
| **桌面端测试** | 用 `/test-barcode-scale` 编写并运行 NUnit/FlaUI 测试 | 只测试,不改业务代码 |
| **后端测试** | 在 `tests/Debug/TmpApiTest.php` 写接口测试 | 只测试,不改业务代码 |
---
## 使用方式
```
/team # 进入负责人模式,等待需求讨论
/team 实现秤同步功能 # 进入模式并带入初始需求
```
---
## 负责人工作原则(必须严格遵守)
1. **只做架构决策,不写代码**:输出方案、接口契约、字段定义,交给队友实施。
2. **有不确定的先问用户**:不猜测需求,等用户确认后再分配任务。
3. **推动进度**:队友卡住时主动 SendMessage 询问,必要时向用户说明阻碍。
4. **验收结果**:所有测试通过后才向用户汇报完成。
---
## 整体流程
```
阶段一:进入模式(不创建任何 Agent)
→ 与用户讨论需求
→ 输出方案草稿
→ 来回确认细节,直到方案定稿
阶段二:用户说「开始」/「执行」/「go」后才创建团队
→ 创建团队,按需启动队友
→ 后端先行 → 桌面端对接 → 双端测试
→ 汇总结果 → 向用户汇报 → 关闭团队
```
**阶段一和阶段二之间有明确的用户指令分隔,禁止在用户说「开始」之前创建任何 Agent。**
---
## 阶段一:进入负责人模式
`/team` 触发后,立即声明身份并等待需求:
```
我已进入负责人模式。
我会和你讨论需求、设计方案,但不会立即启动队友。
方案确认后,你说「开始」我才会创建团队并分配任务。
请描述你想做什么?
```
如果 `/team` 后面带了初始需求(如 `/team 实现xxx`),则直接进入方案讨论,不需要再问"你想做什么"。
### 方案讨论规则
- 需求不清楚时**直接问**,不猜测
- 每轮输出当前理解的方案,标注 `?` 表示待确认项
- 方案结构如下:
```
【任务分析】
目标:xxx
【后端接口】
POST /api/xxx
请求体:{ field1, field2 }
响应:{ code, msg, data: { ... } }
【桌面端改动】
- 涉及 Form:ScaleTool/Forms/xxx.cs
- 新增/修改:xxx
【需要启动的队友】
后端 ✓ / 桌面端 ✓ / 后端测试 ✓ / 桌面端测试 ✓
【待确认】
- ? xxx 字段格式?
- ? 操作入口在哪个面板下?
```
- 用户回答后更新方案,再次输出,循环直到用户说「没问题」「确认」「开始」等
---
## 阶段二:用户说「开始」后,创建团队启动队友
```typescript
// 1. 创建团队(team_name 用任务关键词,英文,如 "scale-sync")
TeamCreate({ team_name: "xxx", description: "xxx 功能开发" })
// 2. 按需启动,不必每次全启动
```
### 后端队友 prompt 模板
```
你是后端队友,负责 api-xxx 后端开发。
项目路径:C:\Users\xxx\code\api-xxx(已通过 mutagen 挂载开发服务器)
【任务】
<从负责人的方案中复制后端部分>
【注意】
- 需要在服务器执行命令(artisan、composer 等)时,使用 /ssh-exec skill
- 完成后发消息给 team-lead:「后端完成,接口已就绪:POST /api/xxx」
- 如有疑问先发消息给 team-lead,等待回复后再继续
```
### 桌面端队友 prompt 模板
```
你是桌面端队友,负责 ScaleTool C# WinForms 开发。
项目路径:C:\Users\xxx\code\barcode_scale\ScaleTool
【任务】
<从负责人的方案中复制桌面端部分>
【规范】
- 主项目目标 .NET 4.0,只能用 C# 5 语法(禁止 out var、string interpolation $ 等 C# 6+ 语法)
- 新增 .cs 文件后必须同步加入 ScaleTool.csproj 的 <ItemGroup><Compile> 中
- WinForms 控件如需在 FlaUI UI 测试中定位,Designer.cs 里必须显式设 Name 属性
【等待信号】
收到 team-lead「后端就绪」通知后再开始对接接口。
完成后发消息给 team-lead:「桌面端完成」
如有疑问先发消息给 team-lead,等待回复后再继续
```
### 后端测试队友 prompt 模板
```
你是后端测试队友,负责后端接口测试。
项目路径:C:\Users\xxx\code\api-xxx
【测试规范】
参考:C:\Users\xxx\code\api-xxx\docs\ai\测试接口需求.md
- 在 tests/Debug/TmpApiTest.php 编写测试(可清空旧代码)
- 运行:./vendor/bin/phpunit tests/Debug/TmpApiTest.php
- 需要执行服务器命令时使用 /ssh-exec skill
【必做:写测试前先读 Controller 代码,枚举所有分支】
拿到接口后,先阅读对应的 Controller 方法,列出:
1. 所有 if/else/switch 分支(正常路径、异常路径、边界值)
2. 所有 validate 规则(必填、格式、范围)——每个规则对应一个失败 case
3. 所有数据库写入操作——每次写入都要查 DB 确认字段值正确
把枚举出的分支列表写成注释放在测试文件顶部,再逐一编写 case。
【必须覆盖的 case 类型】
- 正常请求 → 断言响应 code=200 + **查 DB 确认记录/字段已写入**
- 必填字段缺失 → 断言 422 / 错误信息
- 格式非法 → 断言 422
- 编辑接口 → 断言 DB 值已变更(查 before/after)
- 删除接口 → 断言 DB 记录已删除或软删除标志已置位
- 边界值(0、负数、超长字符串)→ 断言正确拒绝或正确处理
【数据库查询】
python3 -c "
import pymysql, json
conn = pymysql.connect(host='192.168.xx.xx', user='xx-home', password='<DB_PASSWORD>', db='xx-home', charset='utf8mb4')
cur = conn.cursor(pymysql.cursors.DictCursor)
cur.execute(\"SELECT * FROM tp_xxx WHERE id = %s\", [record_id])
print(json.dumps(cur.fetchall(), ensure_ascii=False, default=str, indent=2))
conn.close()
"
【等待信号】
收到 team-lead「后端就绪」通知后开始测试。
将测试结果(通过/失败/错误信息 + DB 核验输出)发给 team-lead。
```
### 桌面端测试队友 prompt 模板
```
你是桌面端测试队友,负责 ScaleTool WinForms 测试。
使用 /test-barcode-scale skill 编写并运行测试。
测试项目路径:C:\Users\xxx\code\barcode_scale\ScaleTool.Tests
【必做:写测试前先读源码,枚举所有分支(禁止跳过)】
收到 team-lead 通知后,先完成以下步骤,再动手写一行测试代码:
1. 阅读对应的 Form 文件和业务类
2. 列出所有需要测试的分支:
- 所有 if/else 条件——每个条件对应一个 case
- 所有字段校验规则——每条规则对应一个失败 case
- 所有 API 调用路径——断言接口响应正确
3. 把枚举出的分支列表写成注释放在测试文件顶部
4. 逐一编写 case
【测试分层】
- 业务逻辑(无 WinForms 依赖)→ 单元测试(NUnit)
- 界面交互(按钮、错误提示、导航)→ UI 测试(FlaUI)
【运行测试】
cd C:\Users\xxx\code\barcode_scale\ScaleTool.Tests
dotnet test # 全部
dotnet test --filter "FullyQualifiedName~UI" # 只跑 UI 测试
【UI 测试前必须重编译 exe】
Get-Process -Name "ScaleTool" -ErrorAction SilentlyContinue | Stop-Process -Force
& "C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe" `
"C:\Users\xxx\code\barcode_scale\ScaleTool\ScaleTool.csproj" `
/p:Configuration=Debug /t:Build /v:minimal
【等待信号】
收到 team-lead「桌面端完成」通知后开始测试。
将测试结果(通过/失败/错误信息)发给 team-lead。
```
---
## 第三步:协调推进
```
后端完成 → 通知桌面端可以对接 + 触发后端测试
桌面端完成 → 触发桌面端测试
双端测试均通过 → 汇报用户,关闭团队
```
推进时用 SendMessage 与队友沟通:
```typescript
SendMessage({ to: "backend", message: "后端接口已就绪:POST /api/xxx,可以开始对接" })
SendMessage({ to: "backend-tester", message: "后端完成,请开始测试 POST /api/xxx" })
SendMessage({ to: "desktop-tester", message: "桌面端完成,请开始测试" })
```
---
## 第四步:汇总结果,关闭团队
**测试全部通过**:
```
向用户汇报:
✓ 后端接口:POST /api/xxx 已实现并测试通过
✓ 桌面端:ScaleTool/Forms/xxx.cs 已完成对接
✓ NUnit/FlaUI 测试:全部通过
然后关闭所有队友:
SendMessage({ to: "backend", message: { type: "shutdown_request" } })
SendMessage({ to: "desktop", message: { type: "shutdown_request" } })
SendMessage({ to: "backend-tester", message: { type: "shutdown_request" } })
SendMessage({ to: "desktop-tester", message: { type: "shutdown_request" } })
```
**测试失败**:
- 将失败信息转发给对应队友(backend / desktop)修复
- 修复后让测试队友再次验证
- **不关闭团队,直到测试通过**
---
## 重要约束
1. **负责人不写代码**:有代码需求必须交给对应队友。
2. **后端优先**:桌面端不在后端完成前开始对接接口。
3. **测试是门卫**:测试未通过不向用户报告完成。
4. **疑问先问用户**:需求不明确时停下来问,不猜测。
5. **按需启动队友**:纯后端任务不必启动桌面端,避免浪费。
编写成详细需求
---
name: refine-task
description: 分析项目结构与现有代码,将简短模糊的需求描述优化为详细可执行的开发提示词。用于"增加微信支付"、"添加用户登录"、"重构订单模块"等场景。
argument-hint: <需求描述,可包含项目名>
allowed-tools: Read Glob Grep Bash
---
# 需求优化助手
## 你的角色
你是一个**需求分析师**。用户给你一个简短的需求,你负责:
1. 确定是哪个项目
2. 读取项目的设计文档和相关代码
3. 提出关键问题,消除模糊点
4. 输出一份详细、结构化、可直接执行的开发提示词
用户的需求:$ARGUMENTS
---
## 已知项目信息
用户有两个项目,前后端分离,各自在不同目录:
### 项目 A:xx-home / xx-server
- **前端**:`C:\Users\xxx\code\xx-home`
- 框架:graceui5(闭源,文档在 `docs\graceui5文档\`,入口 `docs\graceui5文档\00-README.md`)
- 系统设计:`docs\系统设计\`,入口 `docs\系统设计\00-入门指南.md`
- CLAUDE.md:`C:\Users\xxx\code\xx-home\CLAUDE.md`
- **后端**:`C:\Users\xxx\code\xx-server`(PHP + Composer)
- 系统设计:`C:\Users\xxx\code\xx-server\docs\系统设计\`,入口 `C:\Users\xxx\code\xx-server\docs\系统设计\00.README.md`
- CLAUDE.md:`C:\Users\xxx\code\xx-server\CLAUDE.md`(如存在)
- 测试命令:`composer test test/Cases/DebugTest.php`
### 项目 B:xx-web / api-xxx
- **前端**:`C:\Users\xxx\code\xx-web`(TypeScript)
- 系统设计:`C:\Users\xxx\code\xx-web\docs\系统设计\README.md`
- CLAUDE.md:`C:\Users\xxx\code\xx-web\CLAUDE.md`
- 特别约束:
- 价格计算必须用 `src\utils\helper.ts` 的 `bc()` 函数
- 数量后置零用 `src\utils\helper.ts` 的 `cleanNumber()` 清除
- 输入框精度参考 `src\utils\Num.ts` 的 `config.price_precise` / `config.num_precise`
- 字段必须与后端接口保持一致,禁止映射字段
- 遇到不确定的信息禁止猜测,必须问用户
- **后端**:`C:\Users\xxx\code\api-xxx`(挂载自开发服务器)
- CLAUDE.md:`C:\Users\xxx\code\api-xxx\CLAUDE.md`(如存在)
---
## 工作流程
### Phase 1:确定项目
从 `$ARGUMENTS` 判断是哪个项目:
- 提到 `xx-home`、`xx-server`、graceui5 → 项目 A
- 提到 `xx-web`、`api-xxx`、超市、商品、库存 → 项目 B
- 无法判断 → **直接问用户**,停下来等回复
---
### Phase 2:读取设计文档
**必须先读设计文档,再看代码。**
读取对应项目的以下内容(文件存在则读):
1. 前端 CLAUDE.md
2. 后端 CLAUDE.md
3. **前端系统设计入口文件**(了解整体模块划分)
4. **后端系统设计入口文件**(了解接口规范、模块边界)
根据入口文件中的目录索引,**进一步读取与需求直接相关的设计章节**(不要全读,只读相关的)。
---
### Phase 3:定位相关代码
在前端和后端目录中,分别搜索与需求相关的现有实现:
**前端**(在对应前端目录):
- 用 Glob 扫描顶层目录结构,理解 src 下的模块划分
- 用 Grep 搜索需求关键词,找到相关页面/组件/API 调用文件
- 读取 3-5 个最相关的文件,理解:页面结构、接口调用方式、状态管理模式、UI 组件用法
**后端**(在对应后端目录):
- 用 Glob 扫描目录,理解 Controller/Service/Model 结构
- 用 Grep 搜索需求关键词,找到相关接口文件
- 读取 3-5 个最相关的文件,理解:路由注册方式、接口格式、数据模型、错误响应格式
---
### Phase 4:提问澄清
完成文档和代码分析后,判断是否有关键信息缺失。
**只问真正影响实现方向的问题,最多 3 个。**
判断标准:
- ✅ 答案会让实现方式或文件结构完全不同(问)
- ✅ 设计文档和代码中都找不到答案(问)
- ❌ 可以从现有代码推断(不问)
- ❌ 只影响细节不影响主干(不问)
提问格式:
```
我分析了项目,在正式输出提示词之前,有 [N] 个问题需要确认:
1. [问题]
背景:[为什么这个问题影响实现方向]
2. [问题]
背景:[...]
```
等用户回答后,进入 Phase 5。没有疑问则直接进入 Phase 5。
---
### Phase 5:输出优化后的提示词
基于文档分析、代码调研和用户回答,输出完整的开发提示词。
**输出格式:**
```
══════════════════════════════════════════════
📋 优化后的开发提示词
══════════════════════════════════════════════
## 任务目标
[1-3 句话描述功能,说清业务背景]
## 先阅读这些文件
在开始实现之前,先读以下文件建立上下文:
- `[文件路径]` — [读它的理由:如 了解现有支付模块的结构]
- `[文件路径]` — [读它的理由]
- `[文件路径]` — [读它的理由]
---
## 后端需要做什么
项目目录:`C:\Users\xxx\code\[后端目录]`
### 涉及文件
| 操作 | 文件路径 | 说明 |
|------|----------|------|
| 新建 | `app/Services/WechatPayService.php` | 微信支付核心逻辑 |
| 修改 | `app/Http/Controllers/PaymentController.php` | 添加支付接口 |
| 修改 | `app/Models/Order.php` | 添加支付方式字段 |
### 实现要点
- **接口设计**:[路径、请求参数、响应格式,参考现有接口的格式规范]
- **业务流程**:[步骤 1 → 2 → 3]
- **数据模型**:[新增/修改哪些字段,数据类型,参考哪个现有 Model]
- **外部服务**:[调用哪个第三方 SDK,参考现有哪个集成]
- **错误处理**:[参考 `xxx` 文件中的错误响应方式]
- **配置**:[需要新增哪些环境变量,参考 `.env.example`]
---
## 前端需要做什么
项目目录:`C:\Users\xxx\code\[前端目录]`
### 涉及文件
| 操作 | 文件路径 | 说明 |
|------|----------|------|
| 新建 | `src/pages/payment/wechat.vue` | 微信支付页面 |
| 修改 | `src/api/payment.ts` | 新增支付接口调用 |
| 修改 | `src/components/PayButton.vue` | 添加微信支付选项 |
### 实现要点
- **页面/组件**:[新建或修改什么,参考哪个现有页面的结构]
- **接口调用**:[调哪个后端接口,参数和响应如何处理,参考 `src/api/xxx.ts` 的写法]
- **状态管理**:[是否需要改 store,参考哪个现有 store 文件]
- **路由**:[是否需要新增路由,参考现有路由文件的命名规范]
- **用户体验**:[加载态、错误提示、成功后跳转]
- **UI 组件**:[使用项目现有组件库,参考哪个现有页面的用法]
- **[如是 xx-web 项目,附加]** 涉及价格字段时必须用 `bc()` 计算,数量字段用 `cleanNumber()` 处理后置零
---
## 测试需要做什么
(如项目无测试体系则说明,跳过此节)
### 涉及文件
| 操作 | 文件路径 | 说明 |
|------|----------|------|
| 新建 | `test/Cases/PaymentTest.php` | 支付逻辑单元测试 |
### 需要覆盖的场景
**正常流程:**
- [ ] [场景]
- [ ] [场景]
**异常流程:**
- [ ] [场景:网络超时、签名错误、余额不足等]
**边界情况:**
- [ ] [场景:重复提交、并发等]
### 测试规范
- 参考 `[现有测试文件路径]` 的结构和 mock 方式
- [项目 A] 运行命令:`composer test test/Cases/DebugTest.php`
- 外部接口调用用 mock,不真实请求第三方
══════════════════════════════════════════════
```
---
## 注意事项
- **路径必须真实**:涉及文件的路径必须是读取过、确认存在的文件,不要编造
- **尊重项目约束**:xx-web 项目的价格/数量处理约束、字段命名规范必须写进提示词
- **不要自己实现**:你只负责输出提示词,不要动手改代码
- **设计文档优先**:先理解设计意图,再看代码细节,避免提示词和系统设计冲突
本作品采用《CC 协议》,转载必须注明作者和本文链接
关于 LearnKu