utplsql是oracle生态中唯一成熟、可ci集成的原生pl/sql单元测试框架,因其支持命令行调用、事务自动隔离、机器可读报告及会话状态清理,而sql developer测试套件仅是单次交互式脚本执行器,缺乏自动化必需的ci入口、事务回滚、结构化断言和生命周期钩子。
utplsql 是目前 oracle 生态中唯一成熟、可 ci 集成的原生 pl/sql 单元测试框架。其他方式(如 sql developer 内置测试、手动 dbms_output 验证)无法满足自动化、隔离性、可重复执行等工程要求。
为什么不能只用 SQL Developer 的“测试套件”功能?
SQL Developer 提供的图形化测试向导看似便捷,但它本质是**单次交互式脚本执行器**,不具备以下关键能力:
- 无法在 CI 流水线中调用(没有命令行入口或 API)
- 不支持事务自动回滚,测试污染真实表后需人工清理
- 断言靠肉眼比对
DBMS_OUTPUT或结果集,无法生成机器可读的失败报告 - 测试用例之间无生命周期控制(比如没有
@beforeeach/@aftereach)
换句话说:它适合临时调试,但不是自动化测试工具。
ut.run() 为什么必须配合事务控制才能真正自动化?
PL/SQL 存储过程常修改表数据(如 UPDATE employees SET salary = ...),若测试直接操作生产同名表,一次失败就可能留下脏数据,导致后续测试不可靠。UTPLSQL 本身不自动开启事务——你得自己管。
正确做法是在测试包中显式使用事务钩子:
CREATE OR REPLACE PACKAGE ut_pkg_sales AS --%suite <p>--%beforeeach PROCEDURE setup_transaction;</p><p>--%test PROCEDURE test_bonus_calculation; END; /</p><p>CREATE OR REPLACE PACKAGE BODY ut_pkg_sales AS PROCEDURE setup_transaction IS BEGIN SAVEPOINT test_start; -- 记录起点 END;</p><p>PROCEDURE test_bonus_calculation IS l_result NUMBER; BEGIN -- 调用被测过程(会改表) pkg_commission.award_bonus(101, 50000);</p><pre class="brush:php;toolbar:false;">SELECT bonus INTO l_result FROM employees WHERE employee_id = 101; ut.expect(l_result).to_equal(2500); -- 断言
END;
--%aftereach PROCEDURE teardown IS BEGIN ROLLBACK TO test_start; -- 每次测试后回滚,彻底隔离 END; END; /
漏掉 ROLLBACK TO 或误用 COMMIT,会导致测试间状态泄漏——这是最常踩的坑。
如何让 UTPLSQL 测试跑进 GitLab CI?
核心是用 ut.run() 的输出格式 + 命令行驱动。UTPLSQL v3+ 提供 ut_runner.run() 并支持 JSON/XML 报告输出,CI 工具可直接解析。
关键步骤:
- 在 CI 脚本中用
sqlplus连接测试库,执行BEGIN ut.run('ut_pkg_sales', a_reporter => ut_junit_reporter()); END; - 将输出重定向到
report.xml,再用 GitLab 的artifacts:reports:junit解析 - 确保测试用户有
EXECUTE权限给UT包,且测试对象(如pkg_commission)已部署到位
注意:ut_junit_reporter 是默认支持的,无需额外安装;但若用 ut_coverage_html_reporter,需提前在数据库中编译对应包体。
全局包变量和 DBMS_SESSION.SET_IDENTIFIER 怎么隔离?
很多业务包依赖 pkg_state.g_user_id 或 DBMS_SESSION.SET_IDENTIFIER('sess_123') 做上下文路由。这类状态跨会话不隔离,但 UTPLSQL 测试默认复用同一数据库会话。
必须在 @beforeeach 中重置:
--%beforeeach
PROCEDURE setup_context IS
BEGIN
pkg_state.g_user_id := NULL; -- 清空包变量
DBMS_SESSION.SET_IDENTIFIER('test_' || SYSTIMESTAMP); -- 设唯一标识
END;
否则,测试 A 设置了 g_user_id := 999,测试 B 可能意外读到它——这种 bug 极难复现,却真实高频发生。
真正自动化的核心不在“能不能跑”,而在“每次跑完是否像没跑过一样”。事务回滚、包变量清空、会话标识重设,这三件事缺一不可。少做一步,CI 上的测试就只是看起来自动化而已。











