如何(单元)测试数据密集型 PL/SQL 应用程序

发布于 2024-08-29 13:30:18 字数 549 浏览 5 评论 0原文

我们的团队愿意对在扩展现有庞大 Oracle 系统的正在运行的项目下编写的新代码进行单元测试。

该系统仅用 PL/SQL 编写,由数千个表、数百个存储过程包组成,主要是从表中获取数据和/或插入/更新其他数据。

我们的扩展也不例外。大多数函数从许多相互绑定的表上的相当复杂的 SELECT 语句返回数据(在返回它们之前添加一些逻辑),或者从一个复杂的数据结构转换为另一个复杂的数据结构(以另一种方式复杂化)。

对此类代码进行单元测试的最佳方法是什么?

现有代码库没有单元测试。更糟糕的是,只有包、触发器和视图是受源代码控制的,表结构(包括“alter table”内容和必要的数据转换是通过版本控制以外的渠道部署的)。在我们的项目范围内没有办法改变这一点。

维护测试数据集似乎是不可能的,因为每周都有新代码部署到生产环境,通常不会事先通知,经常更改数据结构(此处添加一列,在那里删除一列)。

我很高兴有任何建议或参考来帮助我们。一些团队成员往往会因为弄清楚如何开始而感到疲倦,因为我们的单元测试经验并不涵盖 PL/SQL 数据密集型遗留系统(仅限于那些“从书本上”的新建 Java 项目)。

Our team is willing to unit-test a new code written under a running project extending an existing huge Oracle system.

The system is written solely in PL/SQL, consists of thousands of tables, hundreds of stored procedures packages, mostly getting data from tables and/or inserting/updating other data.

Our extension is not an exception. Most functions return data from a quite complex SELECT statementa over many mutually bound tables (with a little added logic before returning them) or make transformation from one complicated data structure to another (complicated in another way).

What is the best approach to unit-test such code?

There are no unit tests for existing code base. To make things worse, only packages, triggers and views are source-controlled, table structures (including "alter table" stuff and necessary data transformations are deployed via channel other than version control). There is no way to change this within our project's scope.

Maintaining testing data set seems to be impossible since there is new code deployed to the production environment on weekly basis, usually without prior notice, often changing data structure (add a column here, remove one there).

I'd be glad for any suggestion or reference to help us. Some team members tend to be tired by figuring out how to even start for our experience with unit-testing does not cover PL/SQL data intensive legacy systems (only those "from-the-book" greenfield Java projects).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

一腔孤↑勇 2024-09-05 13:30:18

有几种不同的 PL/SQL 测试工具。 Steven Feuerstein 写了其中两篇,utplsqlQuest 代码测试器Oracle(以前称为 QUTE)。我是 utplsql 的忠实粉丝,但它不再有活跃的支持社区(这很遗憾)。它也往往非常冗长,尤其是在设置测试装置时。它确实具有纯 PL/SQL 包的基本虚拟性;源代码有点粗糙,但它是 FOSS。

QCTO 带有 GUI,这意味着 - 与其他 Quest 产品(即 TOAD)一样 - 它仅适用于 Windows。它并不完全自动化测试数据生成,但它提供了一个接口来支持它。与其他 Quest 产品一样,QCTO 已获得许可,但有免费软件副本。

Steven(透露,他是我的 Oracle 英雄之一)编写了所有 PL/SQL 测试工具的功能比较。显然,QOTC 名列前茅,但我认为这种比较是诚实的。 查看一下。

关于 utplsql 中测试装置的建议

管理单元测试的测试数据可能是一个真正的难题。不幸的是 utplsql 没有提供太多来承担这个负担。因此,

  • 请始终针对已知值进行测试:
    • 避免使用 dbms_random;
    • 尝试将序列的使用限制在值无关紧要的列中;
    • 日期也很棘手。避免硬编码日期:使用用 sysdate 填充的变量。学会欣赏 add_months()last_day()intervaltrunc(sysdate, 'MM')、等等
  • 将测试数据与其他用户隔离。从头开始构建它。尽可能使用独特的价值观。
  • 仅创建您需要的测试数据。体积测试是不同的职责。
  • 当测试更改数据的过程时,为每个单元测试创​​建特定的记录。
  • 另外:不要依赖一个测试的成功输出来提供另一测试的输入。
  • 当测试程序时,只需在适当的时候在单元测试之间共享数据报告的数据共享记录。
  • 尽可能共享框架数据(例如引用的主键)。
  • 使用自由文本字段(名称、描述、注释)来标识哪个或哪些测试使用该记录。
  • 最大限度地减少创建新记录所涉及的工作:
    • 仅分配测试套件和表约束所需的值;
    • 尽可能使用默认值;
    • 尽可能程序化。

其他需要记住的事情:

  • 设置测试夹具可能是一项耗时的工作。如果您有大量数据,请考虑构建一个过程来设置每个会话可以运行一次的静态数据,并且在 ut_setup 本身中仅包含易失性数据。这在测试只读功能时特别有用。
  • 请记住,创建测试数据本身就是一种编程练习,因此很容易出现错误。
  • 使用 utplsql 的所有功能。 utAssert.EqQueryutAssert.EqQueryValueutAssert.EqTableutAssert.EqTabCountutAssert.Eq_RefC_Query 在推断易失性数据的值时都是非常有用的功能。
  • 当诊断未按我们预期方式进行的测试运行时,拥有所使用的数据会很有用。因此,请考虑使用一个空的 ut_teardown 过程,并在 ut_setup 开始时清除测试数据。

处理遗留代码

对 Gary 帖子的评论让我想起了另一件您可能会觉得有用的事情。 Steven F 编写了 ulplsql 作为 JUnit 的 PL/SQL 实现,JUnit 是 Test First 运动的 Java 先锋。然而,TDD 技术也可以应用于大量遗留代码(在这种情况下,遗留代码是没有任何单元测试的任何程序集)。

要记住的关键是您不必立即对所有内容进行单元测试。逐步开始。为新东西构建单元测试,测试优先。在应用更改之前,为要更改的部分构建单元测试,这样您就知道它们在更改后仍然有效。

这个领域有很多想法,但(不可避免的是,可耻的是)它主要来自 OO 程序员。迈克尔·费瑟斯 (Michael Feathers) 是主角。阅读他的文章有效使用旧代码。如果您觉得有帮助,他随后写了一本同名的书。

There are several different test tools for PL/SQL out there. Steven Feuerstein has written two of them, utplsql and Quest Code Tester for Oracle (formerly QUTE). I am a big fan of utplsql, but it no longer has an active support community (which is a shame). It also tends to be quite verbose, especially when it comes to setting up test fixtures. It does have the cardinal virtual of being pure PL/SQL packages; the source code is a bit gnarly but it is FOSS.

QCTO comes with a GUI, which means - like other Quest products i.e. TOAD - it is Windows only. It doesn't exactly automate test data generation, but it provides an interface to support it. Also like other Quest products, QCTO is licensed although there is a freeware copy.

Steven (disclosure, he he is one of my Oracle heroes) has written a feature comparison of all the PL/SQL testing tools. Obviously, QOTC comes out tops, but I think the comparison is honest. Check it out.

Advice on test fixtures in utplsql

Managing test data for unit testing can be a real pain in the neck. Unfortunately utplsql doesn't offer much to shoulder the burden. So

  • Always test against known values:
    • Avoid using dbms_random;
    • Try to restrict the use of sequences to columns whose values don't matter;
    • Dates are also tricky. Avoid hard-coding dates: use variables which are populated with sysdate. Learn to appreciate add_months(), last_day(), interval, trunc(sysdate, 'MM'), etc.
  • Isolate the test data from other users. Build it from scratch. Use distinctive values wherever possible.
  • Only create as much test data as you need. Volumetric testing is a different responsibility.
  • When testing procedures which change the data create specific records for each unit test.
  • Also: don't rely on the successful output from one test to provide the input from another test.
  • When testing procedures which simply report against data share records between unit tests when appropriate.
  • Share framework data (e.g. referenced primary keys) whenever possible.
  • Use free text fields (names, descriptions, comments) to identify which test or tests use the record.
  • Minimise the work involved in creating new records:
    • Only assign values which are necessary to the test suite and the table's constraints;
    • Use default values as much as possible;
    • Proceduralize as much as possible.

Other things to bear in mind:

  • setting up a test fixture can be a time-consuming exercise. If you have a lot of data consider building a procedure to set up the static data which can be run once per session, and include only volatile data in the ut_setup itself. This is especially helpful when testing read-only functionality.
  • remember that creating test data is a programming exercise in its own right, and so prone to bugs.
  • use all the features of utplsql. utAssert.EqQuery, utAssert.EqQueryValue, utAssert.EqTable, utAssert.EqTabCount and utAssert.Eq_RefC_Query are all very useful features when it comes to inferring the values of volatile data.
  • when diagnosing a test run which didn't go the way we were expecting it can be useful to have the data which was used. So consider having a hollow ut_teardown procedure and clearing down the test data at the start of ut_setup.

Dealing with legacy code

Commenting on Gary's post reminded me of one other thing you may find useful. Steven F wrote ulplsql as a PL/SQL implementation of JUnit, the Java vanguard of the Test First movement. However, the techniques of TDD can be also applied to large amounts of legacy code (in this context, legacy code is any set of programs without any unit tests).

The key thing to bear in mind is that you don't have to get everything under unit test immediately. Start incrementally. Build unit tests for new stuff, Test First. Build unit tests for the bits you're going to change before you apply the change, so you know they still work after you have made the change.

There is a lot of thought in this area, but (inevitably if shamefully) it mainly comes from the OO programmers. Michael Feathers is the main chap. Read his article Working Effectively With Legacy Code. If you find it helpful he subsequently wrote a book of the same name.

两个我 2024-09-05 13:30:18

以下面的场景为例

FUNCTION ret_count (local_client_id IN number) RETURN NUMBER IS
  v_ret number;
BEGIN
  SELECT count(*) INTO v_ret FROM table_1 WHERE client_id = local_client_id;
  RETURN v_ret;
END;

非常简单的功能,但有一大堆可能出错的东西。数据类型转换、索引、统计数据都可能影响查询路径、性能,在某些情况下还会影响错误。还有很多松散耦合,例如会话设置(例如语言首选项)。如果有人向 table_1 添加了“LOCAL_CLIENT_ID”列,则该函数的整个逻辑就会发生变化。

我个人觉得TDD不太适合这个环境。由知识渊博的人进行代码审查将更有可能发现问题。

如果你有钱,那么看看Oracle的RAT(真实应用程序测试)进行回归测试。

Take the following scenario

FUNCTION ret_count (local_client_id IN number) RETURN NUMBER IS
  v_ret number;
BEGIN
  SELECT count(*) INTO v_ret FROM table_1 WHERE client_id = local_client_id;
  RETURN v_ret;
END;

Very simple function but there is a whole mess of stuff that can go wrong. Datatype conversions, indexing, stats could all impact query paths, performance and, in some cases, errors. There's also a lot of loose coupling such as session settings (eg linguistic preferences). If someone came along and added a column "LOCAL_CLIENT_ID" to table_1, the whole logic of the function changes.

I personally don't feel that TDD is suitable for this environment. Code reviews by knowledgable individuals will have a greater chance of catching problems.

If you've got money, then look at Oracle's RAT (real application testing) for regression testing.

孤蝉 2024-09-05 13:30:18

我使用 DbFit 对 PL/SQL 代码进行单元测试。尝试一下。

I've used DbFit to unit-test PL/SQL code. Give it a try.

云朵有点甜 2024-09-05 13:30:18

utPLSQL 可能会有所帮助,但听起来您需要一种更好的方法来维护测试数据。您可能还想查看 Swingbench

utPLSQL might help, but it sounds like you need a better way of maintaining test data. You might want to also look at Swingbench for that.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文