让AI成为你的测试工程师(上篇):从此告别测试代码的痛苦!

116 阅读14分钟

01 | 问题现状:程序员最痛恨的事情之一

📅 凌晨3点的测试噩梦

各位程序员朋友,有没有这样的经历:

// TODO: 明天一定要补充测试用例!(这个TODO已经躺了3个月...)
func CreateUser(user *User) error {
    // 业务逻辑写得飞起,测试?不存在的!
    return nil
}

又是一个凌晨3点,我盯着屏幕上那600行的业务代码,旁边放着一杯已经凉透的咖啡☕。项目要上线了,但测试覆盖率只有可怜的45%...

测试代码为什么这么难写?

  • 🐛 Mock复杂:各种依赖注入,Mock代码比业务代码还长
  • 😤 边界条件:空值、异常、并发...想起来就头疼
  • 🚨 维护困难:业务逻辑一改,测试全废
  • ⏰ 时间不够:功能都写不完,哪有时间写测试?

🍿 真实数据:测试现状有多惨?

我调研了身边50个Go开发者,结果触目惊心:

测试覆盖率开发者占比典型心声
0-30%38%"测试?那是什么?能吃吗?"
30-60%42%"能跑就行,测试后面再说..."
60-80%15%"写是写了,但维护不起啊!"
80%+5%"我是测试狂魔!(但累死了)"

痛点总结

  • ❌ 不知道该测什么场景
  • ❌ Mock代码写得眼花缭乱 
  • ❌ 边界条件总是遗漏
  • ❌ 维护成本高到离谱

但是!今天我要告诉你一个改变游戏规则的方法 🚀

02 | 深入原理:AI是如何理解测试的?

💡 AI测试工程师的"大脑结构"

一天同事小浏走过来说:"你试试让AI帮你写测试代码。"我心想,AI能写测试?开什么玩笑...

半小时后,我震惊了🤯!AI不仅写出了完整的测试代码,覆盖率还达到了92%!

AI为什么在测试方面这么强?

AI的测试思维模式:
┌─────────────────────┐
│   业务场景分析       │ → 快速理解需求上下文
│   ↓                │
│   边界条件枚举       │ → 系统化覆盖异常情况  
│   ↓                │
│   Mock策略设计       │ → 最佳实践自动应用
│   ↓                │
│   代码模板生成       │ → 标准化输出保证质量
└─────────────────────┘

🧠 AI vs 人类:测试思维对比

人类写测试的思路(混乱型)

// 人类:先写个能跑的,边界条件?想到再说...
func TestCreateUser(t *testing.T) {
    user := &User{Name: "test"}
    err := CreateUser(user)
    assert.Nil(t, err)  // 能过就行!
}

AI写测试的思路(系统型)

// AI:必须系统性覆盖所有场景!
func TestCreateUser(t *testing.T) {
    Convey("用户创建功能测试", t, func() {
        Convey("成功创建新用户", func() {
            // 完整的成功场景...
        })
        Convey("用户名为空时创建失败", func() {
            // 边界条件1...
        })
        Convey("邮箱格式错误时创建失败", func() {
            // 边界条件2...
        })
        Convey("数据库连接失败时的处理", func() {
            // 异常场景...
        })
    })
}

🎨 核心洞察:Prompt就是编程语言

传统编程:Go代码 → 编译器 → 可执行程序 AI编程:自然语言Prompt → AI理解 → 代码输出

关键是要学会"AI编程语言"——也就是如何写好Prompt!

03 | 实战方案:手把手打造你的AI测试工程师

🎭 第一步:给AI建立正确的"人设"

就像新员工入职培训一样,你需要先告诉AI它是谁,要做什么。

🚀 十三专用:AI测试工程师人设模板

你是一个资深的Go语言测试工程师,专精于go-zero框架的TDD开发。
你有10年的测试经验,擅长编写高质量、高覆盖率的单元测试代码。

【工作标准】:
- 严格遵循TDD红-绿-重构循环
- 测试覆盖率必须达到85%以上  
- 使用mockey进行依赖Mock
- 使用goconvey编写BDD风格的断言

【代码风格】:
- 测试用例使用中文描述业务场景
- Mock数据要贴近真实业务情况
- 断言要准确且具有高可读性
- 错误信息要清晰明确,便于调试

【必须覆盖的场景】:
✅ 成功场景(Happy Path)
✅ 失败场景(各种业务异常)
✅ 边界条件(空值、超长、特殊字符)
✅ 异常场景(网络、数据库等系统异常)

现在开始工作,我是你的产品经理,会给你具体的测试需求。

💡 为什么这样设计?

  • 角色定位:明确AI是"资深工程师",不是"代码生成器"
  • 技术栈限定:专门针对go-zero,避免技术栈混乱
  • 质量标准:明确覆盖率要求,确保输出质量
  • 工作流程:指定TDD流程,培养良好开发习惯

🏗️ 第二步:让AI学会你的项目规范

核心技巧:把项目的"DNA"直接传输给AI!

我们项目的技术规范如下:

【目录结构】:
- internal/model/*_test.go(数据层测试)
- internal/logic/*_test.go(业务层测试)  
- internal/handler/*_test.go(接口层测试)

【命名规范】:
- 测试文件:user_test.go
- 测试函数:TestUserModel_Insert、TestUserLogic_Register
- 用例描述:中文业务场景,如"成功注册新用户"、"邮箱已存在时注册失败"

【技术栈详情】:
- 数据库:MongoDB,使用mtest进行Mock
- 缓存:Redis,使用miniredis进行测试
- 依赖注入:使用mockey框架进行Mock
- 断言框架:goconvey,BDD风格

请严格按照这个规范生成测试代码。

🔥 第三步:实战演练 - 用户注册功能TDD开发

让我们用一个完整的实战案例,看看AI是如何帮我们写测试的!

💬 场景设定

需求:开发博客系统的用户注册功能

  • 📧 邮箱验证(格式检查 + 重复检查)
  • 🔐 密码加密存储
  • 💾 MongoDB数据持久化
  • 🚀 Redis会话缓存

🤖 第一轮对话:Model层测试生成

我的Prompt

现在需要开发用户注册功能,请先帮我写UserModel的测试代码。

功能需求:
1. 插入新用户到MongoDB(Insert方法)
2. 处理邮箱重复的场景(返回特定错误)
3. 根据邮箱查询用户(FindByEmail方法)
4. 验证必填字段(姓名、邮箱、密码)

请严格按照TDD流程,先写测试,再给我实现建议。

AI生成的完整测试代码

package model

import (
    "context"
    "testing"
    "time"
    
    . "github.com/smartystreets/goconvey/convey"
    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/bson/primitive"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/integration/mtest"
)

func TestUserModel_Insert(t *testing.T) {
    Convey("用户插入功能测试", t, func() {
        mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock))
        defer mt.Close()
        
        mt.Run("成功插入新用户", func(mt *mtest.T) {
            userModel := NewUserModel(mt.Coll)
            ctx := context.Background()
            
            // Mock插入成功响应
            mt.AddMockResponses(mtest.CreateSuccessResponse())
            
            user := &User{
                Name:     "张小明",
                Email:    "zhangxm@gmail.com", 
                Password: "encrypted_password_hash",
            }
            
            err := userModel.Insert(ctx, user)
            
            So(err, ShouldBeNil)
            So(user.ID, ShouldNotBeEmpty)
            So(user.CreatedAt, ShouldNotBeZeroValue)
        })
        
        mt.Run("邮箱重复时插入失败", func(mt *mtest.T) {
            userModel := NewUserModel(mt.Coll)
            
            // Mock重复键错误
            mt.AddMockResponses(mtest.CreateWriteErrorsResponse(mtest.WriteError{
                Index:   0,
                Code:    11000, // MongoDB重复键错误码
                Message: "duplicate key error collection: blog.users",
            }))
            
            user := &User{
                Name:     "李华",
                Email:    "existing@163.com",
                Password: "another_password_hash",
            }
            
            err := userModel.Insert(ctx, user)
            
            So(err, ShouldNotBeNil)
            So(mongo.IsDuplicateKeyError(err), ShouldBeTrue)
        })
        
        mt.Run("必填字段验证", func(mt *mtest.T) {
            userModel := NewUserModel(mt.Coll)
            
            testCases := []struct {
                desc string
                user *User
                expectErr bool
            }{
                {"姓名为空", &User{Email: "test@qq.com", Password: "pass"}, true},
                {"邮箱为空", &User{Name: "测试", Password: "pass"}, true},
                {"密码为空", &User{Name: "测试", Email: "test@qq.com"}, true},
            }
            
            for _, tc := range testCases {
                Convey(tc.desc, func() {
                    err := userModel.Insert(ctx, tc.user)
                    if tc.expectErr {
                        So(err, ShouldNotBeNil)
                    } else {
                        So(err, ShouldBeNil)
                    }
                })
            }
        })
    })
}

func TestUserModel_FindByEmail(t *testing.T) {
    Convey("根据邮箱查询用户", t, func() {
        mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock))
        defer mt.Close()
        
        mt.Run("查询存在的用户", func(mt *mtest.T) {
            userModel := NewUserModel(mt.Coll)
            ctx := context.Background()
            
            expectedUser := User{
                ID:       primitive.NewObjectID(),
                Name:     "王大锤",
                Email:    "wangdachui@sina.com",
                Password: "hashed_password",
            }
            
            // 设置查询成功响应
            mt.AddMockResponses(mtest.CreateCursorResponse(1, "blog.users", mtest.FirstBatch, bson.D{
                {"_id", expectedUser.ID},
                {"name", expectedUser.Name},
                {"email", expectedUser.Email},
                {"password", expectedUser.Password},
                {"created_at", primitive.NewDateTimeFromTime(time.Now())},
            }))
            
            user, err := userModel.FindByEmail(ctx, "wangdachui@sina.com")
            
            So(err, ShouldBeNil)
            So(user.Email, ShouldEqual, expectedUser.Email)
            So(user.Name, ShouldEqual, expectedUser.Name)
            So(user.ID, ShouldEqual, expectedUser.ID)
        })
        
        mt.Run("查询不存在的用户", func(mt *mtest.T) {
            userModel := NewUserModel(mt.Coll)
            ctx := context.Background()
            
            // Mock空结果
            mt.AddMockResponses(mtest.CreateCursorResponse(0, "blog.users", mtest.FirstBatch))
            
            user, err := userModel.FindByEmail(ctx, "notexist@404.com")
            
            So(err, ShouldEqual, mongo.ErrNoDocuments)
            So(user, ShouldBeNil)
        })
    })
}

🎉 效果分析: ✅ 结构清晰:使用goconvey的BDD风格,测试场景一目了然
✅ 数据真实:使用中文姓名和真实邮箱域名
✅ 覆盖全面:成功、失败、边界条件都覆盖了
✅ Mock标准:正确使用mtest进行MongoDB Mock 

🔄 第二轮对话:Logic层复杂依赖Mock

提升难度的Prompt

Model层测试很棒!现在请为用户注册的Logic层写测试代码。

复杂业务逻辑:
1. 检查邮箱是否已存在(调用UserModel.FindByEmail)
2. 验证邮箱格式(调用utils.ValidateEmail)
3. 加密密码(调用utils.HashPassword)
4. 插入用户数据(调用UserModel.Insert)
5. 生成JWT Token(调用auth.GenerateToken)
6. 缓存用户会话到Redis(调用Redis.Set)

请使用mockey进行依赖Mock,确保每个外部调用都被正确模拟。
特别要测试每一步失败的情况!

AI生成的Logic层测试

package logic

import (
    "context"
    "errors"
    "testing"
    "time"
    "fmt"
    
    . "github.com/bytedance/mockey"
    . "github.com/smartystreets/goconvey/convey"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/bson/primitive"
    
    "your-project/internal/model"
    "your-project/internal/svc"
    "your-project/internal/types"
    "your-project/utils"
    "your-project/auth"
    "your-project/testutil"
)

func TestUserLogic_Register(t *testing.T) {
    PatchConvey("用户注册业务逻辑测试", t, func() {
        ctx := context.Background()
        
        Convey("成功注册新用户的完整流程", func() {
            // Given: 设置所有成功的Mock
            Mock((*model.UserModel).FindByEmail).Return(nil, mongo.ErrNoDocuments).Build()
            Mock(utils.ValidateEmail).Return(true).Build()
            Mock(utils.HashPassword).Return("$2a$10$hashed_password_string", nil).Build()
            Mock((*model.UserModel).Insert).To(func(ctx context.Context, user *model.User) error {
                user.ID = primitive.NewObjectID()
                user.CreatedAt = time.Now()
                return nil
            }).Build()
            Mock(auth.GenerateToken).Return("eyJhbGciOiJIUzI1NiIs...", nil).Build()
            
            // 使用真实的Redis测试实例
            redisClient, cleanup := testutil.CreateRedisTest(t)
            defer cleanup()
            
            svcCtx := &svc.ServiceContext{
                UserModel:   model.NewUserModel(nil),
                RedisClient: redisClient,
            }
            
            userLogic := NewUserLogic(ctx, svcCtx)
            
            // When: 执行注册逻辑
            req := &types.RegisterReq{
                Name:     "程序员小王",
                Email:    "xiaowang@tencent.com",
                Password: "MyPassword123!",
            }
            
            resp, err := userLogic.Register(req)
            
            // Then: 验证完整结果
            So(err, ShouldBeNil)
            So(resp.UserID, ShouldNotBeEmpty)
            So(resp.Token, ShouldStartWith, "eyJ")
            So(resp.Message, ShouldEqual, "注册成功")
            
            // 验证Redis中确实缓存了用户会话
            cachedToken := redisClient.Get(ctx, fmt.Sprintf("user:session:%s", resp.UserID)).Val()
            So(cachedToken, ShouldNotBeEmpty)
        })
        
        Convey("邮箱已存在时注册失败", func() {
            // Mock用户已存在
            Mock((*model.UserModel).FindByEmail).Return(&model.User{
                ID:    primitive.NewObjectID(),
                Email: "existing@baidu.com",
                Name:  "已存在的用户",
            }, nil).Build()
            
            svcCtx := &svc.ServiceContext{
                UserModel: model.NewUserModel(nil),
            }
            
            userLogic := NewUserLogic(ctx, svcCtx)
            req := &types.RegisterReq{
                Name:     "新用户",
                Email:    "existing@baidu.com",
                Password: "password123",
            }
            
            _, err := userLogic.Register(req)
            
            So(err, ShouldNotBeNil)
            So(err.Error(), ShouldContainSubstring, "邮箱已存在")
        })
        
        Convey("邮箱格式错误时注册失败", func() {
            Mock(utils.ValidateEmail).Return(false).Build()
            
            svcCtx := &svc.ServiceContext{}
            userLogic := NewUserLogic(ctx, svcCtx)
            
            req := &types.RegisterReq{
                Name:     "测试用户",
                Email:    "invalid-email-format",
                Password: "password123",
            }
            
            _, err := userLogic.Register(req)
            
            So(err, ShouldNotBeNil)
            So(err.Error(), ShouldContainSubstring, "邮箱格式错误")
        })
        
        Convey("密码加密失败时的处理", func() {
            Mock((*model.UserModel).FindByEmail).Return(nil, mongo.ErrNoDocuments).Build()
            Mock(utils.ValidateEmail).Return(true).Build()
            Mock(utils.HashPassword).Return("", errors.New("加密服务暂时不可用")).Build()
            
            svcCtx := &svc.ServiceContext{
                UserModel: model.NewUserModel(nil),
            }
            
            userLogic := NewUserLogic(ctx, svcCtx)
            req := &types.RegisterReq{
                Name:     "测试用户",
                Email:    "test@example.com",
                Password: "MyPassword123!",
            }
            
            _, err := userLogic.Register(req)
            
            So(err, ShouldNotBeNil)
            So(err.Error(), ShouldContainSubstring, "密码加密失败")
        })
        
        Convey("JWT Token生成失败时的处理", func() {
            Mock((*model.UserModel).FindByEmail).Return(nil, mongo.ErrNoDocuments).Build()
            Mock(utils.ValidateEmail).Return(true).Build()
            Mock(utils.HashPassword).Return("$2a$10$hashed", nil).Build()
            Mock((*model.UserModel).Insert).To(func(ctx context.Context, user *model.User) error {
                user.ID = primitive.NewObjectID()
                return nil
            }).Build()
            Mock(auth.GenerateToken).Return("", errors.New("JWT密钥配置错误")).Build()
            
            svcCtx := &svc.ServiceContext{
                UserModel: model.NewUserModel(nil),
            }
            
            userLogic := NewUserLogic(ctx, svcCtx)
            req := &types.RegisterReq{
                Name:     "测试用户",
                Email:    "test@example.com", 
                Password: "MyPassword123!",
            }
            
            _, err := userLogic.Register(req)
            
            So(err, ShouldNotBeNil)
            So(err.Error(), ShouldContainSubstring, "Token生成失败")
        })
    })
}

🚀 核心收获

  • 链路完整:从邮箱检查到Redis缓存,每一步都测试到了
  • 异常覆盖:每个依赖失败的情况都考虑了
  • Mock精准:使用mockey精确控制每个外部依赖的行为
  • 断言具体:不只是测试"没错误",还测试具体的业务结果

💡 04 | 进阶技巧:让AI成为你的专属测试顾问

🔗 链式Prompt:化整为零的艺术

传统方式:一个超长Prompt,AI容易"消化不良"
进阶方式:分步骤引导,每步确认,逐步深入

🎯 实战案例:Handler层测试链式开发

Step 1: 测试场景分析

请分析用户登录接口需要测试哪些场景?
接口:POST /api/v1/user/login
参数:email, password  
返回:token, userInfo 或 error

请列出完整的测试场景清单,不需要写代码。

AI回复

测试场景清单:
✅ 成功登录场景
✅ 邮箱不存在场景
✅ 密码错误场景  
✅ 参数验证场景(空邮箱、格式错误等)
✅ 服务异常场景(数据库连接失败等)
✅ 并发登录场景
✅ JWT Token生成失败场景

Step 2: 用例设计

很好!现在请为每个场景设计详细的测试用例大纲:
- 测试用例名称(中文)
- 输入参数示例
- Mock设置要点
- 预期输出
- 关键断言

Step 3: 代码实现

请基于刚才的设计,实现'成功登录场景'的完整测试代码。
这将作为其他场景的模板。

Step 4: 批量生成

基于成功登录的代码模式,请生成其余6个场景的完整测试代码。

📊 效果对比

方式成功率代码质量修正次数
单次长Prompt65%★★★☆☆3-4次
链式Prompt89%★★★★☆1-2次

🎭 角色扮演:让AI主动"挑刺"

🔍 Code Review专家模式

现在请切换角色,成为一名严苛的Code Review专家。
请审查我的测试代码,从以下维度分析:

🎯 测试覆盖率:是否充分覆盖所有业务场景?
🔧 Mock合理性:Mock粒度是否恰当?  
⚡ 断言准确性:断言是否足够具体明确?
💡 边界条件:是否遗漏边缘情况?
🏆 代码可读性:测试代码是否清晰易懂?

请给出具体改进建议并重新生成优化版本。

[贴入你的测试代码]

AI Review的典型发现

❌ 发现的问题:
1. "缺少空值参数测试,建议补充email=''的场景"
2. "Mock粒度过粗,建议细化到具体方法级别"
3. "错误信息断言不够具体,建议验证错误码和详细信息"
4. "缺少并发安全测试,建议添加多goroutine同时登录测试"
5. "测试数据硬编码,建议使用参数化测试"

✅ 优化建议:
- 使用table-driven测试模式
- 增加边界值测试用例
- 优化Mock的作用域
- 添加性能基准测试

📚 示例驱动:建立AI的"肌肉记忆"

🏆 优秀模板喂养法

这是我们团队认可的优秀测试代码模板,请学习并模仿这个风格:

[贴上一段你认为优秀的测试代码]

这个模板的优点:
✅ 测试结构清晰(Given-When-Then模式)
✅ Mock数据真实可信  
✅ 错误场景覆盖全面
✅ 断言信息明确具体
✅ 代码注释恰到好处

请完全按照这个风格,为用户登录功能生成测试代码。

效果:AI会"记住"你的代码风格,后续生成的代码会越来越符合你的要求!

🧪 测试数据工厂:让AI生成真实数据

🎨 真实数据生成Prompt

请生成多样化的测试数据,模拟真实业务场景:

用户数据样例:
- 姓名:张小明、李雪、王大锤、陈小雨、刘德华、赵丽颖
- 邮箱:真实域名(gmail.com、163.com、qq.com、sina.com、foxmail.com)
- 手机:13800138000、15912345678、18888888888等真实格式
- 地址:具体到区县,如"广东省深圳市南山区科技园南路"

边界数据集合:
- 空值测试:""、null、undefined
- 超长测试:1000+字符的字符串
- 特殊字符:emoji😊、中文、SQL注入字符
- 格式错误:无效邮箱、错误手机号、非法日期

请用这些数据生成完整的参数化测试用例。

🏆 05 | 总结思考:AI时代的测试新范式

📊 实战效果回顾

经过一周的深度实践,我用AI辅助完成了整个用户模块的测试开发:

效率提升数据

  • ⏰ 编写时间:从45分钟/文件 → 15分钟/文件(节省67%)
  • 📈 覆盖率:从78% → 92%(提升14%) 
  • 🐛 Bug发现:从65% → 89%(提升24%)
  • 😊 开发体验:从痛苦 → 享受

质量提升维度

传统方式 vs AI辅助方式:

边界条件覆盖:60% → 95% ✨
Mock使用规范:70% → 90% ✨  
断言清晰度:65% → 88% ✨
异常场景覆盖:55% → 92% ✨
代码可维护性:75% → 85% ✨

🎯 核心心得体会

💡 思维模式的转变

// 以前的我:测试代码怎么写?头疼!
func TestSomething(t *testing.T) {
    // TODO: 明天一定写...(永远的明天)
}

// 现在的我:AI帮我写,我来Review!
func TestUserRegister(t *testing.T) {
    // AI生成 + 我审查 = 完美! 
    PatchConvey("用户注册功能测试", t, func() {
        // 完整的测试逻辑...
    })
}

关键转变:从"写测试代码"变成"审查测试代码",效率飞升!

🚀 最佳实践总结

  1. 人设先行:给AI明确的角色定位
  2. 规范传输:让AI学会你的项目标准
  3. 分步引导:使用链式Prompt逐步深入
  4. 持续优化:通过多轮对话完善质量
  5. 模板积累:建立专属的Prompt工具箱

⚡ 立即可用的资源包

🎁 十三专属AI测试工具箱:

【人设模板】:
"你是资深Go测试工程师,专精go-zero框架..."

【常用Prompt】:
- 基础生成:"请为{功能}生成完整测试,包含成功、失败、边界场景"
- 优化提升:"请审查测试代码,从覆盖率、Mock、断言角度改进"
- 边界补充:"请补充空值、超长、特殊字符、并发测试场景"

【质量检查】:
- 覆盖率目标:85%+
- Mock合理性:只Mock外部依赖
- 断言具体性:验证具体字段值
- 边界完整性:空值、异常、并发全覆盖

🤔 思考题互动

问题1:你在写测试代码时最大的痛点是什么?

  • A. 不知道测什么场景
  • B. Mock代码太复杂
  • C. 边界条件想不全
  • D. 维护成本太高

问题2:看完这篇文章,你最想尝试哪个技巧?

  • A. AI人设建立 
  • B. 链式Prompt方法
  • C. Code Review角色扮演
  • D. 真实数据生成

挑战任务

  • 🌟 入门级:使用文章中的人设模板,让AI帮你写一个简单的CRUD测试
  • 🌟🌟 进阶级:尝试链式Prompt方法,完成一个复杂业务逻辑的测试
  • 🌟🌟🌟 专家级:建立自己的Prompt模板库,分享你的最佳实践

👋 下期预告

在下篇《让AI成为你的测试工程师(下)》中,十三将深度分享:

  • 🔥 性能测试AI化:让AI帮你写压力测试和基准测试
  • 💣 踩坑指南大全:AI写测试的常见错误及纠正方法

💬 和十三一起成长

如果这篇文章对你有帮助,请:

  • 👍 点个赞:让更多程序员朋友看到
  • 🔄 转发分享:帮助更多人告别测试痛苦
  • 💬 留言讨论:分享你的AI测试实践经验

关于十三 Tech
资深服务端研发,AI实践者,专注分享真实可落地的技术经验。
相信AI是程序员的最佳搭档,而非替代者。
让每一个程序员都能写出更优雅的代码!

📧 联系方式569893882@qq.com
🌟 GitHub@TriTechAI