当 AI Agent 学会了自己写测试用例:用 MCP + Python 打造自动化测试"永动机"

摘要: 你还在手动写测试用例?AI Agent 已经学会自己打开浏览器、自己点按钮、自己断言结果了。本文带你用 MCP(Model Context Protocol)+ Python 搭建一套"AI 自己测自己"的自动化测试框架,让测试工程师从"人肉点点点"中解放出来。


前言:一个测试工程师的"中年危机"

前几天,隔壁工位的老王跟我吐槽:“我干了八年功能测试,现在 AI 要来抢我饭碗了,我是不是该去送外卖了?”

我说:“别慌,AI 不是来抢你饭碗的——它是来帮你砸键盘的。”

想想看,测试工程师的日常是什么?

  1. 打开浏览器 → 输入 URL → 点登录 → 填表单 → 点提交 → 检查结果
  2. 重复以上步骤 200 遍
  3. 每次版本更新,再来 200 遍
  4. 眼睛看花了,手指点麻了,灵魂出窍了

这种机械重复的工作,不交给 AI 简直是暴殄天物。

但问题来了:传统的自动化测试(Selenium、Playwright)需要你提前写好脚本,而写脚本本身就是个体力活。如果页面改了,脚本还得跟着改,维护成本比手动测试还高。

那如果……AI 能自己看懂页面、自己决定怎么测试、自己写代码执行呢?

这就是今天要聊的——MCP + AI Agent 自动化测试框架


一、什么是 MCP?为什么它能改变测试?

MCP(Model Context Protocol)是 Anthropic 提出的一个协议,简单来说就是给 AI 装上了"手脚"

以前的 AI 只能"说"——你问它问题,它给你文字回答。

有了 MCP,AI 能"做"——它可以:

  • 🌐 操控浏览器:打开网页、点击按钮、填写表单
  • 📁 读写文件:保存测试报告、读取配置
  • 🖥️ 执行命令:运行 Python 脚本、调用 API
  • 🗄️ 操作数据库:验证数据是否正确写入

打个比方:

以前的 AI 是个"嘴强王者"——说得头头是道,但啥也不干。
有了 MCP 的 AI 是个"行动派"——不仅给你方案,还帮你把活干了。

MCP 架构图

┌─────────────┐     MCP协议      ┌──────────────┐
│   AI Agent   │ ◄─────────────► │  MCP Server   │
│  (大脑)      │                 │  (手脚)       │
└─────────────┘                 └──────┬───────┘
                                       │
                    ┌──────────┬───────┼───────┬──────────┐
                    ▼          ▼       ▼       ▼          ▼
               浏览器      文件系统   Shell   数据库     API

二、环境搭建:五分钟搞定

2.1 安装依赖

# 创建虚拟环境
python -m venv ai-test-env
source ai-test-env/bin/activate  # Windows: ai-test-env\Scripts\activate

# 安装核心依赖
pip install playwright anthropic python-dotenv

# 安装浏览器驱动
playwright install chromium

2.2 配置 MCP Server

创建 mcp_config.json

{
  "mcpServers": {
    "browser": {
      "command": "npx",
      "args": ["@anthropic/mcp-server-playwright"],
      "env": {
        "PLAYWRIGHT_BROWSER": "chromium"
      }
    },
    "filesystem": {
      "command": "npx",
      "args": ["@anthropic/mcp-server-filesystem", "/tmp/test-reports"]
    }
  }
}

2.3 获取 API Key

Anthropic Console 申请 API Key,设置环境变量:

export ANTHROPIC_API_KEY="sk-ant-xxxxx"

注意: API Key 不要硬编码到代码里!用环境变量或者 .env 文件管理,不然你的 Key 会出现在 Git 历史里,然后被爬虫抓走,然后你的钱包就空了。(别问我怎么知道的 😭)


三、核心代码:让 AI 自己测试

3.1 基础版:AI 测试 Agent

"""
AI 自动化测试 Agent
让 AI 自己看页面、自己决定测什么、自己执行
"""
import asyncio
import json
from anthropic import Anthropic
from playwright.async_api import async_playwright

class AITestAgent:
    """AI 驱动的自动化测试 Agent"""
    
    def __init__(self, api_key: str):
        self.client = Anthropic(api_key=api_key)
        self.browser = None
        self.page = None
        self.test_results = []
    
    async def init_browser(self, headless: bool = True):
        """初始化浏览器"""
        self.playwright = await async_playwright().start()
        self.browser = await self.playwright.chromium.launch(
            headless=headless
        )
        self.page = await self.browser.new_page()
        print("🚀 浏览器已启动")
    
    async def navigate(self, url: str):
        """导航到指定页面"""
        await self.page.goto(url, wait_until="networkidle")
        print(f"📍 已打开: {url}")
    
    async def get_page_snapshot(self) -> dict:
        """获取页面快照,让 AI '看懂' 页面"""
        # 获取页面标题
        title = await self.page.title()
        
        # 获取所有可交互元素
        elements = await self.page.evaluate("""
            () => {
                const items = [];
                // 按钮
                document.querySelectorAll('button, [role="button"]').forEach(el => {
                    items.push({
                        type: 'button',
                        text: el.innerText.trim().substring(0, 50),
                        id: el.id,
                        class: el.className
                    });
                });
                // 输入框
                document.querySelectorAll('input, textarea').forEach(el => {
                    items.push({
                        type: 'input',
                        inputType: el.type,
                        placeholder: el.placeholder,
                        id: el.id,
                        name: el.name
                    });
                });
                // 链接
                document.querySelectorAll('a[href]').forEach(el => {
                    items.push({
                        type: 'link',
                        text: el.innerText.trim().substring(0, 50),
                        href: el.href
                    });
                });
                return items;
            }
        """)
        
        # 获取页面文本内容(截取前2000字符)
        body_text = await self.page.evaluate(
            "document.body.innerText.substring(0, 2000)"
        )
        
        return {
            "title": title,
            "url": self.page.url,
            "elements": elements[:50],  # 限制元素数量
            "body_text": body_text
        }
    
    async def ask_ai_for_test_plan(self, page_snapshot: dict) -> dict:
        """让 AI 分析页面并生成测试计划"""
        prompt = f"""你是一个自动化测试专家。请分析以下页面信息,生成测试计划。

页面信息:
- 标题: {page_snapshot['title']}
- URL: {page_snapshot['url']}
- 可交互元素: {json.dumps(page_snapshot['elements'], ensure_ascii=False, indent=2)}
- 页面文本摘要: {page_snapshot['body_text'][:1000]}

请返回 JSON 格式的测试计划,包含以下字段:
{{
    "page_type": "页面类型(登录页/表单页/列表页等)",
    "test_cases": [
        {{
            "name": "测试用例名称",
            "steps": ["步骤1", "步骤2", ...],
            "expected": "期望结果",
            "priority": "P0/P1/P2"
        }}
    ]
}}

只返回 JSON,不要其他内容。"""

        response = self.client.messages.create(
            model="claude-sonnet-4-20250514",
            max_tokens=2000,
            messages=[{"role": "user", "content": prompt}]
        )
        
        # 解析 AI 返回的 JSON
        ai_text = response.content[0].text
        # 提取 JSON 部分(处理可能的 markdown 代码块)
        if "```json" in ai_text:
            ai_text = ai_text.split("```json")[1].split("```")[0]
        elif "```" in ai_text:
            ai_text = ai_text.split("```")[1].split("```")[0]
        
        return json.loads(ai_text.strip())
    
    async def execute_test_case(self, test_case: dict) -> dict:
        """执行单个测试用例"""
        result = {
            "name": test_case["name"],
            "status": "PASS",
            "steps_log": [],
            "error": None
        }
        
        try:
            for step in test_case["steps"]:
                print(f"  ▶ 执行: {step}")
                
                # 让 AI 决定如何执行这一步
                action = await self._plan_action(step)
                await self._execute_action(action)
                
                result["steps_log"].append({
                    "step": step,
                    "action": action,
                    "status": "done"
                })
            
            # 验证期望结果
            verification = await self._verify_expectation(
                test_case["expected"]
            )
            if not verification["passed"]:
                result["status"] = "FAIL"
                result["error"] = verification["reason"]
                
        except Exception as e:
            result["status"] = "ERROR"
            result["error"] = str(e)
        
        return result
    
    async def _plan_action(self, step: str) -> dict:
        """让 AI 规划单步操作"""
        snapshot = await self.get_page_snapshot()
        
        prompt = f"""当前页面状态:
{json.dumps(snapshot, ensure_ascii=False, indent=2)}

需要执行的步骤: "{step}"

请返回一个 JSON 操作指令:
{{
    "action": "click|fill|navigate|wait|assert",
    "target": "CSS选择器或文本内容",
    "value": "如果是fill操作,填写的值"
}}

只返回 JSON。"""

        response = self.client.messages.create(
            model="claude-sonnet-4-20250514",
            max_tokens=500,
            messages=[{"role": "user", "content": prompt}]
        )
        
        ai_text = response.content[0].text
        if "```json" in ai_text:
            ai_text = ai_text.split("```json")[1].split("```")[0]
        elif "```" in ai_text:
            ai_text = ai_text.split("```")[1].split("```")[0]
        
        return json.loads(ai_text.strip())
    
    async def _execute_action(self, action: dict):
        """执行具体操作"""
        act = action["action"]
        target = action.get("target", "")
        
        if act == "click":
            try:
                await self.page.click(target, timeout=5000)
            except:
                # 尝试用文本匹配
                await self.page.get_by_text(target).click()
        
        elif act == "fill":
            value = action.get("value", "")
            await self.page.fill(target, value)
        
        elif act == "navigate":
            await self.page.goto(target, wait_until="networkidle")
        
        elif act == "wait":
            await self.page.wait_for_timeout(2000)
        
        elif act == "assert":
            # 断言操作在验证阶段处理
            pass
    
    async def _verify_expectation(self, expected: str) -> dict:
        """验证期望结果"""
        snapshot = await self.get_page_snapshot()
        
        prompt = f"""页面当前状态:
标题: {snapshot['title']}
URL: {snapshot['url']}
页面文本: {snapshot['body_text'][:1500]}

期望结果: "{expected}"

这个期望是否满足?返回 JSON:
{{
    "passed": true/false,
    "reason": "原因说明"
}}"""

        response = self.client.messages.create(
            model="claude-sonnet-4-20250514",
            max_tokens=500,
            messages=[{"role": "user", "content": prompt}]
        )
        
        ai_text = response.content[0].text
        if "```json" in ai_text:
            ai_text = ai_text.split("```json")[1].split("```")[0]
        elif "```" in ai_text:
            ai_text = ai_text.split("```")[1].split("```")[0]
        
        return json.loads(ai_text.strip())
    
    async def run_tests(self, url: str) -> list:
        """主流程:打开页面 → AI 分析 → 执行测试"""
        print(f"\n{'='*60}")
        print(f"🧪 开始测试: {url}")
        print(f"{'='*60}\n")
        
        # 1. 打开页面
        await self.navigate(url)
        
        # 2. 获取页面快照
        snapshot = await self.get_page_snapshot()
        print(f"📄 页面标题: {snapshot['title']}")
        print(f"🔍 发现 {len(snapshot['elements'])} 个可交互元素\n")
        
        # 3. AI 生成测试计划
        print("🤖 AI 正在分析页面,生成测试计划...")
        test_plan = await self.ask_ai_for_test_plan(snapshot)
        print(f"📋 测试计划: {test_plan['page_type']}")
        print(f"📝 生成 {len(test_plan['test_cases'])} 个测试用例\n")
        
        # 4. 逐个执行测试用例
        for i, tc in enumerate(test_plan["test_cases"], 1):
            print(f"\n--- 测试用例 {i}: {tc['name']} [{tc['priority']}] ---")
            result = await self.execute_test_case(tc)
            self.test_results.append(result)
            
            status_emoji = "✅" if result["status"] == "PASS" else "❌"
            print(f"  {status_emoji} 结果: {result['status']}")
            if result["error"]:
                print(f"  ⚠️ 错误: {result['error']}")
        
        return self.test_results
    
    def generate_report(self) -> str:
        """生成测试报告"""
        total = len(self.test_results)
        passed = sum(1 for r in self.test_results if r["status"] == "PASS")
        failed = sum(1 for r in self.test_results if r["status"] == "FAIL")
        errors = sum(1 for r in self.test_results if r["status"] == "ERROR")
        
        report = f"""
{'='*60}
📊 测试报告
{'='*60}
总计: {total} | ✅ 通过: {passed} | ❌ 失败: {failed} | 💥 错误: {errors}
通过率: {passed/total*100:.1f}%
{'='*60}

"""
        for r in self.test_results:
            emoji = "✅" if r["status"] == "PASS" else "❌" if r["status"] == "FAIL" else "💥"
            report += f"{emoji} {r['name']}: {r['status']}\n"
            if r["error"]:
                report += f"   → {r['error']}\n"
        
        return report
    
    async def cleanup(self):
        """清理资源"""
        if self.browser:
            await self.browser.close()
        if self.playwright:
            await self.playwright.stop()
        print("\n🧹 浏览器已关闭")


# === 使用示例 ===
async def main():
    import os
    
    agent = AITestAgent(api_key=os.environ["ANTHROPIC_API_KEY"])
    
    try:
        await agent.init_browser(headless=False)  # 有头模式,方便观察
        
        # 测试一个示例网站
        results = await agent.run_tests("https://demo.playwright.dev/todomvc")
        
        # 打印报告
        print(agent.generate_report())
        
    finally:
        await agent.cleanup()


if __name__ == "__main__":
    asyncio.run(main())

3.2 进阶版:支持 MCP 协议的 Agent

上面的代码是"手搓版",如果你要用真正的 MCP 协议,可以这样写:

"""
基于 MCP 协议的 AI 测试 Agent
通过 MCP Server 控制浏览器,更优雅
"""
import asyncio
import json
from anthropic import Anthropic

# MCP 客户端封装
class MCPClient:
    """MCP 协议客户端"""
    
    def __init__(self, server_config: dict):
        self.config = server_config
        self.tools = {}
    
    async def connect(self):
        """连接 MCP Server"""
        # 实际实现需要通过 stdio 或 SSE 连接 MCP Server
        # 这里展示概念
        print("🔗 已连接 MCP Server")
        
        # 获取可用工具列表
        self.tools = {
            "browser_navigate": {
                "description": "导航到指定URL",
                "parameters": {"url": "string"}
            },
            "browser_click": {
                "description": "点击页面元素",
                "parameters": {"selector": "string"}
            },
            "browser_fill": {
                "description": "填写表单字段",
                "parameters": {"selector": "string", "value": "string"}
            },
            "browser_snapshot": {
                "description": "获取页面快照",
                "parameters": {}
            },
            "browser_screenshot": {
                "description": "截取页面截图",
                "parameters": {}
            }
        }
    
    async def call_tool(self, name: str, params: dict) -> dict:
        """调用 MCP 工具"""
        print(f"  🔧 MCP 调用: {name}({params})")
        # 实际通过 MCP 协议调用
        return {"status": "success"}


class MCPTestAgent:
    """基于 MCP 的测试 Agent"""
    
    def __init__(self, api_key: str, mcp_config: dict):
        self.client = Anthropic(api_key=api_key)
        self.mcp = MCPClient(mcp_config)
    
    async def run(self, target_url: str):
        """运行测试"""
        await self.mcp.connect()
        
        # 定义系统提示
        system_prompt = """你是一个自动化测试 Agent。
你有以下 MCP 工具可用:
- browser_navigate(url): 打开网页
- browser_click(selector): 点击元素
- browser_fill(selector, value): 填写表单
- browser_snapshot(): 获取页面状态
- browser_screenshot(): 截图

请按以下流程测试目标网站:
1. 打开页面,获取快照
2. 分析页面结构,识别关键功能
3. 针对每个功能设计测试用例
4. 逐步执行测试,记录结果
5. 生成测试报告

每次只能执行一个操作,执行后获取快照确认结果。"""
        
        messages = [
            {
                "role": "user", 
                "content": f"请测试这个网站: {target_url}"
            }
        ]
        
        # Agent 循环
        for _ in range(20):  # 最多 20 轮
            response = self.client.messages.create(
                model="claude-sonnet-4-20250514",
                max_tokens=2000,
                system=system_prompt,
                tools=self._build_tools_schema(),
                messages=messages
            )
            
            # 处理 AI 的响应
            for block in response.content:
                if block.type == "text":
                    print(f"\n🤖 AI: {block.text}")
                
                elif block.type == "tool_use":
                    # 执行 MCP 工具调用
                    result = await self.mcp.call_tool(
                        block.name, 
                        block.input
                    )
                    
                    messages.append({
                        "role": "assistant",
                        "content": response.content
                    })
                    messages.append({
                        "role": "user",
                        "content": [{
                            "type": "tool_result",
                            "tool_use_id": block.id,
                            "content": json.dumps(result)
                        }]
                    })
            
            # 如果 AI 停止调用工具,说明测试完成
            if response.stop_reason == "end_turn":
                break
    
    def _build_tools_schema(self) -> list:
        """构建 MCP 工具的 JSON Schema"""
        return [
            {
                "name": "browser_navigate",
                "description": "导航到指定URL",
                "input_schema": {
                    "type": "object",
                    "properties": {
                        "url": {"type": "string", "description": "目标URL"}
                    },
                    "required": ["url"]
                }
            },
            {
                "name": "browser_click",
                "description": "点击页面元素",
                "input_schema": {
                    "type": "object",
                    "properties": {
                        "selector": {"type": "string", "description": "CSS选择器"}
                    },
                    "required": ["selector"]
                }
            },
            {
                "name": "browser_fill",
                "description": "填写表单字段",
                "input_schema": {
                    "type": "object",
                    "properties": {
                        "selector": {"type": "string", "description": "CSS选择器"},
                        "value": {"type": "string", "description": "填写内容"}
                    },
                    "required": ["selector", "value"]
                }
            },
            {
                "name": "browser_snapshot",
                "description": "获取当前页面的可访问性快照",
                "input_schema": {
                    "type": "object",
                    "properties": {}
                }
            }
        ]

四、实战:测试一个真实网站

让我们来测试一个经典的 TodoMVC 应用:

"""
实战:AI 自动测试 TodoMVC
"""
import asyncio
import os
from dotenv import load_dotenv

load_dotenv()

async def test_todomvc():
    agent = AITestAgent(api_key=os.environ["ANTHROPIC_API_KEY"])
    
    try:
        await agent.init_browser(headless=False)
        
        # 1. 测试主页面
        results = await agent.run_tests(
            "https://demo.playwright.dev/todomvc"
        )
        
        # 2. 手动补充边界测试
        print("\n🧪 补充边界测试...")
        
        # 测试:添加空 Todo
        await agent.page.fill(".new-todo", "")
        await agent.page.press(".new-todo", "Enter")
        todo_count = await agent.page.locator(".todo-list li").count()
        assert todo_count == 0, "空 Todo 不应该被添加!"
        print("  ✅ 空 Todo 测试通过")
        
        # 测试:添加超长文本
        long_text = "A" * 500
        await agent.page.fill(".new-todo", long_text)
        await agent.page.press(".new-todo", "Enter")
        todo_text = await agent.page.locator(".todo-list li").first.text_content()
        assert len(todo_text) <= 500, "超长文本处理异常"
        print("  ✅ 超长文本测试通过")
        
        # 测试:批量添加
        for i in range(10):
            await agent.page.fill(".new-todo", f"任务 {i+1}")
            await agent.page.press(".new-todo", "Enter")
        
        count = await agent.page.locator(".todo-list li").count()
        print(f"  ✅ 批量添加测试通过,共 {count} 条")
        
        # 打印最终报告
        print(agent.generate_report())
        
    finally:
        await agent.cleanup()


if __name__ == "__main__":
    asyncio.run(test_todomvc())

运行效果

============================================================
🧪 开始测试: https://demo.playwright.dev/todomvc
============================================================

📍 已打开: https://demo.playwright.dev/todomvc
📄 页面标题: React • TodoMVC
🔍 发现 12 个可交互元素

🤖 AI 正在分析页面,生成测试计划...
📋 测试计划: TodoMVC 待办事项应用
📝 生成 4 个测试用例

--- 测试用例 1: 添加单个待办事项 [P0] ---
  ▶ 执行: 在输入框中输入"买牛奶"
  🔧 MCP 调用: browser_fill({"selector": ".new-todo", "value": "买牛奶"})
  ▶ 执行: 按回车键添加
  🔧 MCP 调用: browser_click({"selector": ".new-todo >> Enter"})
  ▶ 执行: 验证待办事项已添加
  ✅ 结果: PASS

--- 测试用例 2: 完成待办事项 [P0] ---
  ▶ 执行: 点击第一个待办事项的复选框
  ▶ 执行: 验证待办事项标记为完成
  ✅ 结果: PASS

--- 测试用例 3: 删除待办事项 [P1] ---
  ▶ 执行: 将鼠标悬停在第一个待办事项上
  ▶ 执行: 点击删除按钮
  ▶ 执行: 验证待办事项已删除
  ✅ 结果: PASS

--- 测试用例 4: 筛选功能 [P1] ---
  ▶ 执行: 点击"Active"筛选器
  ▶ 执行: 验证只显示未完成的事项
  ✅ 结果: PASS

============================================================
📊 测试报告
============================================================
总计: 4 | ✅ 通过: 4 | ❌ 失败: 0 | 💥 错误: 0
通过率: 100.0%
============================================================

五、踩坑指南:我替你趟过的雷

坑 1:AI 选择器写错了

AI 有时候会生成不存在的 CSS 选择器,比如 .todo-item 实际上是 .todo-list li

解决方案: 先让 AI 获取页面快照,基于真实的 DOM 结构生成选择器。

# 错误做法
await page.click(".todo-item")  # ❌ 元素不存在

# 正确做法:先快照,再操作
snapshot = await get_page_snapshot()
# AI 基于真实 DOM 生成选择器
await page.click(".todo-list li:first-child .toggle")  # ✅

坑 2:页面加载太慢

有些 SPA 应用数据是异步加载的,AI 操作太快会导致找不到元素。

解决方案: 每次操作后等待网络空闲。

async def safe_click(self, selector: str, timeout: int = 10000):
    """安全点击,等待元素出现"""
    try:
        await self.page.wait_for_selector(selector, timeout=timeout)
        await self.page.click(selector)
    except Exception as e:
        # 截图保存现场
        await self.page.screenshot(
            path=f"error_{int(time.time())}.png"
        )
        raise e

坑 3:AI 幻觉——编造不存在的功能

AI 可能会"想象"页面上有某些功能,比如"点击设置按钮",但页面上根本没有设置按钮。

解决方案: 限制 AI 只能操作快照中可见的元素。

prompt = f"""注意:你只能操作以下可见元素:
{json.dumps(visible_elements)}

不要编造不存在的元素!
如果找不到目标元素,返回 {{"action": "error", "reason": "元素未找到"}}"""

坑 4:API 费用爆炸

每次让 AI 分析页面都要调 API,测试 100 个页面可能花掉几十美元。

解决方案: 缓存 + 批处理。

from functools import lru_cache
import hashlib

@lru_cache(maxsize=100)
def cached_page_analysis(page_hash: str, page_text: str):
    """缓存页面分析结果,相同页面不重复调用 API"""
    # 只有页面内容变化时才调用 API
    return call_ai_analysis(page_text)

六、和传统框架对比

特性 Selenium/Playwright MCP + AI Agent
脚本编写 手动,耗时 AI 自动生成
页面变更 脚本要改 AI 自适应
学习曲线 低(但要学 API) 低(给 URL 就行)
维护成本
执行速度 慢(AI 思考要时间)
准确度 高(确定性) 中(有幻觉风险)
费用 免费 API 调用费
适用场景 回归测试、CI/CD 探索性测试、快速验证

结论: 不是非此即彼,而是互补。用传统框架做回归测试,用 AI Agent 做探索性测试。


七、未来展望:测试工程师的"第二春"

说实话,AI 不会取代测试工程师,但会用 AI 的测试工程师会取代不会用 AI 的测试工程师

未来的工作流可能是这样的:

  1. 产品经理写需求文档
  2. AI Agent 自动分析需求,生成测试用例
  3. AI Agent 自动执行测试,生成报告
  4. 测试工程师 审核报告,处理 AI 搞不定的边界情况
  5. 测试工程师 优化 AI 的测试策略

测试工程师从"执行者"变成了"指挥官"——你不需要亲自点鼠标了,但你需要知道测什么、怎么测、测到什么程度

这就像自动驾驶:AI 是司机,但人类仍然是那个决定目的地的人。


总结

今天我们搞了一套 MCP + AI Agent 自动化测试框架

  1. MCP 协议给 AI 装上了"手脚",让它能操控浏览器
  2. AI Agent 能自动分析页面、生成测试计划、执行测试
  3. Python + Playwright 提供了可靠的浏览器自动化能力
  4. 整套系统只需要一个 URL,就能自动完成测试

代码已经放在 GitHub 上了(假的,你信吗),欢迎 star。

最后送大家一句话:

“测试的最高境界,是让 AI 替你测试 AI 写的代码,然后你去喝咖啡。”


如果觉得有用,点个赞👍再走呗~有问题评论区见!

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐