AI驱动单元测试生成:三步工作流提升代码质量与开发效率

news2026/5/10 4:50:11
1. 项目概述用AI为你的代码自动生成单元测试如果你和我一样每天都要和一堆功能函数、组件打交道那么写单元测试这件事大概率是让你又爱又恨的。爱的是它确实能帮你提前发现bug让代码更健壮恨的是写一套覆盖全面的测试用例实在是太耗时了尤其是在项目初期或者面对遗留代码时。很多时候我们不是不想写而是“没时间”写或者觉得“太麻烦”了。最近我在一个名为holasoymalva/AI-Unit-Test-Builder的开源项目中找到了一套非常有意思的解决方案。它不是什么新的测试框架而是一套专门为Cursor IDE设计的.mdc命令文件。简单来说你可以把它理解为一套给 Cursor 内置 AI 的“操作手册”。通过这套手册AI 能像一位经验丰富的测试工程师一样系统性地分析你的源代码并自动生成高质量的单元测试。这套工具的核心价值在于它将“写测试”这个模糊的、依赖个人经验的任务拆解成了一个标准化的、可重复的流水线。你不再需要绞尽脑汁去想“这个函数该测哪些边界条件”AI 会基于代码结构帮你分析出来。整个过程分为三步提取分析 - 生成测试 - 优化重构。无论你是前端开发者在使用 React/Vue 组件还是后端开发者在处理工具函数这套方法都能显著提升你编写测试的效率和质量。接下来我就结合自己的实际使用经验为你详细拆解这套工作流的每一个环节并分享一些踩坑后总结出来的实用技巧。2. 核心工作流拆解从代码到测试的三步法这套 AI 单元测试构建器的精髓就在于其清晰、线性的三步工作流。它不是让 AI 一次性生成所有测试而是通过分阶段、有引导的交互确保生成的测试代码既全面又准确。下面我们来深入看看每一步具体在做什么以及为什么这样的设计是合理的。2.1 第一步代码解析与函数提取任何测试工作的起点都是理解你要测试的对象。第一步extract-functions.mdc的核心任务就是让 AI 扮演一个“代码分析器”的角色。当你把源代码文件比如一个UserService.ts或Button.vue交给它时它会执行一次深度扫描。这个过程具体会分析什么函数签名这是最基础的包括函数名、参数名称、类型、是否可选、默认值、返回值类型。对于 TypeScript 或 JSDoc 注释良好的代码AI 能获得非常准确的信息。依赖关系函数内部是否调用了其他模块的函数、引用了外部 API、或使用了全局状态识别这些依赖是后续生成 Mock模拟的关键。逻辑复杂度通过简单的代码结构分析如条件分支if/else、循环for/while、异常处理try/catchAI 会评估函数的复杂度并据此建议需要重点测试的分支路径。潜在测试场景基于以上分析AI 会初步罗列出“快乐路径”正常输入、“边界情况”空值、极值、非法输入和“错误路径”抛出异常等测试场景。注意这一步的输出通常是一个结构化的 Markdown 文档而不是可执行的测试代码。它的目的是为你和后续的 AI 步骤建立一个清晰的“作战地图”。很多人在初次使用时容易跳过或忽视这一步直接去生成测试结果就是 AI 可能遗漏某些边缘函数或对复杂依赖理解不全。花一两分钟让 AI 先做好分析后续的生成效率会高得多。一个实操命令示例 在 Cursor 的 Agent 聊天框中你可以这样触发请使用 extract-functions.mdc 分析 src/utils/formatDate.tsAI 会读取你的formatDate.ts文件并生成一份分析报告其中可能包含函数formatDate(timestamp: number, format: string ‘YYYY-MM-DD’): string依赖无外部依赖仅使用内置Date对象。复杂度中等涉及日期解析和字符串格式化。建议测试场景正常时间戳、0值、负数、超大数字、非法格式字符串、空格式参数等。2.2 第二步基于分析的测试生成拿到详细的分析报告后第二步generate-unit-test.mdc就开始大显身手了。此时AI 的角色从“分析员”转变为“测试开发工程师”。它会依据第一步的产出针对每一个被识别出的函数 scaffold搭建出完整的测试文件。生成内容通常包括哪些部分测试框架配置与导入根据项目环境自动引入正确的测试框架如 Jest, Vitest、断言库以及可能需要的工具如testing-library/react。Mock 定义如果分析报告指出函数有外部依赖如 HTTP 请求、数据库查询、文件读写AI 会自动生成对应的 Mock 实现。例如用jest.mock(‘../api’)来模拟一个 API 模块。测试套件与用例结构为每个函数创建describe块内部包含多个it或test块。每个用例都会有清晰的描述如‘应该使用默认格式格式化有效时间戳’。具体的断言在用例中会调用被测试函数并对其返回值或副作用如函数被调用、状态更新进行断言。AI 会尝试覆盖第一步中提到的所有场景。Setup 与 Teardown如果需要会生成beforeEach,afterAll等钩子函数来管理测试环境。这一步的交互命令示例 在第一步的分析完成后直接在同一个聊天会话中继续现在请使用 generate-unit-test.mdc基于刚才的分析为 formatDate 函数生成 Jest 单元测试。AI 便会开始工作输出一个完整的formatDate.test.ts文件内容。实操心得在这一步明确指定你使用的测试框架至关重要。虽然.mdc文件有一定通用性但直接告诉 AI “用 Jest 写” 或 “用 Vitest 写”能获得更符合你项目配置、语法更地道的测试代码。此外生成后务必快速浏览一下生成的 Mock 是否合理有时 AI 对复杂依赖的模拟可能需要微调。2.3 第三步测试代码的审查与重构自动生成的代码很少是完美的。第三步refactor-test.mdc的设计理念就是引入“质量保证”环节。让 AI 扮演一个“高级测试评审”对刚刚生成的测试代码进行一轮审查和优化。重构主要关注哪些方面消除重复代码检查多个测试用例中是否存在重复的初始化数据或断言逻辑并将其提取到beforeEach或辅助函数中。改善测试描述让it(‘…’)中的描述更具可读性和业务语义遵循 “Should … when …” 或 “It should …” 的模式。补充遗漏的用例基于代码逻辑可能会发现一些边界情况在第二步被遗漏此时会建议添加。优化 Mock 的粒度和真实性检查 Mock 是否过于粗糙或过于复杂调整其实现以更贴近真实场景同时保持测试的独立性。调整代码结构让测试文件的结构更清晰比如按“正常流”、“边界流”、“错误流”来组织describe块。触发重构的命令 在生成测试代码后继续在聊天中发送请使用 refactor-test.mdc 来优化和重构刚刚生成的测试代码。AI 会给出一个优化后的版本并通常会附上简短的修改说明比如“将重复的用户测试数据提取到了setupTestUser函数中”。注意事项不要盲目接受所有重构建议。AI 的重构可能有时会为了“简洁”而过度抽象或者改变了一些你更喜欢的代码风格。把它当作一个强大的建议工具最终决定权在你手上。我个人的习惯是将重构前后的代码进行对比只采纳那些确实能提升可读性、可维护性且不影响测试意图的改动。3. 环境配置与实战演练了解了理论流程我们来看看如何把它用起来。这套工具的核心是 Cursor IDE 和.mdc文件因此正确的配置是成功的第一步。3.1 项目结构与文件部署首先你需要将下载或克隆的.mdc文件放置到 Cursor 能够识别的位置。虽然理论上可以放在任何地方但遵循一个约定的结构能让管理更轻松。推荐的项目目录结构如下your-project/ ├── .cursor/ │ └── mdc/ # Cursor 的 MDC 命令目录 │ ├── extract-functions.mdc │ ├── generate-unit-test.mdc │ └── refactor-test.mdc ├── src/ │ ├── services/ │ │ ├── authService.ts │ │ └── (authService.test.ts) # 即将生成 │ └── components/ │ ├── LoginForm.tsx │ └── (LoginForm.test.tsx) # 即将生成 └── package.json关键点在于.cursor/mdc/这个目录。Cursor 会自动加载该目录下的.mdc文件使其在 AI Agent 聊天中可以通过符号被引用。将三个命令文件放在这里是最标准、最不容易出错的方式。部署步骤在你的项目根目录下检查是否存在.cursor文件夹。如果没有创建一个。在.cursor文件夹内创建mdc子文件夹。将三个.mdc文件复制到.cursor/mdc/路径下。重启 Cursor IDE有时是必要的以确保新命令被加载。3.2 实战案例为一个用户认证服务生成测试让我们用一个具体的例子贯穿整个流程。假设我们有一个简单的用户认证服务authService.ts它包含登录和验证令牌的功能。步骤一提取函数分析在 Cursor 中打开authService.ts文件。打开 Cursor 的 Agent 聊天面板通常侧边栏或命令面板可打开。输入命令使用 extract-functions.mdc 分析当前打开的 authService.ts 文件。AI 会开始工作并输出一份分析摘要。这份摘要可能包含函数login(username: string, password: string): PromiseAuthResponse依赖apiClient.post(外部HTTP调用)复杂度中等异步操作错误处理测试场景成功登录、密码错误、网络异常、无效用户名格式。函数validateToken(token: string): boolean依赖jwt.verify(外部库)复杂度低测试场景有效令牌、过期令牌、篡改令牌、空令牌。步骤二生成单元测试在同一个聊天会话中基于上一步的分析继续输入现在使用 generate-unit-test.mdc为 authService 生成使用 Jest 和 jest.mock 的单元测试。请将测试文件放在与源文件相同的目录。AI 会生成一个authService.test.ts文件的内容。内容大致会包括对apiClient和jwt模块的 Mock。针对login函数的多个测试用例模拟成功响应、模拟 401 错误、模拟网络超时。针对validateToken函数的测试用例模拟验证成功、验证失败。每个用例都有清晰的断言例如expect(loginResult.user).toEqual(mockUser)。步骤三审查与重构查看生成的测试代码你可能会发现一些可以改进的地方比如模拟网络错误的代码在两个用例中重复了。在聊天中输入使用 refactor-test.mdc 优化刚才生成的测试代码重点消除重复的模拟错误逻辑。AI 可能会建议将共同的错误模拟逻辑提取到一个setupLoginErrorMock函数中并在beforeEach里重置 Mock从而使测试代码更简洁。踩坑记录在早期使用中我遇到过 AI 生成的 Mock 过于“死板”的问题。例如它用jest.fn().mockReturnValueOnce(...)精确模拟了一次调用但我的测试用例执行顺序调整后Mock 就错乱了。解决方案是在生成后手动将一些mockReturnValueOnce改为更稳定的mockResolvedValue或mockReturnValue除非你确实需要测试连续的、不同返回值的调用序列。这是一个需要人工介入判断的典型场景。4. 高级技巧与个性化定制基础流程跑通后你可以通过一些技巧和定制让这套工具更贴合你的项目和团队习惯。4.1 优化 AI 提示词以获得更佳输出.mdc文件本质上是包含系统指令和示例的提示词模板。你可以直接打开这些文件进行编辑以影响 AI 的行为。例如在generate-unit-test.mdc中你可能会找到类似这样的指令You are an expert in writing unit tests. Generate comprehensive tests for the provided function analysis. Use the Jest framework. Include happy path, edge cases, and error handling. Use clear describe/it blocks.你可以根据需求修改它指定测试库如果你用 Vitest可以把 “Jest” 改成 “Vitest”。Vitest 的 API 和 Jest 高度兼容但一些导入和配置细节不同。定义代码风格添加指令如 “Use arrow functions for test cases”, “Preferexpect().toBe()overexpect().toEqual()for primitives”让生成的代码风格与你的项目统一。强制包含特定用例如果你团队要求所有异步测试都必须测试loading状态可以加入 “For async functions, include tests for loading state if applicable.”定制示例 打开generate-unit-test.mdc在核心指令部分添加Important: We use Vitest in this project. Please use import { describe, it, expect, vi } from ‘vitest’ and use vi.mock for mocking. Also, prefer using toBe for primitive comparisons and toStrictEqual for object comparisons.这样AI 下次生成测试时就会使用 Vitest 的语法。4.2 处理复杂场景与边界情况AI 在处理一些非常规或高度复杂的代码时可能会力不从心。这时需要你提供更多上下文或进行手动引导。场景一测试 Redux Thunk 或 Vuex Action这些函数通常涉及多个 dispatch 和 getState 调用。生成测试前最好在第一步分析后额外告诉 AI这个 fetchUserData 是一个 Redux Thunk action creator。它接收 dispatch 和 getState 作为参数。请为它生成测试需要 mock 整个 store 状态和 dispatch 函数。AI 会据此生成更合适的测试框架比如使用redux-mock-store库。场景二测试具有复杂生命周期或副作用的前端组件对于 React/Vue 组件测试其渲染、用户交互和状态变化是关键。在命令中明确指定测试库请为这个 LoginForm React 组件生成测试使用 testing-library/react 和 user-event 库。重点测试表单输入、提交按钮点击以及成功/错误状态下的UI反馈。这能引导 AI 生成更侧重于组件行为而非内部实现的测试符合 Testing Library 的哲学。场景三需要特定测试数据如果函数处理的数据结构很复杂你可以在聊天中直接提供示例数据在为 calculateInvoice 函数生成测试时请使用以下示例订单数据作为输入{ items: [ {id: 1, price: 10, quantity: 2} ], taxRate: 0.1, discountCode: ‘SAVE5’ }。并测试折扣码无效的情况。个人经验把 AI 当作一个强大的、但需要明确指令的初级程序员。你给它的上下文越清晰、越具体它的输出质量就越高。不要假设它“应该知道”你们项目的特定约定。直接告诉它就像你告诉一位新同事一样。5. 常见问题排查与效能提升在实际使用中你可能会遇到一些典型问题。这里我总结了一份速查表并分享一些提升整体效能的思路。5.1 问题排查速查表问题现象可能原因解决方案AI 无法识别mdc命令1..mdc文件未放在正确目录。2. Cursor 未加载命令。1. 确认文件在.cursor/mdc/下。2. 重启 Cursor或在命令面板尝试输入MDC查看已加载命令。生成的测试无法运行导入错误AI 使用了错误的测试框架或导入路径。1. 在生成命令中明确指定框架如“用 Vitest 写”。2. 检查并修正生成的import语句使其符合项目配置。Mock 不工作或报错1. Mock 作用域不对。2. 模拟的模块路径错误。3. 异步 Mock 未正确处理。1. 将jest.mock提升到文件顶部。2. 核对被 Mock 模块的路径是否与require或import路径一致。3. 对于返回 Promise 的函数使用mockResolvedValue。测试覆盖率不足遗漏分支源代码逻辑过于复杂或 AI 分析不全面。1. 回到第一步检查分析报告是否识别了所有分支。2. 手动补充提示“请特别为这个if-else块和try-catch块生成测试用例。”生成的测试描述语意不清.mdc文件的提示词未强调测试描述的质量。1. 编辑refactor-test.mdc加入优化描述的指令。2. 手动重写描述使其符合 “It should [expected behavior] when [condition]” 格式。流程中断AI 不理解上下文在聊天中切换了话题或开启了新会话。确保三步流程在同一个连续的聊天会话中完成。上下文分析结果在会话中保留新会话会丢失。5.2 集成到开发工作流要让这套工具发挥最大价值不仅仅是偶尔用用而是将其融入你的日常开发习惯。1. 为遗留代码快速创建测试基线面对一个没有测试的旧模块使用此工作流可以快速生成一个覆盖主要功能的测试套件。这比从零开始写要快得多为你后续的重构或功能添加提供了安全网。2. 在实现新功能时 TDD测试驱动开发你可以稍微调整顺序先写一个非常简单的函数签名和空实现。用extract-functions.mdc分析这个“骨架”。用generate-unit-test.mdc为它生成你期望的测试用例这实际上是在定义需求。看着这些生成的测试用例它们会失败再去实现真正的函数逻辑直到所有测试通过。最后用refactor-test.mdc优化测试代码。3. 代码审查的一部分在 Review 同事的 Pull Request 时如果发现新增代码缺少测试可以建议或自己操作使用此工作流快速生成测试草案然后由代码作者进行审查和完善。这能有效提升团队的测试覆盖率文化。4. 定制团队共享命令库你可以将优化后的.mdc文件比如专门为你的 ReactTypeScriptVitest 技术栈调校过的版本放入团队的代码库或共享文档中。新成员 onboarding 时可以立即使用这套标准化工具生成符合团队规范的测试降低学习成本。最后我想说的是AI-Unit-Test-Builder这类工具的目标不是取代开发者而是充当一个强大的“副驾驶”。它负责处理那些繁琐、模板化的部分帮你把测试的“架子”搭好。而你作为经验丰富的驾驶员则需要负责审核逻辑、调整细节、处理复杂情况并将 AI 的产出打磨成真正坚固可靠的测试代码。这套组合拳打好了你会发现编写和维护单元测试不再是一件苦差事而是一个高效、甚至有点乐趣的质量保障过程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2599637.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…