Oracle gem of the day: TAP compliant testing in PL/SQL

jdavidb on 2006-08-15T20:03:23

This is my own personal test framework for testing PL/SQL modules. It's been around since before TAP had a name, actually. In the past I have built a public test subroutine right into packages which I then call from a short wrapper SQL*Plus script for testing. (I usually call the wrapper script t.sql, so I can test with three keystrokes. :) ) At times I've found it better to put the tests into the t.sql script itself.

Maybe somebody can clean this up and make something better out of it.

Note that IS is a reserved word in SQL, so I couldn't name a routine is within the T package. And so I named it tis: name of the package + "is", or "tis" if you like to use older English and sound like you're reciting the Night before Christmas. :)

CREATE OR REPLACE PACKAGE T
IS

PROCEDURE plan;
PROCEDURE end_tests;
PROCEDURE ok(condition BOOLEAN, name VARCHAR2 := NULL);
PROCEDURE pass(name VARCHAR2 := NULL);
PROCEDURE fail(name VARCHAR2 := NULL);
PROCEDURE todo(condition BOOLEAN, name VARCHAR2 := NULL);
PROCEDURE tis(got VARCHAR2, expected VARCHAR2, name VARCHAR2 := NULL);
PROCEDURE tis(got BOOLEAN, expected BOOLEAN, name VARCHAR2 := NULL);
PROCEDURE set_verbose(p_on BOOLEAN);

END T;
/
show errors
CREATE OR REPLACE PACKAGE BODY T
IS

test_count INTEGER := 0;
traffic_light BOOLEAN := TRUE;
success_count INTEGER := 0;
start_time DATE;
verbose BOOLEAN := TRUE;

PROCEDURE plan
IS
BEGIN
  start_time := SYSDATE;
  test_count := 0;
  traffic_light := TRUE;
  success_count := 0;
END plan;

PROCEDURE end_tests
IS
  success_percentage VARCHAR2(7);
  interval NUMBER;
BEGIN
  success_percentage := TO_CHAR(100 * success_count / test_count, '999D99');
  DBMS_OUTPUT.PUT_LINE(success_percentage || ' percent of tests succeeded.');
  interval := SYSDATE - start_time;
  interval := interval * 24 * 60 * 60;
  DBMS_OUTPUT.PUT_LINE(TO_CHAR(interval, '999D99') || ' seconds elapsed');
  IF traffic_light
  THEN
    DBMS_OUTPUT.PUT_LINE('green light');
  ELSE
    DBMS_OUTPUT.PUT_LINE('RED LIGHT');
  END IF;
END end_tests;

PROCEDURE ok(condition BOOLEAN, name VARCHAR2 := NULL)
IS
BEGIN
  IF condition
  THEN
    pass(name);
  ELSE
    fail(name);
  END IF;
END ok;

FUNCTION name_output(name VARCHAR2 := NULL)
RETURN VARCHAR2
IS
BEGIN
  IF name IS NOT NULL
  THEN
    RETURN ' - ' || name;
  ELSE
    RETURN NULL;
  END IF;
END name_output;

PROCEDURE pass(name VARCHAR2 := NULL)
IS
BEGIN
  test_count := test_count + 1;
  success_count := success_count + 1;
  IF verbose
  THEN
    DBMS_OUTPUT.PUT_LINE('ok ' || TO_CHAR(test_count) || name_output(name));
  END IF;
END pass;

PROCEDURE fail(name VARCHAR2 := NULL)
IS
BEGIN
  test_count := test_count + 1;
  DBMS_OUTPUT.PUT_LINE('not ok ' || TO_CHAR(test_count) || name_output(name));
  traffic_light := FALSE;
END fail;

PROCEDURE todo_pass(name VARCHAR2 := NULL)
IS
BEGIN
  test_count := test_count + 1;
  success_count := success_count + 1;
  DBMS_OUTPUT.PUT_LINE('ok ' || TO_CHAR(test_count) || name_output(name) ||
                       ' # UNEXPECTEDLY SUCCEEDED');
END todo_pass;

PROCEDURE todo_fail(name VARCHAR2 := NULL)
IS
BEGIN
  test_count := test_count + 1;
  DBMS_OUTPUT.PUT_LINE('not ok ' || TO_CHAR(test_count) || name_output(name) ||
                       ' # TODO');
  -- don't change traffic_light
END todo_fail;

PROCEDURE todo(condition BOOLEAN, name VARCHAR2 := NULL)
IS
BEGIN
  IF condition
  THEN
    todo_pass(name);
  ELSE
    todo_fail(name);
  END IF;
END todo;

PROCEDURE tis_fail(got VARCHAR2, expected VARCHAR2, name VARCHAR2 := NULL)
IS
BEGIN
  fail(name);
  DBMS_OUTPUT.PUT_LINE('#      got: ' || nvl(got, 'NULL'));
  DBMS_OUTPUT.PUT_LINE('# expected: ' || nvl(expected, 'NULL'));
END tis_fail;

PROCEDURE tis(got VARCHAR2, expected VARCHAR2, name VARCHAR2 := NULL)
IS
BEGIN
  IF got = expected
  THEN
    pass(name);
  ELSE
    tis_fail(got, expected, name);
  END IF;
END tis;

FUNCTION bto_char(p BOOLEAN)
RETURN VARCHAR2
IS
BEGIN
  IF p
  THEN
    RETURN 'TRUE';
  ELSIF NOT p
  THEN
    RETURN 'FALSE';
  ELSE
    RETURN NULL;
  END IF;
END bto_char;

PROCEDURE tis(got BOOLEAN, expected BOOLEAN, name VARCHAR2 := NULL)
IS
BEGIN
  IF got = expected
  THEN
    pass(name);
  ELSE
    tis_fail(bto_char(got), bto_char(expected), name);
  END IF;
END tis;

PROCEDURE set_verbose(p_on BOOLEAN)
IS
BEGIN
  verbose := p_on;
END set_verbose;

END T;
/
show errors