AI结对编程:JUnit+Selenium自动化测试的10个高效指令

发布时间:2026/10/16 7:27:00

AI结对编程:JUnit+Selenium自动化测试的10个高效指令 这两年写Web自动化测试身边越来越多的人开始让 AI 帮忙写用例。我自己用过之后的感受很直接只把 AI 当成“翻译器”让它把需求直接翻译成 Java 代码帮助其实有限真正有价值的是把它当成一个熟悉 JUnit 和 Selenium 的结对程序员——你告诉它业务规则、页面结构、异常场景它给你吐出结构完整、能直接运行的测试代码。这篇文章我就把这套打法完整梳理一遍重点写清楚我自己长期在用的 10 个自动化指令模板全部围绕 JUnit 5 Selenium WebDriver 展开适合正在做 Web 自动化测试、想提升用例编写效率的同学。这套流程解决的核心问题其实就三个测试代码写得太慢、重复劳动太多、用例里的等待和定位总是写得不够稳。AI 能帮你把测试类的骨架搭好把几十个元素的定位写出来把边界数据和断言逻辑生成出来你需要做的是定义好规则然后做最后的人工把关。文章后面会给 10 个可以直接复制使用的指令再加上一个登录模块的完整实战案例。如果你本来就会写一点 Java 和 Selenium跟着这套思路走效率能提升不少就算刚入门照着环境准备部分一步步搭也能看懂大部分内容。1. 为什么要把 AI 拉进自动化测试的流程1.1 传统用例编写中的三个老大难先说一个很多团队都有的现象自动化用例库里的代码一半以上是“复制粘贴然后改个参数”。这句话不好听但确实是常态。第一个老大难是元素定位。从开发手里拿到的页面几十个输入框、按钮、下拉框每个都要手工打开浏览器开发工具去 DOM 里翻半天把 XPath 或者 CSS 选择器复制出来贴进代码再跑一遍看看稳不稳定。一个登录页面还好遇到那种表格加弹窗加多级菜单的后台管理系统光写定位器就能耗掉一个下午。而且不同浏览器的 DOM 结构细节有差异复制出来的 XPath 又长又脆动不动就碎。第二个老大难是测试数据准备。很多用例不是随便填一个值就能跑的。比如注册功能用户名有长度限制、字符类型限制密码有复杂度要求邮箱有格式要求手机号有号段要求。手工把这些数据列出来再一个一个写进代码里很容易漏掉边界情况。更常见的情况是数据写死了下次跑用例的时候系统里已经存在同名用户用例直接挂掉。第三个老大难是等待策略。新手写 Selenium 经常是 Thread.sleep(3000) 一把梭跑起来时好时坏。老手知道要用 WebDriverWait 显式等待但写出来的等待条件多了之后代码被撑得非常臃肿。而且“先等按钮可点击、再等某个文本消失、再等表格刷新完成”这种逻辑手写特别容易出错一错就是用例随机失败。这三个问题单靠堆人力去解决效率天花板很明显。而 AI 生成代码恰恰在这三个方向上都很擅长它能快速生成大量稳定风格的定位器能按边界值分析方法批量造测试数据还能把等待逻辑写得比大多数手工代码更规范。这也是我把 AI 拉进测试流程的根本原因。1.2 测试同学该盯哪些环节有人一听“AI 生成测试用例”就觉得要失业其实完全搞反了。AI 真正替代的是“输入输出比较明确的编码动作”而测试用例里最值钱的部分——判断要测什么、哪些场景值得自动化、哪些结果算正常——恰恰需要人来决定。我的分工方式是这样的我先想清楚被测功能的业务规则然后把规则用文字描述给 AI它负责生成完整的代码骨架和细节实现。生成完之后我不会直接拿来跑而是先看三件事第一关键流程有没有覆盖全第二断言的地方有没有断在真正重要的节点上第三异常场景有没有漏掉。这三件事看完了再交给 AI 去补或者改。打个比方这就像是你当主编AI 是手速很快的实习生。你把提纲和风格要求讲清楚它把初稿写出来你负责审稿和拍板。把 AI 当“自动填空工具”用体验不会好把它当“听话的结对编程搭档”用越用越顺手。1.3 这套组合适合谁老实说不是所有人都适合这套流程。如果你完全不懂 Java、不理解什么是元素定位那 AI 生成的代码对你来说就像天书出了错也不知道怎么调。这套方案适合两类人一类是已经能独立编写 Selenium 用例但想提升效率的测试开发另一类是懂业务、会写一点代码但没时间系统学习测试框架的测试工程师。JUnit 5 和 Selenium 的组合是目前 Java 生态里最主流、资料也最多的搭配。JUnit 负责用例生命周期、断言和测试报告Selenium 负责驱动浏览器模拟用户操作。AI 对这两个框架的训练语料非常充足你让它生成 JUnit 5 风格的测试类它产出的代码通常比通用代码质量更高因为网上有海量样例可供参考。2. 动手前的环境准备JUnit 5 Selenium 基础2.1 依赖引入与项目结构建议直接用 Maven 工程结构清晰依赖管理省心。在 pom.xml 里至少需要引入这几个核心依赖selenium-java、junit-jupiter以及对应浏览器的驱动库。如果用的是 Chrome通常还要保证本机装了匹配版本的 ChromeDriver。dependencies dependency groupIdorg.seleniumhq.selenium/groupId artifactIdselenium-java/artifactId version4.21.0/version /dependency dependency groupIdorg.junit.jupiter/groupId artifactIdjunit-jupiter/artifactId version5.10.2/version scopetest/scope /dependency /dependencies这里有一个很多人忽略的点Selenium 4 的 WebDriver 是内置的不需要再单独引入 json-chromedriver 之类的包。如果你用的是老版本 3.x驱动管理会麻烦一些建议直接上 4.x。版本号不需要刻意追新选一个自己本地能跑通的稳定版本即可。项目结构上推荐把测试代码分成两层一层是页面对象放在 page 包里一层是测试用例放在 test 包里。后面讲页面对象模式的时候会细说为什么这么分。现在就按这个结构把 src/test/java 目录建好。2.2 一个能跑起来的最小测试类AI 生成大段代码之前最好先让它看到一个能跑的最小样例这样生成的内容会更有针对性。我自己习惯先放一个最简单的冒烟用例确认环境没问题再让 AI 扩展import org.junit.jupiter.api.*; import org.openqa.selenium.WebDriver; import org.openqa.selenium.chrome.ChromeDriver; import java.time.Duration; public class SmokeTest { WebDriver driver; BeforeEach void setUp() { driver new ChromeDriver(); driver.manage().window().maximize(); driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(5)); } AfterEach void tearDown() { if (driver ! null) { driver.quit(); } } Test void visitHomePage() { driver.get(http://localhost:8080); Assertions.assertEquals(欢迎页, driver.getTitle()); } }这段代码本身没什么神秘的但它验证了三件事浏览器能正常拉起、被测系统能访问、JUnit 5 的注解和断言没问题。把这套跑通了后面让 AI 生成的代码就有了一个可对照的基准。2.3 给 AI 描述项目背景的技巧AI 能不能生成你想要的代码很大程度上取决于你怎么描述项目背景。只说一句“帮我写个登录测试”和下面这段描述得到的结果完全不一样“我有一个后台管理系统登录页地址是 http://localhost:8080/login。用户名输入框 id 是 username密码输入框 id 是 password登录按钮 id 是 loginBtn。登录成功后跳转到首页首页右上角有用户名。请用 JUnit 5 Selenium 4 写一个登录成功的测试用例要求使用 WebDriverWait 等待而不是固定 sleep。”这个技巧要刻意练习。页面上的关键元素 id、跳转后的预期特征、等待方式的偏好这三样写清楚之后AI 生成的第一版代码就基本可用了。反之描述越模糊越要来回改。后面第 4 章的实战里我会给一个更完整的描述模板。3. 10 个实用指令从骨架到稳定运行这 10 个指令是我平时最高频使用的它们之间有一定递进关系先搭骨架再补细节最后做工程化加固。你可以一次性把全部指令发给 AI让它分步产出也可以每条单独用逐步完善现有代码。下面逐个拆开讲。3.1 指令一生成 JUnit 测试类骨架指令的核心是让 AI 帮你搭出测试类的基本框架包括类结构、生命周期方法、WebDriver 的初始化与释放。参考提示词如下“请用 JUnit 5 写一个 Selenium 测试类测试后台管理系统的登录功能。要求类名为 LoginTest放在 test 包下BeforeEach 中启动 ChromeDriver 并访问 http://localhost:8080/loginAfterEach 中退出驱动先写一个空白的登录测试方法占位。”AI 生成的结果通常包含 WebDriver 初始化、窗口最大化、隐式等待设置、quit 方法清理。这个骨架的价值在于省掉了每次新建测试类都要重复敲一遍的样板代码而且由 AI 统一生成多个用例类之间的风格是一致的。实际使用中我会再追加一句“浏览器驱动路径通过 WebDriverManager 统一管理”这样连本机驱动版本不匹配的问题都绕开了。注意骨架里的隐式等待只是为了兜底后面生成独立等待逻辑时不要依赖它去解决所有问题。3.2 指令二生成健壮的元素定位器元素定位是自动化测试最容易被 AI 写翻车的地方因为 AI 会编造并不存在的 id。所以我给这个指令的建议是先贴一段页面 HTML 片段再让它生成定位器这样准确性会高很多。“这是登录页面的 HTML 片段[粘贴片段]。请为用户名输入框、密码输入框、登录按钮分别生成 Selenium 定位器。要求优先使用 id 或 name如果都没有使用相对 CSS 选择器不要使用 xpath 里的下标。”HTML 片段怎么拿打开浏览器开发者工具选中元素右键 Copy → Copy OuterHTML粘贴给 AI 就行。这比用语言描述“右上角有个按钮”准确得多。有一点要提醒如果页面结构里有大量动态 id比如每次打开都是 uuid那 AI 生成的定位器基本都会失效。这种情况应该推动前端工程师在关键元素上加上稳定的>WebDriverWait wait new WebDriverWait(driver, Duration.ofSeconds(10)); wait.until(ExpectedConditions.urlToBe(http://localhost:8080/home));值得多说一句的是等待条件要精确但不要过度。等待“URL 包含 home”通常比等待某个具体元素更稳定因为页面跳转的本质是 URL 变化。等待单个元素时优先选择最晚出现的那个业务元素而不是页面顶部的公共导航栏。3.6 指令六页面对象模式重构当用例变多所有登录步骤都直接写在测试类里代码会很臃肿。页面对象模式POM是解决这个问题的标准做法。指令“请将登录测试重构为页面对象模式。创建 LoginPage 类封装用户名输入、密码输入、点击登录按钮、获取错误提示文本这几个方法。LoginTest 只调用 LoginPage 的方法不直接操作 WebDriver。”得到的 LoginPage 大致长这样public class LoginPage { private final WebDriver driver; public LoginPage(WebDriver driver) { this.driver driver; } public void login(String username, String password) { driver.findElement(By.id(username)).sendKeys(username); driver.findElement(By.id(password)).sendKeys(password); driver.findElement(By.id(loginBtn)).click(); } public String getErrorText() { return driver.findElement(By.id(errorMsg)).getText(); } }POM 的好处在于当页面元素 id 变化时只需要改 Page 类所有调用它的测试用例都不用动。这是手工重构也能做但 AI 做得快得多、标准得多。实际使用中我会让 AI 为每个被测页面都生成对应的 Page 类和几个核心操作入口。3.7 指令七生成异常与负面场景只测试正常流程的用例集是不完整的。让 AI 生成负面场景可以快速补齐覆盖度“请为登录功能生成以下负面场景测试空用户名、空密码、错误密码、用户不存在、连续输错 5 次后的锁定提示。每个场景单独一个测试方法断言统一使用错误提示文本 id 为 errorMsg 的元素。”这里有个操作细节负面场景往往涉及验证码、锁定策略、防爆破机制如果被测环境里这些策略会影响测试重复执行需要提前和开发约定测试环境的特殊处理方式。不然用例跑两轮就会被封 IP。AI 不会知道这些环境限制它只负责生成逻辑跑不跑得动要靠你来判断。3.8 指令八失败截图与日志输出用例失败时附上截图能让排查效率提升一个档次。指令“在测试失败时自动截图保存到 target/screenshots 目录文件名包含测试方法名和时间戳。同时用 log 记录每一步关键操作比如‘输入用户名 admin’‘点击登录按钮’。”AI 通常能生成一个监听器类基于 JUnit 5 的 TestWatcher 接口实现示例逻辑如下public class ScreenshotWatcher implements TestWatcher { Override public void testFailed(ExtensionContext context, Throwable cause) { // 截图逻辑从 context 中获取当前 WebDriver 实例 } }注意一个现实问题JUnit 5 的 TestWatcher 回调里拿不到测试类中的 WebDriver 实例需要配合静态变量或者自定义扩展来传递。这是 AI 第一次生成时容易踩的坑你要么自己调整代码要么明确告诉 AI“通过静态 ThreadLocal 保存 WebDriver”它就能给出一个线程安全的方案。3.9 指令九数据驱动参数化当同一套流程要覆盖多组数据时参数化比写多个重复测试方法更高效。指令“请将登录成功用例改为 JUnit 5 参数化测试使用 MethodSource 提供三组数据源正确用户名密码、边界长度用户名密码、包含字母数字和下划线的用户名密码。测试方法接收两个参数 username 和 password。”AI 生成的代码类似ParameterizedTest MethodSource(loginData) void loginSuccess(String username, String password) { loginPage.login(username, password); Assertions.assertEquals(http://localhost:8080/home, driver.getCurrentUrl()); } static StreamArguments loginData() { return Stream.of( Arguments.of(admin, 123456), Arguments.of(abcdefghij, pass_123), Arguments.of(user_01, abc123XYZ) ); }参数化测试最大的好处是数据与逻辑分离后续加用例只需要增加一行数据源不需要新增测试方法。代价是失败时定位问题要稍费点劲所以要在参数里带上场景说明或者让数据行体现出明显差异。3.10 指令十失败重试机制处理“偶发失败”几乎是所有自动化测试团队的日常。JUnit 5 不像 TestNG 那样自带重试注解通常需要自己实现一个扩展。AI 生成这个扩展很擅长“请写一个 JUnit 5 扩展实现失败自动重试最多重试 2 次。提供 Retry 注解注解参数是重试次数。测试类中标记需要重试的测试方法。”AI 会生成 Retry 注解、RetryExtension 两个类核心逻辑是拦截测试执行失败时重新运行。使用方式Test Retry(2) void loginWithRetry() { // 测试逻辑 }这里我建议把重试范围控制得小一点只给确实出现过偶发问题的用例添加重试不要全场无脑重试。原因很简单如果用例本身有缺陷重试只会掩盖问题让测试集跑得更慢失败的信号也被弱化。以上 10 个指令基本覆盖了从编写到稳定的完整链路。接下来用一个实战案例把它们串起来。4. 实战AI 帮你拿下一个登录模块4.1 给 AI 的完整需求描述我选了一个最常见的场景某后台管理系统的登录模块。启动项目之后我希望 AI 帮我生成整套用例。先给它发一段完整描述“被测系统某后台管理系统地址 http://localhost:8080/login。登录成功后会跳转到 /home首页右上角显示登录用户名。登录失败时登录表单下方显示 id 为 errorMsg 的错误提示。页面元素用户名输入框 idusername密码输入框 idpassword登录按钮 idloginBtn。请用 JUnit 5 Selenium 4 页面对象模式生成登录测试的完整代码包括 LoginPage 类和 LoginTest 类。LoginPage 封装输入用户名、输入密码、点击登录、读取错误提示LoginTest 包含成功登录用例和失败登录用例使用显式等待不使用 Thread.sleep失败时自动截图到 target/screenshots。”这段描述把被测对象、元素定位、输出要求、约束条件全都说清楚了。AI 给我的第一版代码基本可以直接编译只出现过一次小问题失败用例断言时错误提示还没渲染出来加了显式等待后就好了。4.2 生成后的代码怎么改AI 生成代码不是拿来就能用我每次都会做三处调整。第一处是去掉重复初始化的代码。AI 有时候会在 LoginTest 的 BeforeEach 里创建 LoginPage 对象但后续每个测试方法又 new 一个这种细节不影响运行但看着别扭统一放一处。第二处是给等待条件补充业务含义。AI 生成的等待往往过于通用比如“等待 errorMsg 可见”我会改成“等待 errorMsg 的文本等于具体错误信息”这样更精确也能防止脚本误判。第三处是补环境相关的逻辑。比如测试账号的密码明文写在代码里不安全我会提示 AI 改成从环境变量读取或者从 properties 文件读取。AI 生成的环境变量读取代码同样很标准。4.3 跑通之后还要做什么第一次跑通这个登录模块的全部用例后我还会做几件事把生成好的 LoginPage 类用于其他测试类复用把登录步骤抽取到 BaseTest 类里作为公共前置操作把失败截图目录加入 .gitignore避免把截图提交进仓库。最后把整个用例集跑三遍确认没有偶发失败。如果还有随机挂掉的用例比如某次点击后页面响应慢我会把对应的等待条件再调精确一点而不是直接加重试。这一步宁可多花 20 分钟也不要留下一个时好时坏的用例。5. 我踩过的坑AI 生成代码的典型问题与排查5.1 定位器生成后跑不过的两类常见原因AI 生成定位器失败九成是这两类问题。第一类是“凭想象生成 id”。AI 并不知道页面真实结构如果你没告诉它元素 id它就自己编。所以前面强调必须贴 HTML 片段或者明确给出 id。第二类是“定位器写得太脆弱”比如生成了包含绝对路径的 XPath。这种定位器在当前页面能跑开发改一个节点就碎。排查方法也很简单失败后先看 error 里的定位器信息打开浏览器自己在开发者工具的 Console 里试一下。推荐在提示词里固定一句约束“不要使用按索引选择的 XPath优先使用 id、name、或包含>ChromeOptions options new ChromeOptions(); options.addArguments(--headlessnew); driver new ChromeDriver(options);这里还有个很多人都忽略的细节headless 模式下“点击后跳转”比有界面模式快得多但页面动画效果可能不一样。比如依赖 CSS 过渡动画的元素有界面模式下等待动画结束很稳定headless 下反而需要等待元素真正可交互。所以在本地调试通过后一定要在 headless 模式下再跑一遍。5.3 AI 过度设计怎么办AI 有时候会生成远超你需要的东西比如一个小小登录测试它给你生成 5 个抽象类、2 个接口、1 个工厂模式。这种代码放到测试工程里只会增加阅读成本。我的处理原则是测试代码宁可重复不要增加间接层。遇到 AI 生成的过度设计直接删掉多余抽象保留最直接、最直白的代码。任何一个新同事看到测试代码都应当能在五分钟内看懂它在测什么否则就是过度设计。5.4 常见问题速查表现象可能原因建议处理用例跑不过报找不到元素定位器是 AI 编造的贴真实 HTML 片段重新生成点击后页面没反应等待条件不匹配改用 URL 断言或核心业务元素出现本地通过CI 失败等待超时太短或 headless 差异加长超时统一 headless 运行生成代码有大量无用抽象AI 过度设计直接删掉多余层级保持扁平失败时没有截图TestWatcher 拿不到 WebDriver用 ThreadLocal 存 WebDriver 实例6. 把 AI 变成测试团队的日常外挂6.1 沉淀自己的指令库用 AI 生成测试代码最大的进阶不是每次现场打字而是把自己验证过的好提示词沉淀成模板。我的做法是在工程里维护一个 AI-PROMPTS.md 文件把 10 个指令按用途分类保存。下次遇到类似的模块直接复用指令只改页面地址和元素 id。这些指令经过一次次使用、修正会变得越来越精准。比如最初的定位器指令只是简单一句“生成定位器”现在已经变成了“优先 id其次包含>
延伸阅读

更多相关文章

2026/10/16 7:22:00

多微网电能交互的分布式优化:计及碳排放的ADMM调度策略

1. 多微电网电能交互:一个实际运营中的硬骨头我做过多年的微电网能量管理,最常被问的一句话就是:“几个微电网挨在一起,能不能互相借点电?”答案是能,但远不是拉一根线、签个协议那么简单。多微电网的电能交…

2026/10/16 7:22:00

Oracle 11g INSERT 全解析:批量插入、性能优化与避坑指南

1. 先搞清楚 Oracle 11g 的 INSERT 语法骨架我在接手维护一个订单系统的时候,发现最频繁出问题的 SQL 往往不是那些几百行的复杂查询,反而是看起来人畜无害的 INSERT。原因很简单,SELECT 写错了最多查不出数据,INSERT 写错了直接污…

2026/10/16 9:32:33

Elasticsearch 9200端口拒绝连接:从报错到根因的排查指南

刚看到这个报错的时候,八成是又在调测试环境,顺手敲了条curl http://walker001:9200想看下 Elasticsearch 集群状态,结果却出来一行curl: (7) Failed to connect to walker001 port 9200: 拒绝连接。干这行久了,这种提示见得太多&…

2026/10/16 9:32:33

BrowserSkill:基于Chrome DevTools协议的命令行浏览器自动化框架

1. 项目概述:BrowserSkill到底是什么,它解决的不是“浏览器插件”这个表层问题 BrowserSkill——这个名字乍一听像某个新出的Chrome插件,或者某个AI工具的副产品。但如果你真去翻过它的GitHub仓库、CLI命令手册,甚至亲手跑过几条…

2026/10/16 9:32:33

知识工作插件全解析:从加载原理到故障排除实战

做知识工作的人,几乎每天都在跟插件打交道。写方案、写代码、做研究、整理信息、管理项目,背后都有一层插件在撑腰。我说的 knowledge-work-plugins,指的是围绕知识工作场景构建的整套插件体系:怎么选、怎么装、怎么维护、出了故障…

2026/10/16 9:32:33

OpenSpec实战:规范驱动开发如何让需求像代码一样可管理

OpenSpec 这个词我最早是在一个技术社区的开源仓库 README 里看到的,当时它还没多少 star,但我一眼就被它的定位吸引住了——它不是一个代码框架,而是帮团队把“需求”这件事做成“工程化”的工具。如果你和我一样,受够了需求文档…

2026/10/16 9:32:33

AI产品设计的本质:从技术实现到人本协作

1. 这门课不是“学AI”,而是重新校准你对“产品”的认知坐标系“学 AI 产品课之前,我走了不少弯路”——这句话我去年在内部复盘会上听一位刚转岗的PM说了三遍。她不是在抱怨课程难,而是在描述一种认知错位:她带着做To C App的经验…

2026/10/16 9:27:31

Java面向对象三大特征之--多态

前言:前面我们已经学习了封装、继承两大特征,今天我们来学习面向对象的三大特征中的最后一个特征:多态。废话不多说,让我们一起开始吧! 目录 一、向上转型和向下转型 1.1 向上转型(自动类型转换&#xf…

2026/10/16 5:33:43

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/16 4:03:48

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/15 13:42:16

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/16 0:01:11

Cephadm实战指南:Linux系统工程师的生产级Ceph集群部署与排障

简介:本资源是一份完整的Ceph分布式存储系统中文技术文档合集,面向Linux系统运维工程师、云平台架构师及存储方向开发者,帮助读者系统掌握Ceph集群的部署、配置与运维核心能力。文档覆盖对象存储(RGW)、块设备&#xf…

2026/10/16 0:01:11

ITK中SpatialObject几何体系解析:坐标系、变换与管道集成

1. 空间对象的定位:为什么 ITK 需要一套几何描述体系很多刚开始接触 ITK 的朋友,包括我自己,最初都是被它的图像滤波、配准、分割这些“正统”功能吸引进来的。等真正上手做点实际项目,比如把分割出来的器官边界做量化分析、给血管…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑