news 2026/9/29 10:06:42

存储过程实战全解:MySQL、Oracle、openGauss差异与SQLSugar调用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
存储过程实战全解:MySQL、Oracle、openGauss差异与SQLSugar调用

存储过程这个老面孔,在数据库考试的程序填空题里是常驻选手,在实际业务里也是处理复杂逻辑的一把好手。很多同学在填空题里能写对CREATE PROCEDURE的拼写,但一碰到DELIMITER、游标、异常处理就露怯;很多开发同学说会用,但真让他写一个带事务、带循环、带游标的存储过程,又容易卡壳。这篇就来把存储过程从头到尾拆一遍,覆盖MySQL、Oracle、openGauss三种主流数据库的写法差异,还会带上.NET生态里用SQLSugar调存储过程的实战姿势。不管你是准备考试、应付面试,还是要上手写生产级存储过程,这篇都能给你一套能直接抄的思路。

1. 存储过程到底是什么——把它想象成数据库里的“预制菜生产线”

1.1 存储过程的本质不是“能放代码”,而是把逻辑沉淀在数据层

很多人把存储过程理解成“数据库里的函数”,这个说法对了一半。函数重在计算和返回,存储过程重在“执行一段完整的事务逻辑”。它是一段预先编译好的、保存在数据库服务端的代码块,客户端通过一个名字加一组参数就能触发它。

我习惯用一个类比来解释:程序填空题里让你补全的存储过程,就像一条预制菜生产线。原材料是表里的数据,生产线的每个工位是一条SQL语句,控制流转的是IF、WHILE、CURSOR这些流程控制结构,最后出货口是OUT参数或者结果集。调用方不需要关心生产线内部怎么运转,只需要把原料(IN参数)送进去,到点来取结果就行。

这个设计带来的核心价值有三个:第一,高性能——存储过程在首次执行时会被数据库优化器编译,后续执行直接走缓存计划,尤其在循环调用场景下比反复发送SQL文本快得多;第二,低耦合——业务逻辑对客户端隐藏,修改内部实现不需要改应用代码,也不需要对客户端暴露表结构;第三,安全性可控——DBA可以只授予执行权限,而不授予底层表的读写权限。

不过,正因为逻辑沉在数据库里,它也带来了调试困难、版本管理不便、跨库迁移成本高等问题。所以现在的架构趋势是“能不放就不放”,但遇到复杂报表、批量数据处理、事务一致性要求高的场景,存储过程依然是不可替代的选择。

1.2 考试、面试和真实业务里,存储过程各占什么位置

在考试的程序填空题里,存储过程主要考三件事:语法结构完整性、流程控制逻辑、参数与变量的使用。也就是说,你不需要设计一个复杂的业务,但要能准确补全DELIMITER、BEGIN、DECLARE、END IF、FETCH这些关键字,并且保证逻辑正确。

在面试里,考察点会偏向“是否理解存储过程的边界”:什么时候适合用、什么时候不能用、和事务如何配合、并发下有什么问题。在真实业务里,尤其是银行、电商、ERP这类系统,存储过程依然是核心交易链路里的常客,Oracle存储过程、MySQL存储过程、openGauss存储过程都有大量存量代码在跑。

从搜索热词能看出一个趋势——很多人在找“mysql存储过程”和“oracle存储过程”的语法对比,还有“opengauss存储过程”这种国产数据库的写法。这说明存储过程不仅没有过时,反而因为国产数据库迁移再次成为技能刚需。不管底层数据库怎么换,存储过程的思维模型是通用的,只是语法细节有差异。

2. 声明语法逐行拆解——程序填空题的得分关键

2.1 从一段最朴素的MySQL存储过程讲起

先看一段最简单的MySQL存储过程,也是程序填空题最基础的模板:

DELIMITER $$ CREATE PROCEDURE sp_hello(IN name VARCHAR(20)) BEGIN SELECT CONCAT('Hello, ', name, '!') AS greeting; END$$ DELIMITER ;

这里面每行都有考点。DELIMITER是MySQL特有的、最容易在填空题里被挖掉的关键字。它的作用是把MySQL客户端默认的分号结束符临时改成别的符号,否则客户端会在CREATE PROCEDURE的END处就把整个语句截断,导致语法错误。

CREATE PROCEDURE后面跟过程名和参数列表,参数用括号包裹,每个参数由“模式 + 参数名 + 数据类型”组成。BEGIN和END之间是过程体,过程体里可以写多条SQL语句、变量声明、流程控制、游标等。最后一定要把DELIMITER改回分号,否则后续的普通语句都会以$$结尾,容易踩坑。

Oracle和openGauss的存储过程结构略有不同,它们没有DELIMITER这一说,直接以AS或IS开始声明部分,过程体用BEGIN...END;包裹。比如:

CREATE OR REPLACE PROCEDURE sp_hello (p_name IN VARCHAR2) IS BEGIN DBMS_OUTPUT.PUT_LINE('Hello, ' || p_name || '!'); END sp_hello; /

openGauss兼容Oracle语法的存储过程,同时也支持类似PL/pgSQL的写法。填空题里如果考Oracle或openGauss,多半会挖IN、IS、BEGIN、END这些位置。

2.2 IN、OUT、INOUT三种参数模式,怎么选才不丢分

参数模式是存储过程的核心考点,也是新手最容易混淆的地方。MySQL的参数模式有三种:IN、OUT、INOUT,Oracle和openGauss则写成IN、OUT、IN OUT,注意中间有空格。

IN模式是默认模式,也是最常用的。它表示调用方传入一个值,存储过程内部可以读取这个值,但对它的修改不会传回给调用方。想象成你把钱放进保险箱,保险箱处理完,钱还是你的,不会因为你存过一次它就变成别的面额。

OUT模式表示存储过程内部会给这个参数赋值,调用方在调用前不需要给它值,调用完成后可以读取到结果。这就是“出货口”。比如你送进去一个订单号,过程把统计金额写到OUT参数里返回。

INOUT模式则是双向通道:调用方先传入一个值,存储过程内部可以读、可以改,最终值传回给调用方。最典型的应用场景是做自增序号或累加器。

选择题和填空题最喜欢出这种题:问“存储过程内部修改后,哪个参数的值能传回调用方”,答案是OUT和INOUT,IN不行。如果题目问“调用前必须赋初值的是哪个”,答案是INOUT,因为OUT调用前不需要赋值,IN就算赋了也会被忽略。

2.3 变量的三个层级,搞不清就写不出正确的赋值逻辑

存储过程里变量有三个层级:局部变量、用户变量、系统变量。程序填空题里常挖的是前两种。

局部变量用DECLARE声明,必须放在BEGIN...END语句块的开头部分,也就是所有可执行语句之前。比如:

DECLARE total_amount DECIMAL(10,2) DEFAULT 0; DECLARE done INT DEFAULT 0;

局部变量只能在声明它的BEGIN...END块内使用,块结束变量就销毁。给它赋值有两种方式:SET和SELECT...INTO。

用户变量用@前缀,比如@dept_count。它不需要DECLARE,直接用SET赋值即可创建,生命周期是当前会话。用户变量的一个特点是可以在存储过程内部使用,也可以在过程返回后继续在客户端查询。这常用于在存储过程之间传递临时值,或者在调试时打印中间结果。

区分这三个层级的有效记忆方法是:DECLARE是“面试前的本地草稿纸”,@是“会议室的公共白板”,系统变量则不要碰,一般不用存储过程去修改。填空题里如果出现“声明变量”的关键字,答案通常就是DECLARE;如果出现“给变量赋值”的语句,优先考虑SET。

3. 核心实现机制的五个必考模块:IF、CASE、循环、游标、异常

3.1 条件判断:IF和CASE的语法差异,填空题的“送分题”与“送命题”

存储过程里的IF和普通编程语言里的IF很接近,但语法细节不同。MySQL的IF结构是:

IF 条件 THEN 语句; ELSEIF 条件 THEN 语句; ELSE 语句; END IF;

注意ELSEIF是一个单词,不是ELSE IF,而且整个结构必须以END IF加空格加冒号结尾,也就是END IF;。这两个位置是程序填空题的高频挖空点。如果写成ENDIF,会直接语法报错。

CASE有两个变体。第一个是简单CASE,拿一个表达式和多个值比较:

CASE @level WHEN 'A' THEN SET @score = 100; WHEN 'B' THEN SET @score = 80; ELSE SET @score = 60; END CASE;

第二个是搜索CASE,每个WHEN后面跟完整条件布尔表达式:

CASE WHEN @score >= 90 THEN SET @grade = '优'; WHEN @score >= 60 THEN SET @grade = '及格'; ELSE SET @grade = '不及格'; END CASE;

别漏掉END CASE;结尾。在Oracle和openGauss中,IF的写法变成了ELSIF(少一个E),END IF同样不可缺少,而CASE的结束是END CASE;,与MySQL一致。考Oracle时,ELSIF就是一个经典的坑,很多人想当然写上ELSEIF就丢了分。

3.2 三种循环结构:WHILE、REPEAT、LOOP,什么时候用哪个

MySQL存储过程支持三种循环:WHILE、REPEAT、LEAVE配合LOOP。

WHILE循环是“先判断,后执行”,循环体可能一次都不执行。语法:

WHILE 条件 DO 语句; END WHILE;

REPEAT循环是“先执行,后判断”,至少执行一次。语法:

REPEAT 语句; UNTIL 条件 END REPEAT;

LOOP则是一个无限循环,必须配合LEAVE语句跳出:

LOOP 语句; IF 条件 THEN LEAVE loop_label; END IF; END LOOP;

括号里这个loop_label就是循环标签,标签必须在循环体开头声明,格式是标签名加冒号,比如loop_label: LOOP。程序填空题里,考LOOP循环时会挖LEAVE和标签的位置。

实际写复杂逻辑时,我比较推荐WHILE,因为它最直观,不容易出现死循环。REPEAT适合“无论如何处理,至少要先做一次”的场景,比如遍历生成序列。LOOP则适合需要在循环体中间多条件跳出、又没有统一循环条件的场景。

3.3 游标的四步走:声明、打开、抓取、关闭,一步都不能少

游标是程序填空题的压轴题。游标本质上是一个指向查询结果集的指针,让你可以一行一行地处理数据,而不是一次性把整个结果集塞进内存。

一个标准的游标使用流程包含四个步骤,正好是填空题的四个空位:

-- 第一步:声明游标,绑定查询结果集 DECLARE cur_emp CURSOR FOR SELECT id, salary FROM employee; -- 第二步:声明异常处理器,用于判断数据是否遍历完 DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; -- 第三步:打开游标 OPEN cur_emp; -- 第四步:循环抓取数据,处理完关闭 FETCH cur_emp INTO v_id, v_salary; CLOSE cur_emp;

关键点在于done变量的配合使用。在MySQL中,当FETCH没有更多数据可抓取时,会触发NOT FOUND条件,如果之前声明了CONTINUE HANDLER FOR NOT FOUND,就会把done变量置为TRUE。于是循环条件可以写成WHILE NOT done DO,循环体内第一件事是FETCH,第二件事是处理数据。

这里有个非常经典的踩坑点:FETCH语句和CONTINUE HANDLER的声明顺序。在MySQL里,DECLARE语句必须在BEGIN块最前面,游标和异常处理器也都属于DECLARE范畴,所以必须放在一起声明,并且游标必须声明在变量的声明之后、异常处理器的声明之前,否则会报语法错误。这个顺序在程序填空题里如果挖空,很容易写乱。

Oracle和openGauss的游标写法也遵循类似的四步流程,不过异常处理的写法变成:

BEGIN OPEN cur_emp; LOOP FETCH cur_emp INTO v_id, v_salary; EXIT WHEN cur_emp%NOTFOUND; END LOOP; CLOSE cur_emp; END;

这里用的是游标属性%NOTFOUND来判断是否取完,不再依赖CONTINUE HANDLER。两种风格都要熟,因为考试和面试可能一起考。

3.4 异常处理:不是加分项,而是生产环境的保命项

MySQL的异常处理核心是DECLARE...HANDLER,它有两种级别:CONTINUE和EXIT。CONTINUE表示遇到异常继续执行后续代码,EXIT表示遇到异常直接退出当前BEGIN块。常见写法:

DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN ROLLBACK; SELECT '事务回滚' AS msg; END;

SQLEXCEPTION是MySQL的异常类别,它捕获SQL执行过程中的所有错误。也可以针对具体错误码写,比如声明一个专门针对“表不存在”错误码1146的处理器。这种精细化控制在批量任务里很有用,能做到“遇到某类错误就跳过,遇到致命错误就停止”。

Oracle和openGauss则是用EXCEPTION块:

EXCEPTION WHEN NO_DATA_FOUND THEN v_msg := '数据不存在'; WHEN OTHERS THEN v_msg := '未知错误'; END;

这里OTHERS对应MySQL的SQLEXCEPTION,NO_DATA_FOUND对应“NOT FOUND”条件。在生产级存储过程里,异常处理不是可有可无的装饰,而是必须在编码阶段就布局好的安全网——否则一条报错就能让整个批量任务的中断状态变得不可控。

4. 完整实操:从建表到SQLSugar调用,一条龙写一个订单统计存储过程

4.1 需求设计:这个存储过程要解决什么问题

光讲语法不落地等于纸上谈兵。这里我设计一个贯穿始终的实战例子:一个电商系统的订单表,需要按部门统计订单总额,并把结果保存到统计表,同时返回总额给调用方。这个场景涵盖了建表、变量、游标、事务、OUT参数、异常处理可以一次性全用上。

先建两张表:

CREATE TABLE dept_order_count ( dept_id INT PRIMARY KEY, total_amount DECIMAL(12,2) DEFAULT 0.00, stat_date DATE ); CREATE TABLE order_detail ( order_id INT PRIMARY KEY AUTO_INCREMENT, dept_id INT NOT NULL, amount DECIMAL(10,2) NOT NULL, status TINYINT DEFAULT 1 COMMENT '1正常 0取消' );

order_detail是订单流水,dept_order_count是统计结果表。存储过程要做的事情是:遍历所有部门,统计每个部门在order_detail里未取消订单的总额,如果统计表里已有该部门的记录就更新,没有就插入,最后把所有部门的总汇总金额写到OUT参数。

这个需求用一条普通的GROUP BY SQL也能做,但一旦牵扯到“逐部门处理,还要做UPSERT,还要返回总金额给外部”,用存储过程就显得更内聚。

4.2 完整代码:带注释的MySQL存储过程范本

先不急着上完整代码,要分析一个关键点:为什么这里要用游标,而不是直接INSERT...SELECT?因为需求要求“逐个部门计算并输出中间过程”,在实际的报表场景里可能每个部门还要调用独立的算法或者写日志。游标能把“遍历部门”这个流程显式化,方便扩展。如果MySQL的窗口函数能完美处理,就不必游标,“能用集合操作优先用集合”始终是铁律。

下面是完整代码,也是可以直接背下来的模板:

DELIMITER $$ CREATE PROCEDURE sp_stat_dept_order( IN p_stat_date DATE, OUT p_total_amount DECIMAL(12,2) ) BEGIN -- 声明局部变量 DECLARE v_dept_id INT; DECLARE v_amount DECIMAL(12,2); DECLARE v_total DECIMAL(12,2) DEFAULT 0.00; DECLARE done INT DEFAULT FALSE; -- 声明游标:遍历有效订单的部门 DECLARE cur_dept CURSOR FOR SELECT dept_id, SUM(amount) FROM order_detail WHERE status = 1 AND DATE(create_time) <= p_stat_date GROUP BY dept_id; -- 声明异常处理器 DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; OPEN cur_dept; REPEAT FETCH cur_dept INTO v_dept_id, v_amount; IF NOT done THEN -- 如果统计表中已有记录,更新;否则插入 IF EXISTS(SELECT 1 FROM dept_order_count WHERE dept_id = v_dept_id) THEN UPDATE dept_order_count SET total_amount = v_amount, stat_date = p_stat_date WHERE dept_id = v_dept_id; ELSE INSERT INTO dept_order_count(dept_id, total_amount, stat_date) VALUES(v_dept_id, v_amount, p_stat_date); END IF; SET v_total = v_total + v_amount; END IF; UNTIL done END REPEAT; CLOSE cur_dept; -- 结果写入OUT参数 SET p_total_amount = v_total; END$$ DELIMITER ;

有几个细节值得留意。第一,游标查询里明确只查status=1的订单,把无效订单在源头过滤掉;第二,IF EXISTS判断后面,子查询SELECT 1比SELECT *性能更好;第三,v_total累加逻辑放在IF NOT done之内,避免done为TRUE时把重复的FETCH结果累加进去。

程序填空题如果拿这个当原型,挖空点会集中在DELIMITER、DECLARE、CURSOR FOR、CONTINUE HANDLER、REPEAT、UNTIL、CLOSE这些位置。你可以在心里试着把这些词遮住,默写一遍,就知道自己哪里薄弱了。

4.3 在Navicat和命令行里调用:调用的姿势也要规范

存储过程创建好之后,调用本身很简单。命令行里用CALL命令:

CALL sp_stat_dept_order('2025-01-31', @total); SELECT @total;

第一句执行存储过程,传入统计日期,第二个参数传一个用户变量用来接收OUT返回值。调用完成后,SELECT @total;能看到总金额。

Navicat里可以直接选中存储过程,点击运行按钮,在弹出的参数输入界面填参数值。如果你传的是一个普通字符串,注意加上单引号,否则会被当成列名解析报错。这是很多新手在可视化工具里的第一个坑。

4.4 用SQLSugar在.NET里调用存储过程:ORM不是只能查表

如果项目是.NET技术栈,SQLSugar是一个非常好用的ORM框架,它对存储过程的支持也很完整。热词里出现“sqlsugar存储过程”,说明现在Express应用里调存储过程的需求并不少。

用SQLSugar调存储过程,核心是SqlSugarClient的Ado属性。最常用的方式是:

var result = db.Ado.UseStoredProcedure() .GetDataTable("sp_stat_dept_order", new SugarParameter("@p_stat_date", "2025-01-31"), new SugarParameter("@p_total_amount", null, System.Data.DbType.Decimal, ParameterDirection.Output)); decimal total = result.Rows[0][0] == DBNull.Value ? 0 : Convert.ToDecimal(result.Rows[0][0]);

如果你更关心OUT参数,用UseStoredProcedure配合GetDataTable时,可以把输出参数一并传递,返回的表数据是过程里最后SELECT出来的结果集。关于OUT参数,注意SugarParameter的构造重载:第四个参数是ParameterDirection.Output,这样调用完成后同一个SugarParameter对象的Value属性里就会有输出值。

如果你要的是OUT值而非结果集,更清晰的做法是:

var outParam = new SugarParameter("@p_total_amount", null, System.Data.DbType.Decimal, ParameterDirection.Output); db.Ado.UseStoredProcedure().ExecuteCommand("sp_stat_dept_order", new SugarParameter("@p_stat_date", "2025-01-31"), outParam); Console.WriteLine(outParam.Value);

建议在项目里统一封装一个StoredProcedureHelper,把参数构造、异常处理、日志输出都放到公共方法里,避免每个业务点都重复写参数代码。调用完成后,务必判断outParam.Value是否为DBNull,因为存储过程里没给OUT赋值时,客户端收到的值会是DBNull,直接转decimal会抛异常。

5. 常见报错与踩坑实录——每一个都是真金白银换来的教训

5.1 DELIMITER不生效的几种情况

第一个坑是生命周期问题。DELIMITER只在当前会话里生效,如果你断开连接重连,必须再设置一次。用Navicat或DataGrip时,如果创建存储过程总是报错,先检查编辑器底部的会话是否支持多语句执行,有些图形工具默认就把DELIMITER当成普通语句发给服务端,需要关闭“将查询用分号分隔”这类选项。

第二个坑是最容易忽略的:DELIMITER后面必须跟空格。写成DELIMITER$$和DELIMITER $$在多数情况下都能用,但老版本MySQL里如果没空格,分隔符解析可能出错。更稳妥的写法是DELIMITER $$,后面留一个空格。

第三个坑是无意中把DELIMITER写进了存储过程内部。分隔符只是客户端指令,不是数据库服务端语法,所以千万别把DELIMITER写在BEGIN...END块内部,那会直接报语法错误。

5.2 参数名和列名冲突,这是一个隐形的逻辑炸弹

很多初学者会给参数起名叫dept_id,而表里正好也有dept_id列。这时候在WHERE子句里写WHERE dept_id = dept_id,数据库会怎么理解?MySQL在这种情况下会把两个dept_id都当作列名来解析,结果就是条件恒为真,查询结果是全表数据。

更隐蔽的情况是在SET语句里写SET dept_id = dept_id,本意是想把参数值赋给变量,结果两边都指向列名,根本没有赋值效果。避免办法只有两条:一是参数命名统一加前缀,比如p_dept_id、v_dept_id做区分;二是在存储过程开头就养成习惯,任何参数和变量都不与列名同名。

Oracle和openGauss开发者也有个习惯,就是在参数前加p_前缀,变量加v_前缀,这不是什么编码洁癖,而是实打实规避二义性的做法。MySQL同样适用。

5.3 游标和循环的性能陷阱

游标是逐行处理,性能天然不如集合操作。在数据量大的表上跑游标,如果每行都执行一个INSERT或UPDATE,大量行锁、日志和网络往返很快会把数据库拖垮。

建议的做法是:能用JOIN、窗口函数、CASE WHEN聚合解决的,就坚决不用游标;必须用游标时,在游标查询里就把条件收紧,尽量只捞必要字段。另外在循环里做UPDATE前,先仔细检查WHERE条件是否命中索引,否则每行一个全表扫描,直接变成生产事故。

OPEN到CLOSE之间的游标会占用临时表空间,用完务必CLOSE释放。程序填空题里的CLOSE就是这个作用,它不是形式,而是资源管理的一部分。

5.4 权限问题:创建和执行不是一回事

存储过程创建后,调用方不一定有权限执行。MySQL里需要单独授权:

GRANT EXECUTE ON PROCEDURE demo_db.sp_stat_dept_order TO 'app_user'@'localhost';

很多开发者在本地用root写好了存储过程,部署到测试环境直接报“PROCEDURE sp_stat_dept_order does not exist”,排查半天发现其实是账号没有EXECUTE权限导致的,不是过程不存在。

权限设计上还有个专业建议:存储过程内部读哪些表,授权时不需要把这些表的SELECT权限也授给调用方。因为存储过程默认以创建者权限执行,或明确使用SQL SECURITY INVOKER来按调用者权限。这是存储过程在安全层面的优势,也是设计存储过程时一定要考虑清晰的边界。

说到我的经验,最值得记住的一点是:存储过程的代码风格必须比普通SQL更保守,每一步都要考虑异常退出后的状态,该回滚就回滚,该关游标就关游标。程序填空题只考你会不会写,而生产环境会考你能不能善后。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 10:05:18

C语言操作符详解:从算术到位运算,一篇吃透所有运算符

C语言学习记录 日期&#xff1a; 9.18~9.20 &#x1f4d6;今日知识点 ——算术操作符 -*/ %&#x1f4bb;练习代码 &#x1f4d6;今日知识点 ——移位操作符 <<左移操作符&#xff0c;>>右移操作符 &#x1f4bb;练习代码 //移位操作符 //左移操作符&#xff1a;左…

作者头像 李华
网站建设 2026/9/29 9:59:28

嵌入式驱动开发培训机构避坑指南:识别伪驱动课程

做过十多年嵌入式开发&#xff0c;也带过不少新人&#xff0c;这几年收到私信里出现频率最高的问题&#xff0c;就是“怎么选嵌入式驱动开发培训机构”。打开招聘软件看一眼&#xff0c;嵌入式Linux驱动工程师的薪资在硬件岗里确实很能打&#xff0c;于是大量应用开发、纯单片机…

作者头像 李华
网站建设 2026/9/29 9:57:22

读Open Terminal源码:FastAPI+PTY如何把一台电脑变成REST API

读Open Terminal源码:FastAPIPTY如何把一台电脑变成REST API 【免费下载链接】open-terminal A computer you can curl ⚡ 项目地址: https://gitcode.com/gh_mirrors/ope/open-terminal Open Terminal 是一个轻量级的自托管终端 API&#xff1a;它用 FastAPI 做 HTTP 服…

作者头像 李华
网站建设 2026/9/29 9:56:49

UI 自动化踩坑记:一次被百度安全验证拦下的测试

报错在进行《实验案例1&#xff1a;百度搜索软件测试》时发现代码跑完assert "软件测试" in driver.title 测试报错断言期望&#xff1a;页面标题里包含“软件测试”。 实际结果&#xff1a;标题并没有包含软件测试即没有成功跳转调试反复确认代码编写没有错误在AI的…

作者头像 李华