utPLSQL示例二

示例一

1、测试一个完整包API

大多数包由多个程序组成,通常需要测试包规范中的每一个程序。使用utGen生成一个测试包时,它会为每个程序产生一个模板单元测试程序,然后修改这些模板程序。

一个更复杂的例子是表封装包。这种包创建了一个代码层,即应用需求与底层数据结构之间的控制层。虽然这种做法对于PL/SQL开发人员并不常见,它却是一种强烈推荐做法。事实上,许多工具都提供了自动的表封装包生成,包括Oracle Designer、RevealNet's PL/Generator以及许多IDE工具。

假如使用PL/Generator为employee表生成了一个表封装包。代码参见te_employee.pks与te_employee.pkb文件。它包含了几十个程序,意味着需要创建许多单元测试。而且,许多程序执行了DML操作。那么,如何简单可靠地测试这些程序呢?

当处理许多拥有一致的结构与行为的程序时,应该寻找生成测试包的方法,而不是手动编写。utGen不能生成这样的测试,因为封装包的逻辑与环境相关。

然而,可以创建自定义的生成器或者使用能够满足需求的已有生成器。

Steven使用CGML(代码生成标记语言)创建了一个模板(参见te_utpkg.gdr文件),读取数据字典信息并定义设置程序、清除程序以及单元测试程序的一个好的起点。以下是设置程序的模板逻辑:

   PROCEDURE {utprefix}setup
   IS
   BEGIN
      -- Clean start
      {utprefix}teardown;
[ASIS]   
      -- Generic copy of base table for testing 
      EXECUTE IMMEDIATE 
         'CREATE TABLE {tabprefix}[objname] AS
            SELECT * FROM [objname]';
            
[ENDASIS]   
   [FOREACH]prog
   [IF] {allprogs} [EQ]Y [OR] [progname] [LIKE] UPD% [OR]
 [progname] [LIKE] INS% [OR] [progname] [LIKE] DEL% -- Create copy of base table for this unit test. EXECUTE IMMEDIATE 'CREATE TABLE ^{progtab}^ AS [ASIS] SELECT * FROM [objname]'; [ENDASIS] [ENDIF] [ENDFOREACH] END;

以下是部分生成逻辑(参见ut_te_employee.pks与tu_te_employee.pkb文件),测试删除操作的程序:

PROCEDURE ut_del1
   IS
      fdbk PLS_INTEGER;
   BEGIN
      /* Delete that finds now rows. */

      EXECUTE IMMEDIATE '
      DELETE FROM ut_DEL1
       WHERE employee_id = -1
      ';
      te_employee.del (-1, rowcount_out => fdbk);
      -- Test results
      utassert.eqtable ('Delete rows', 'EMPLOYEE', 'ut_DEL1');
      /* Successful delete */

      EXECUTE IMMEDIATE '
      DELETE FROM ut_DEL1
       WHERE employee_id between 7800 and 7899
      ';

      FOR rec IN (SELECT *
                    FROM employee
                   WHERE employee_id BETWEEN 7800 AND 7899)
      LOOP
         te_employee.del (
            rec.employee_id,
            rowcount_out => fdbk
         );
      END LOOP;

      -- Test results
      utassert.eqtable ('Delete rows', 'EMPLOYEE', 'ut_DEL1');
      ROLLBACK;
   EXCEPTION
      WHEN OTHERS
      THEN
         utassert.this (
            'DEL1 exception ' || SQLERRM,
            SQLCODE = 0
         );
   END;

以上程序测试了两个场景:删除0行的操作与删除指定行的操作。在两种情况中都针对employee表的一个副本(在设置程序中创建的副本)显式地执行了DML操作,然后使用API程序针对employee表执行了相同的操作。最后调用断言程序比较结果。在程序的结束时调用ROLLBACK回滚原始表的数据状态。注意,在异常处理部分的断言程序捕获了所有错误并将它标记为一个失败的测试。

1.1、设置数据结构

构建独立的测试

独立的测试允许执行一个、全部或者部分测试,而不需要担心其他测试的影响或依赖。它对于测试DML操作尤其重要。验证DML操作的方法是分析“源”表与“测试”表的内容。如果所有测试都修改相同的测试表,将会很难验证是否成功。

因此,运行DML操作的单元测试的最好方式是为每一个测试创建一个单独的测试表。包te_employee的设置程序如下所示:

PROCEDURE ut_setup
   IS
   BEGIN
      ut_teardown;
      EXECUTE IMMEDIATE 'CREATE TABLE ut_employee AS
            SELECT * FROM employee';
      EXECUTE IMMEDIATE 'CREATE TABLE ut_DEL1 AS
            SELECT * FROM employee';
      EXECUTE IMMEDIATE 'CREATE TABLE ut_DELBY_EMP_DEPT_LOOKUP AS
            SELECT * FROM employee';
      EXECUTE IMMEDIATE 'CREATE TABLE ut_DELBY_EMP_JOB_LOOKUP AS
            SELECT * FROM employee';
      EXECUTE IMMEDIATE 'CREATE TABLE ut_DELBY_EMP_MGR_LOOKUP AS
            SELECT * FROM employee';
      EXECUTE IMMEDIATE 'CREATE TABLE ut_INS1 AS
            SELECT * FROM employee';
      EXECUTE IMMEDIATE 'CREATE TABLE ut_UPD1 AS
            SELECT * FROM employee';
      EXECUTE IMMEDIATE 'CREATE TABLE ut_UPD$HIRE_DATE1 AS  
            SELECT * FROM employee';
      EXECUTE IMMEDIATE 'CREATE TABLE ut_UPD$SALARY1 AS
            SELECT * FROM employee';
   END;

首先,调用清除程序删除所有的数据结构以确保一个全新的开始。然后使用动态SQL创建所有的表。

然后执行测试。

1.2、清除数据结构

以下是包te_employee的清除程序:

PROCEDURE ut_teardown
   IS
   BEGIN
      BEGIN
         EXECUTE IMMEDIATE 'DROP TABLE ut_employee';
      EXCEPTION
         WHEN OTHERS
         THEN
            NULL;
      END;

      BEGIN
         EXECUTE IMMEDIATE 'DROP TABLE ut_DEL1';
      EXCEPTION
         WHEN OTHERS
         THEN
            NULL;
      END;

      BEGIN
         EXECUTE IMMEDIATE 'DROP TABLE ut_DELBY_EMP_DEPT_LOOKUP';
      EXCEPTION
         WHEN OTHERS
         THEN
            NULL;
      END;

      BEGIN
         EXECUTE IMMEDIATE 'DROP TABLE ut_DELBY_EMP_JOB_LOOKUP';
      EXCEPTION
         WHEN OTHERS
         THEN
            NULL;
      END;

      BEGIN
         EXECUTE IMMEDIATE 'DROP TABLE ut_DELBY_EMP_MGR_LOOKUP';
      EXCEPTION
         WHEN OTHERS
         THEN
            NULL;
      END;

      BEGIN
         EXECUTE IMMEDIATE 'DROP TABLE ut_INS1';
      EXCEPTION
         WHEN OTHERS
         THEN
            NULL;
      END;

      BEGIN
         EXECUTE IMMEDIATE 'DROP TABLE ut_UPD1';
      EXCEPTION
         WHEN OTHERS
         THEN
            NULL;
      END;

      BEGIN
         EXECUTE IMMEDIATE 'DROP TABLE ut_UPD$HIRE_DATE1';
      EXCEPTION
         WHEN OTHERS
         THEN
            NULL;
      END;

      BEGIN
         EXECUTE IMMEDIATE 'DROP TABLE ut_UPD$SALARY1';
      EXCEPTION
         WHEN OTHERS
         THEN
            NULL;
      END;

   END;

再次使用动态SQL,每个DROP TABLE包含一个异常部分,以便DROP失败时能够继续执行。

示例三
请使用浏览器的分享功能分享到微信等