Test the contents of a collection of records
In many cases, you will be working with collections whose elements contain more than just a single scalar. A very common example is a collection of records, each field of which is a scalar. Quest Code Tester for Oracle helps you test such structures very easily.

Flash tutorial explaining the topic: A brief video that shows you how to use Quest Code Tester to solve the problem posed by this topic.

Step-by-step instructions: Follow these steps to replicate what we show you in the tutorial.

Steps

From the Test Dashboard, create a new test definition by selecting New from the Test definition for program right-click menu. Select qctod#dyn_placeholder from the list and press OK. Click OK on the Welcome to Test Builder dialog. You are inside Test Builder.  Use the Program Details toolbox to set the current program to ALL_IN_STRING.  

Set up the test cases

Before diving into the details of each of the test cases, use "top down design" to brainstorm the various test cases that you will need. You do this in the Test Case list:

Press the "Add test case" button  to create a new test case and rename the test cases using the following names:

  • No placeholders.
  • One placeholder.
  • Two placeholders.

No placeholders

Click on No placeholder in the Test Case List. In this case, the collection returned by the program should be empty. So:

  • In the Inputs grid, set STRING_IN to "ABC", stick with the default for Boolean argument.
  • The Outcome grid should show you an outcome that compares the collection returned by the function to another collection of the same type. For this test case, we expect the collection to be empty (no placeholders), set the Operator dropdown to "is empty"
  • That’s it, next test case!

One placeholder

  • In the Inputs Grid, click on one placeholder in the Test Case List. Set STRING_IN to "ABC :DEF". DEF is the lone placeholder. Note that the position of the ":" is 5. Stick with the default for Boolean argument.
  • The Outcome grid should show you an outcome that compares the collection returned by the function to another collection of the same type. Perfect! Now we only need to set the properties and contents of that expected or control collection.
  • Click on Expected Results value field […] button to set the Properties.
  • Starting at the top, the Variable Name is the name of the local variable that we will declare for you in the generated code. You can generally leave it unchanged, but you will be using it. So we will copy it into the clipboard. Note: the name is currently set to "e_Function_return_value1", but that can change, so pay close attention!
  • Further down, we have the "Are-Elements-Equal Function" section. For this collection of records, you will need to provide that function. You see, there is no built-in way with PL/SQL to compare one record to another, even if you know the actual type of the record in advance (which Quest Code Tester does not). So you have to provide the name and/or implementation of that function.
  • Good news! The qctod#dyn_placeholder already contains such a function, called EQ. Put the following text into the Function Name field:

                   QCTOD#DYN_PLACEHOLDER.EQ

If you have not yet written this function, you can do so in the original package you are testing, or you can paste the code directly into the box below the "Use implementation of function below". You still need to provide the name of the function separately from the implementation.

  • Finally, we need to fill up this array with data to match what we expect to be found in the collection returned by the program. So put the following code into the Initialization section:

e_Function_return_value1 (1).name := 'DEF';

e_Function_return_value1 (1).position := 5;

Those two values, in that single row, are exactly what should be returned by all_in_string.

  • Press OK and we are done with this test case.

Two placeholders

Click on the two placeholders in the Test Case list. In this case, the input string has two placeholders in it. So we repeat many of the steps from the previous test case.

  • In the Input Grid, set STRING_IN to "ABC :DEF HIJ :123". DEF and 123 are both placeholders. Note that the positions of the two ":"s are 5 and 14. Stick with the default for Boolean argument.
  • The Outcome grid should show you an outcome that compares the collection returned by the function to another collection of the same type. Perfect! Now we only need to set the properties and contents of that expected or control collection.
  • Click on Expected Results […] to set the Properties.
  • Starting at the top, the Variable Name is the name of the local variable that we will declare for you in the generated code. You can generally leave it unchanged, but you will be using it. So we will copy it into the clipboard. Note: the name is currently set to "e_Function_return_value1", but that can change, so pay close attention!
  • Enter QCTOD#DYN_PLACEHOLDER.EQ in the “Are-Elements-Equal Function name field.
  • Finally, we need to fill up this array with data to match what we expect to be found in the collection returned by the program. So put the following code into the Initialization section:

e_Function_return_value1 (1).name := 'DEF';

e_Function_return_value1 (1).position := 5;

e_Function_return_value1 (2).name := '123';

e_Function_return_value1 (2).position := 14;

Those four values, in those two rows, are exactly what should be returned by all_in_string.

  • Press OK and we are done with this test case.

Time to save/run your test!

You can now save your test definition and run your test with a single press of the Save, Close, and Run  button in the Test Cases toolbar:

You will then see the results in the Results Viewer. After reviewing your results, close the Test Viewer.

Source code of program tested: Compile this code in your schema so that you can build your test definition for it.

CREATE OR REPLACE PACKAGE qctod#dyn_placeholder
/*
Dynamic PL/SQL Placeholder Analyzer Package

Author: Steven Feuerstein, steven@stevenfeuerstein.com
Created: December 20, 2005

Overview: Helps you analyze strings for placeholders, which is defined as the
colon character ":" followed by anything but =.

Most programs have a parameter named dyn_plsql_in. If your string is part of
a dynamic PL/SQL block, you should pass TRUE for this argument. If you pass
NULL and the string ends with a semi-colon, then I will assume it is a
dynamic PL/SQL block. Why bother with this? In a dynamic SQL block, you need
to provide a bind variable for each placeholder, even if there are multiple
with the same name. In a dynamic PL/SQL block, you only provide a value for
the first occurrence of each placeholder (by name).

Note: these programs do not take into account : characters that occur
within a comment or within a literal.
*/
IS
   /* Return two pieces of information about each placeholder:
      1. The name (without the colon) upper-cased.
      2. The location in the string (starting with the colon)
   */
   TYPE placeholder_rt IS RECORD (
      NAME VARCHAR2 (100)
    , POSITION PLS_INTEGER
   );

   -- Associative array type to hold information about all placeholders.
   TYPE placeholder_aat IS TABLE OF placeholder_rt
      INDEX BY PLS_INTEGER;

   -- Return the number of placeholders found in the string.
   FUNCTION count_in_string (
      string_in IN VARCHAR2
    , dyn_plsql_in IN BOOLEAN DEFAULT FALSE
   )
      RETURN PLS_INTEGER DETERMINISTIC;

   -- Return the nth placeholder in the string.
   FUNCTION nth_in_string (
      string_in IN VARCHAR2
    , nth_in IN PLS_INTEGER
    , dyn_plsql_in IN BOOLEAN DEFAULT FALSE
   )
      RETURN placeholder_rt DETERMINISTIC;

   -- Return all the placeholders in an associative array.
   FUNCTION all_in_string (
      string_in IN VARCHAR2
    , dyn_plsql_in IN BOOLEAN DEFAULT FALSE
   )
      RETURN placeholder_aat DETERMINISTIC;

   -- Display all the placeholders.
   PROCEDURE show_placeholders (
      list_in IN placeholder_aat
    , dyn_plsql_in IN BOOLEAN DEFAULT FALSE
   );

   PROCEDURE show_placeholders (
      string_in IN VARCHAR2
    , dyn_plsql_in IN BOOLEAN DEFAULT FALSE
   );

   FUNCTION eq (
      rec1_in IN placeholder_rt
    , rec2_in IN placeholder_rt
    , nullq_eq_in IN BOOLEAN DEFAULT TRUE
   )
      RETURN BOOLEAN DETERMINISTIC;
END qctod#dyn_placeholder;
/

SHO ERR

CREATE OR REPLACE PACKAGE BODY qctod#dyn_placeholder
IS
   FUNCTION only_identifier_from (
      string_in IN VARCHAR2
    , position_in IN PLS_INTEGER
   )
      RETURN VARCHAR2
   IS
      c_delimiters CONSTANT VARCHAR2 (100)
         := '!@%^&*()-=+\|`~{[]};:''",<.>/? ' || CHR (10) || CHR (13)
            || CHR (9);
      l_end PLS_INTEGER;
   BEGIN
      l_end :=
         INSTR (TRANSLATE (string_in
                         , c_delimiters
                         , RPAD (CHR (2), LENGTH (c_delimiters), CHR (2))
                          )
              , CHR (2)
              , position_in
               );

      IF l_end = 0
      THEN
         RETURN SUBSTR (string_in, position_in);
      ELSE
         RETURN SUBSTR (string_in, position_in, l_end - position_in);
      END IF;
   END only_identifier_from;

   FUNCTION all_in_string (
      string_in IN VARCHAR2
    , dyn_plsql_in IN BOOLEAN DEFAULT FALSE
   )
      RETURN placeholder_aat
   IS
      c_is_dynplsql CONSTANT BOOLEAN
                            := NVL (dyn_plsql_in, SUBSTR (string_in, -1) =
                                                                          ';');
      l_start PLS_INTEGER := 1;
      l_loc PLS_INTEGER;
      l_placeholders placeholder_aat;

      PROCEDURE add_placeholder (loc_in IN PLS_INTEGER)
      IS
         l_row PLS_INTEGER := l_placeholders.FIRST;
         c_last CONSTANT PLS_INTEGER := l_placeholders.LAST;
         l_name VARCHAR2 (32767);
         l_already_used BOOLEAN := FALSE;
      BEGIN
         l_name := UPPER (only_identifier_from (string_in, loc_in + 1));

         IF c_is_dynplsql
         THEN
            WHILE (NOT l_already_used AND l_row <= c_last)
            LOOP
               l_already_used := l_name = l_placeholders (l_row).NAME;
               l_row := l_row + 1;
            END LOOP;
         END IF;

         IF NOT l_already_used
         THEN
            l_row := l_placeholders.COUNT + 1;
            l_placeholders (l_row).NAME := l_name;
            l_placeholders (l_row).POSITION := loc_in;
         END IF;
      END add_placeholder;
   BEGIN
      LOOP
         l_loc := INSTR (string_in, ':', l_start);
         EXIT WHEN l_loc = 0 OR l_loc IS NULL;

         IF SUBSTR (string_in, l_loc + 1, 1) != '='
         THEN
            add_placeholder (l_loc);
         END IF;

         l_start := l_loc + 1;
      END LOOP;

      RETURN l_placeholders;
   END all_in_string;

   FUNCTION count_in_string (
      string_in IN VARCHAR2
    , dyn_plsql_in IN BOOLEAN DEFAULT FALSE
   )
      RETURN PLS_INTEGER
   IS
      l_placeholders placeholder_aat;
   BEGIN
      l_placeholders := all_in_string (string_in, dyn_plsql_in);
      RETURN l_placeholders.COUNT;
   END count_in_string;

   FUNCTION nth_in_string (
      string_in IN VARCHAR2
    , nth_in IN PLS_INTEGER
    , dyn_plsql_in IN BOOLEAN DEFAULT FALSE
   )
      RETURN placeholder_rt
   IS
      l_placeholders placeholder_aat;
   BEGIN
      l_placeholders := all_in_string (string_in);

      IF nth_in > l_placeholders.COUNT
      THEN
         RETURN NULL;
      ELSE
         RETURN l_placeholders (nth_in);
      END IF;
   END nth_in_string;

   PROCEDURE show_placeholders (
      list_in IN placeholder_aat
    , dyn_plsql_in IN BOOLEAN DEFAULT FALSE
   )
   IS
      l_index PLS_INTEGER := list_in.FIRST;
   BEGIN
      WHILE (l_index IS NOT NULL)
      LOOP
         DBMS_OUTPUT.put_line (   list_in (l_index).NAME
                               || ' - '
                               || list_in (l_index).POSITION
                              );
         l_index := list_in.NEXT (l_index);
      END LOOP;
   END show_placeholders;

   PROCEDURE show_placeholders (
      string_in IN VARCHAR2
    , dyn_plsql_in IN BOOLEAN DEFAULT FALSE
   )
   IS
   BEGIN
      show_placeholders (all_in_string (string_in, dyn_plsql_in));
   END show_placeholders;

   FUNCTION eq (
      rec1_in IN placeholder_rt
    , rec2_in IN placeholder_rt
    , nullq_eq_in IN BOOLEAN DEFAULT TRUE
   )
      RETURN BOOLEAN
   IS
   BEGIN
      RETURN (    (   rec1_in.NAME = rec2_in.NAME
                   OR (rec1_in.NAME IS NULL AND rec2_in.NAME IS NULL)
                  )
              AND (   rec1_in.POSITION = rec2_in.POSITION
                   OR (rec1_in.POSITION IS NULL AND rec2_in.POSITION IS NULL
                      )
                  )
             );
   END eq;
END qctod#dyn_placeholder;
/

SHO ERR