DeepEdit!

Программирование баз данных на Oracle, техническая документация, литература, статьи и публикации

  • Увеличить размер шрифта
  • Размер шрифта по умолчанию
  • Уменьшить размер шрифта

Реализация утилиты тестирования PL/SQL-блоков Oracle


Тест блока – это такая подпрограмма, которая создается разработчиком для проверки корректности работы “блока”, как правило, отдельной процедуры или функции. Тестирование системы и тестирование функциональности значительно отличаются друг от друга; причем последнее из них предназначено для тестирования возможностей, как отдельного приложения, так и всей системы в целом. Невозможно корректно и эффективно выполнить тестирование системы до тех пор, пока еще не известно, работают ли отдельные подпрограммы как требуется. 
Можно, конечно, предположить, что программисты с достаточной ответственностью тестируют подпрограммы и получают, соответственно, высокий уровень их надежности. О, если бы это действительно было так! На самом деле разработчики обычно выполняют недостаточное число неполных тестов и полагают, что если они не нашли ошибку, то ее и не существует. Почему так получается? Подсчитаем возможные причины... 

Психология успешной и не успешной работы.

 Мы до такой степени увлекаемся получением корректной работы подпрограммы, что иногда отбрасываем не только плохие новости, но даже возможность их появления. Лучше немного протестировать по шагам и убедиться, что в целом работа выглядит корректной, а затем ждать, чтобы кто-нибудь другой нашел ошибки, если они существуют (как будто были какие-то сомнения). 

Давление конечного срока.

 Это же эпоха Internet! Сроки продаж определяют все. Все должно быть сделано еще вчера, поэтому мы уподобляемся Microsoft и Netscape: выпускаем систему, недоработанную даже до бета-версии, под видом готовой к эксплуатации и предоставляем пользователям возможность тестировать и испытывать ее. 

Отсутствие понимания у руководства.

 Общеизвестно, что IT-менеджеры - это люди, не понимающие процесса разработки программного обеспечения. Когда недостаточно времени и возможностей (включая, в наилучшем случае, тестирование, документирование, усовершенствование и т.д.) для правильного написания кода, то работа над ним всегда заканчивается, несмотря на незавершенные части, наличие которых никто не признает. 

Потеря времени на настройку и выполнение тестов.

 Когда написание и запуск тестов требуют больших затрат, их можно и не создавать. Времени не хватает, а всегда есть и другие задачи, которые необходимо решить, и т.д. В итоге все больше и больше работы по тестированию перекладывается на отдел QA (Quality Assurance – контроль качества), если он существует. Такая передача ответственности, с одной стороны, одобряется. Профессиональные высоко квалифицированные тестеры могут значительно повлиять на качество приложения. Однако разработчики все же должны отвечать за тестирование блоков своего кода; в противном случае процесс тестирования/контроля качества будет гораздо труднее и продолжительнее по времени. 
Из выше перечисленного можно сделать вывод, что код, как правило, необходимо тестировать более тщательно. Я не могу помочь с предельными сроками, и мои возможности, позволяющие убедить руководство в необходимости выделить больше времени на тестирование, тоже ограничены. Поэтому вместо них я могу предложить "утилиту" – набор процессов и подпрограмм – которая упростит тестирование. Эта утилита называется utPLSQL (Unit Test PL/SQL – Тестирование Блоков PL/SQL); а в настоящей статье описывается ее реализация. 
Переход к экстремальному программированию 
Недавно при знакомстве с “Экстремальным программированием” (Extreme Programming - XP) меня приятно поразило автоматизированное тестирование. Вы слышали об экстремальном программировании? Его название немного пугает, однако там содержатся действительно замечательные идеи! Посетите http://www.xprogramming.com/ и http://www.extremeprogramming.org/, чтобы найти подробную информацию о нем. Кроме того, я рекомендую разыскать книгу Кента Бека "

Extreme Programming Explained"

. Слово "экстремальный" не означает, что Бек и другие приравнивают написание кода к прыжку с самолета без парашюта с высоты 15,000 футов. Просто XP придерживается общеизвестных принципов и правил экстремального уровня: 

"Если перепросмотр кода нужен, то надо выполнять его постоянно (парное программирование). Если тестирование нужно, то все должны выполнять его постоянно (тестирование блоков), даже клиенты (функциональное тестирование)". 

-Extreme Programming Explained, глава XV 

В этой статье не будет рассматриваться парное программирование (применительно к этой концепции, а не только к программам. Каждый программист работает в паре, причем один из них выполняет тактические задачи, а другой стратегически улучшает код). Вместо этого остановимся на так называемом "регулярном тестировании". Звучит нелепо, не так ли? Так могло бы быть, если бы не нашелся способ тестировать блоки в процессе их разработки, благодаря которому этот процесс стал бы обычной и эффективной частью работы над кодом. 
XP рекомендует: 
Перед тем, как написать код, создайте для него тест. Следуя этому принципу, обеспечьте вызов этого кода, подключив минимум логики, и опишите требования. ("То, что код должен выполнить, чтобы считаться корректным".) 
Перед тем, как зафиксировать ошибку, напишите тест, в котором она будет обнаруживаться. В том случае, если тест будет добавлен в набор таких же тестов, появляется возможность удостовериться, что эта ошибка зафиксирована, и, что еще лучше, другие разработчики могут легко добавлять свои тесты в такой набор и запускать их. 
Тесты следует запускать автоматически и следовать правилу "красного и зеленого света": если код работает, загорается "зеленый свет". Если в тесте что-то не так, то "красный свет" сообщает, где обнаружена ошибка.
Чтобы разобраться в технологии красного и зеленого цвета, я приведу пример запуска теста с помощью utPLSQL. Я создал пакет для тестирования PL/Vision-пакета PLVstr и запустил тест, выполнив единственную команду: 
SQL> exec utplsql.test ('plvstr')
plvstr SUCCESS
Зеленый свет!
 
Если в коде обнаруживается ошибка, то вместо “зеленого” должен загореться “красный” свет, например: 
SQL> exec utplsql.test ('plvstr')
plvstr FAILURE
betwn_numbers: 
  Typical execution; expected "like", got "dislike"
 (Обычное выполнение; ожидалось "like", а есть "dislike")
Это же просто, не так ли? Взгляните: несмотря на то, что тестирование нам не нравится, мы знаем, что необходимо проделать еще большую работу. Чтобы разработчики могли выполнять более тщательное тестирование, необходимо сделать этот процесс по возможности более простым, быстрым и безболезненным. Тест – это то средство, которое позволяет улучшить качество кода. Он не является окончательным или самостоятельным. Поэтому утилита для тестирования должна быть очень простой – простой в установке и использовании, простой в изменении и запуске. 
Требования к реализации 
Я считаю, что utPLSQL может значительно повлиять на качество кода и его производительность. Рассказать о нем помог замечательный издатель моих книг, O'Reilly & Associates, который предоставил возможность доступа к этому пакету в режиме online. 
Исходный текст можно получить по ссылке “Download file” компании, и/или из полной документации в http://oracle.oreilly.com/utplsql, где можно более подробно узнать о применении utPLSQL наряду с другими возможностями разработки. Далее в этой статье мне бы хотелось познакомить вас с реализацией utPLSQL. Перед чтением этой статьи надо было бы просмотреть документацию и примеры utPLSQL; хотя я думаю, вы и так поймете, как используется его интерфейс. 
Прежде чем вплотную заняться реализацией, рассмотрим требования к этой утилите. Она должна быть как можно проще в использовании. Как видно из примера этой статьи, показанного ранее, для тестирования пакета PLVstr не нужно выполнять никаких действий, кроме: 
exec utPLSQL.test ('plvstr'); 
В виде параметра передается имя пакета, а затем тест автоматически запускается. Тест вы должны, конечно же, написать самостоятельно. Сам utPLSQL, разумеется, не может понять требований к тестируемой подпрограмме и способ ее выполнения. Поэтому тесты пишутся в виде набора процедур, которые следует выполнить. Пакет utPLSQL должен: 

Найти тестовые процедуры исключительно по имени пакета; 

Настроить параметры тестирования (описать или заполнить структуры данных, например); 

Выполнить найденные тестовые процедуры, запоминая порядок получения результатов; 

Определить, в каком месте тестовая процедура прерывается, и сформировать отчет о результатах (красный или зеленый свет?); и 

Очистить данные, образовавшиеся после тестирования. 

Это основное. Кроме того, желательно, чтобы utPLSQL сохранял результаты тестирования в специальном репозитории и предоставлял удобный GUI для просмотра результатов. Может быть позднее... С помощью кого-нибудь из разработчиков! 
Создание utPLSQL 
Итак, я проникся идеей XP и думал о том, как бы применить их к PL/SQL. Многое из того, что я прочитал об утилитах для тестирования, используется в объектно-ориентированном программировании (см. информацию о системах для Java, C++, Smalltalk, и т.п. в http://www.xprogramming.com/software.htm)
В Java, а именно в Junit (это открытая система для тестирования Java-блоков), разработчики могут воспользоваться преимуществами так называемых "interface class" ("классов элементов интерфейса"). Этот вид классов описывает интерфейс или API, которому должны подчиняться все классы, созданные разработчиками для использования Junit. Вы просто включаете класс Junit-интерфейса в свой собственный класс, пишете процедуру для каждого стандартного элемента интерфейса и затем, с помощью "reflection" (отражения) и функциональности Java-классов, используете свой класс. 
Да, так устроено в Java, и звучит весьма неплохо. Однако, в данном случае я "привязан" к PL/SQL. В нем нет классов, а есть значительно ограниченная возможность "отражения" (способность анализа структуры и свойств объектов и Java-классов). Есть еще объектные типы. Объектные типы Oracle, к сожалению, не поддерживают наследование, из-за чего они становятся совершенно бесполезными с точки зрения объектно-ориентированного программирования. Oracle, между прочим, предполагает добавить поддержку наследования в версии 8.2; однако сейчас объектные типы не могут помочь в создании утилиты типа utPLSQL. 
Поэтому лучше не пытаться бороться с "объектами" PL/SQL, а идти по пути создания пакета (как и могли бы легко догадаться все, кто когда-нибудь читал мои статьи и книги или приходил на мои лекции). Я создал серию пакетов, включающих обобщенную систему тестирования, а также процедуры, которые могут использоваться для ее поддержки (их можно скачать из http://oracle.oreilly.com/utplsql; см. Таблицу 1 со списком компонентов utPLSQL, некоторые из которых рассматриваются в этой статье). В дальнейшем можно создать свои тестовые пакеты, которые будут запускаться автоматически из utPLSQL. 
Таблица 1. Компоненты utPLSQL. 
Компонент 
Тип 
Описание 
ut_suite
Таблица
Содержит наборы тестов. Набор тестов состоит из одного и более пакетов тестов. Создается файлом tables.sql.
ut_package
Таблица
Содержит пакеты, запускаемые из набора. Создается файлом tables.sql.
ut_test
Таблица
Описание отдельного теста. Тест может представлять собой одну пакетную процедуру или функцию, а также самостоятельную процедуру или функцию. Создается файлом tables.sql.
ut_testcase
Таблица
Описание событий теста. В одном тесте могут происходить различные события в зависимости от входных параметров и ожидаемого поведения. Создается файлом tables.sql.
utPLSQL
Пакет
Основной пакет для запуска тестов. Создается файлом code.sql.
utSuite
Пакет
API таблицы ut_suite. Создается файлом code.sql.
utPackage
Пакет
API таблицы ut_ package. Создается файлом code.sql.
utTest
Пакет
API таблицы ut_test. Создается файлом code.sql.
utTestcase
Пакет
API таблицы ut_testcase. Создается файлом code.sql.
utAssert
Пакет
Набор доступных процедур, обрабатывающих результаты тестирования. Создается файлом code.sql.
utResult
Пакет
API массива данных, который заполняется при обращении к процедурам пакета utAssert. Создается файлом code.sql.
utGen
Пакет
Генерирует шаблон теста для заданного пакета. Создается файлом code.sql.

Замечание

: в utPLSQL используются все возможные средства Oracle8i, и в значительной степени – встроенный динамический SQL. Он не может работать с Oracle8 и Oracle7, хотя изменения для преобразования его к более ранним версиям Oracle, не должны вызывать затруднений. 
Идентификация теста 
Итак, при вызове теста необходимо передать название пакета: 

exec utplsql.test ('plvstr') 

а на его основании идентифицировать тестируемые процедуры этого пакета и запустить их. Каким образом это можно сделать? Существует два основных (и дополняющих друг друга) метода: 

Использовать таблицы Oracle для хранения описания информации о тестах соответствующего пакета. 

Использовать динамический SQL для формирования названий тестируемых процедур исходного пакета, а затем выполнять эти программы (опять же динамически). 

Утилита utPLSQL поддерживает оба метода. В этой статье я уделяю внимание только второму, так как он же используется для "прогона" самого теста. 
Вы уже видели пример вызова utPLSQL.test. А теперь рассмотрим весь заголовок этой процедуры более внимательно: 
PROCEDURE uPLSQL.test (
    package_in IN VARCHAR2,
    samepackage_in IN BOOLEAN := FALSE,
    prefix_in IN VARCHAR2 := c_prefix,
    recompile_in IN BOOLEAN := TRUE,
    dir_in IN VARCHAR2 := NULL
 );
В виде параметров передаются: имя тестируемого пакета (или отдельной процедуры/функции), параметр, показывающий, в том ли же пакете находится тестируемый код или в другом (по умолчанию он в другом), префикс для формирования имени отдельного тестового пакета (по умолчанию "ut_"), флаг, показывающий, следует ли перекомпилировать этот пакет перед его выполнением, а также директория, содержащая исходный код теста. 
Таким образом, вызов процедуры utPLSQL.test с параметром - именем пакета "plvstr" - на самом деле означает: "Найди отдельный пакет ut_plvstr, расположенный в директории по умолчанию для пакета utPLSQL. Скомпилируй спецификацию и тело этого пакета, а затем выполни все тестовые процедуры, которые будут найдены в этом пакете". 
Предположим, что тест был помещен в пакет, расположенный в отдельной директории. Для его вызова необходимо выполнить следующую команду: 
BEGIN
    utPLSQL.test (
       'plvstr',
       TRUE,
       dir_in => '/apps/studentreg'
    );
 END;
Рассмотрим реализацию utPLSQL.test (сверьте текст файла utplsql.pkb с текстом на сайте O'Reilly): 
PROCEDURE test
 ...
 BEGIN
    setpkg (package_in, 
      samepackage_in, 
      prefix_in,
      v_pkg);
    
    IF do_recompile (recompile_in)
    THEN
       compile (
         v_pkg || '.pks', dir_in);
       compile (
         v_pkg || '.pkb', dir_in);
    END IF;
Процедура setpkg связывает параметры с записью текущего теста, описанной на уровне пакета. Затем, если это требуется, выполняется перекомпиляция спецификации и тела пакета. В данном случае в utPLSQL используется процедура для "генерации из файла" с соответствующими комментариями: 
PROCEDURE compile (
    file_in IN VARCHAR2,
    dir_in IN VARCHAR2
 )
 IS
    fid UTL_FILE.file_type;
    v_dir VARCHAR2 (2000)
       := NVL (dir_in, g_dir);
    linenum PLS_INTEGER;
    lines DBMS_SQL.VARCHAR2S;
    cur PLS_INTEGER
       := DBMS_SQL.OPEN_CURSOR;
"Подожди-ка минутку, дружище", может сказать придирчивый читатель. "Почему используется DBMS_SQL, если ранее говорилось об использовании встроенного динамического SQL?" Это необходимо, так как файлы исходного кода, которые надо откомпилировать, имеют произвольный размер и, кроме того, включают NDS, а максимальная длина строки, которую я могу передать для компиляции – 32K. В DBMS_SQL может использоваться версия PARSE (начиная с Oracle8), которая для SQL-предложений большого размера позволяет использовать массивы, имеющие тип DBMS_SQL.VARCHAR2S. 
Итак, файл открывается с помощью новой версии оператора PARSE, допускающего строки длиной до 32767: 
BEGIN
    fid :=
       UTL_FILE.fopen (v_dir,
          file_in,
          'R',
          max_linesize => 32767
       );
Затем содержимое файла перекачивается в индексную таблицу. Когда весь файл будет прочитан, возникнет исключение NO_DATA_FOUND, поэтому его надо обработать и выйти из цикла: 
LOOP
       BEGIN
          linenum := 
            NVL (lines.LAST, 0) + 1;
          UTL_FILE.get_line (fid,
             lines (linenum)
          );
       EXCEPTION
          WHEN NO_DATA_FOUND
          THEN
             EXIT;
       END;
END LOOP;
Теперь можно заняться практическими вопросами. Многие исходные файлы оканчиваются на символ "/", который в SQL*Plus является командой для выполнения DDL-предложения, компилирующего код. Однако в динамическом SQL этот символ вызывает ошибку компиляции. Поэтому все пустые строки и символы "/", расположенные в конце строки, удаляются: 
/* Удаление символов ограничения. */
    LOOP
       IF    lines (lines.LAST) = '/'
          OR RTRIM (lines (lines.LAST)) IS NULL
       THEN
          lines.delete (lines.LAST);
       ELSE
          EXIT;
       END IF;
    END LOOP;
 
    UTL_FILE.fclose (fid);
 
Теперь откомпилируем код. Для этого вызывается DBMS_SQL.parse со следующими параметрами: 
DBMS_SQL.parse (cur,
       lines,
       lines.FIRST,
       lines.LAST,
       TRUE,
       DBMS_SQL.native
    );
    DBMS_SQL.close_cursor (cur);
Не забудьте очистить данные при возникновении исключений (для упрощения вместо DBMS_OUTPUT.PUT_LINE в utPLSQL используется процедура pl): 
EXCEPTION
    WHEN OTHERS
    THEN
       pl ('Ошибка компиляции: ' || SQLERRM);
       UTL_FILE.fclose (fid);
 
       IF DBMS_SQL.is_open (cur)
       THEN
          DBMS_SQL.close_cursor (cur);
       END IF;
 END;
Только что описанный в utPLSQL.test этап гарантирует, что будет использоваться последняя версия теста. А теперь можно настроить параметры тестирования (которые, как вы увидите, еще и "регистрируют" все тесты пакета) и перейти к выполнению самого тестирования. 
Настройка параметров тестирования 
Этап настройки в utPLSQL состоит из двух отдельных функций: 

"Регистрации" тестов. 

Инициализации всех тестовых данных. 

Логика настройки должна содержаться в процедуре setup, заголовок которой выглядит следующим образом: 
PROCEDURE setup; 
Другими словами, для того чтобы utPLSQL корректно инициализировал тесты, в тестовом пакете необходимо создать процедуру setup без параметров. 
Рассмотрим процедуру setup на примере, в котором регистрируются два теста и описываются структуры тестовых данных (копирует базовую таблицу для тестирования операций insert и delete): 
PROCEDURE setup
 IS
 BEGIN
    teardown;
    utplsql.addtest ('DEL');
    EXECUTE IMMEDIATE 
       'CREATE TABLE ut_DEL AS
        SELECT * FROM employee';
 
    utplsql.addtest ('INS');
    EXECUTE IMMEDIATE 
       'CREATE TABLE ut_INS AS
        SELECT * FROM employee';
 END;
addtest регистрирует процедуру, подлежащую тестированию. Для этого она добавляет ее в список "тестов", как показано ниже: 
PROCEDURE addtest (
    package_in IN VARCHAR2,
    NAME_IN IN VARCHAR2,
    prefix_in IN VARCHAR2 := NULL,
    iterations_in IN PLS_INTEGER := 1
 )
 IS
    indx PLS_INTEGER
      := NVL (tests.LAST, 0) + 1;
 BEGIN
    tests (indx).pkg := package_in;
    tests (indx).prefix := prefix_in;
    tests (indx).name := NAME_IN;
    tests (indx).iterations :=
      iterations_in;
 END;
Можно, конечно же, использовать другой метод – получать информацию из постоянных таблиц. Однако преимущество именно такого подхода заключается в том, что тестовый пакет можно легко изменить (вызвать utPLSQL.addtest, чтобы добавить еще один вызов в процедуру setup) и сразу выполнить utPLSQL.test. При этом пакет перекомпилируется, а новый тест будет добавлен в список. Выполнять вставку в таблицу базы данных не нужно. 
Конечно, недостаточно только добавить процедуру setup. utPLSQL должен выполнить ее перед тестированием. Вернемся к телу пакета utPLSQL.test и найдем строку, показанную ниже: 
runprog ('setup', TRUE); 
где процедура runprog определена следующим образом: 
PROCEDURE runprog (
    name_in IN VARCHAR2,
    propagate_in IN BOOLEAN := FALSE
 )
 IS
В ней формируется имя тестового пакета и имена самих тестов, входящих в этот пакет (структура этих имен зависит от нескольких параметров, включающих параметр, показывающий, чем является тестируемый код, пакетом или отдельной процедурой, а также параметр, показывающий, входит ли тестируемый код в тот же самый пакет, и другие): 
v_pkg VARCHAR2 (100)
       := pkgname (
             testpkg.pkg,
             testpkg.samepkg,
             testpkg.prefix
          );
    v_name VARCHAR2 (100)
       := progname (
             name_in,
             testpkg.samepkg,
             testpkg.prefix
          );
    v_str VARCHAR2 (32767);
Создается корректный PL/SQL-блок для вызова тестируемой процедуры и затем выполняется с помощью встроенного динамического SQL: 
BEGIN
    v_str := 
       'BEGIN ' || 
          v_pkg || '.' || v_name || 
       '; END;';
    EXECUTE IMMEDIATE v_str;
Далее необходимо обработать ошибки, которые могут появиться, что позволит легко выявить проблемные ситуации: 
EXCEPTION
    WHEN OTHERS
    THEN
       pl ('Ошибка компиляции "' || SQLERRM || '" в: ');
       pl (v_str);
 END;
После того, как utPLSQL.test завершит выполнение процедуры setup тестового пакета, все тесты будут содержаться в списке, и все структуры данных будут созданы. 
Выполнение тестов и проверка результатов 
Все тесты определены в списке тестов. Таким образом, теперь можно пройти по этому списку, чтобы выполнить их: 
indx := tests.FIRST;
 LOOP
    EXIT WHEN indx IS NULL;
    runit (indx);
    indx := tests.NEXT (indx);
 END LOOP;
Где runit – простая процедура для вызова runprog и запуска отдельного теста: 
PROCEDURE runit (indx_in IN PLS_INTEGER)
 IS
 BEGIN
    setcurrcase (indx_in);
    runprog (tests (indx_in).name, FALSE);
 END;
Замечание: setcurrcase связывает параметры с записью, объявленной в спецификации пакета utPLSQL. Эта запись используется в utResult для регистрации результатов тестирования. Взгляните на объявление записи currcase в спецификации пакета utPLSQL и ее заполнение в теле этого пакета. Затем обратите внимание на ее использование в пакете utResult. Это замечательный пример "уязвимой" структуры данных. Другими словами, это слабая реализация, которая допускает ошибки, постоянно проникающие в основной код utPLSQL. 
На самом деле эта запись должна быть скрыта в теле пакета utPLSQL; доступ к ее содержимому из utResult должен осуществляться только через набор "охраняющих" процедур и функций. Почему я выбрал именно такой путь? От лени. Следует ли мне отлавливать ошибки? Несомненно. 
Вот и весь код, который требуется для запуска тестов. А как же просмотр результатов тестирования? Вы видели что-нибудь? Нет, не видели. Это потому, что процедура отслеживания результатов скрыта в процедурах пакета utPLSQL, которые обрабатывают результаты тестирования. Обработка результатов происходит по окончанию выполнения теста и позволяет определить, успешно ли он выполнился. Если тест завершился с ошибкой, то процедура-обработчик выполняет действия по сохранению всех результатов. 
Проследим за этими операциями, взяв тестовую процедуру, в которой тестируется пакет для подсчета строк таблицы employee. Процедура ut_te_employee.deptcount тестирует функцию te_employee.deptcount. Эта функция возвращает количество сотрудников в заданном отделе. Для того чтобы увидеть, правильно ли она работает, количество вычисляется сначала с помощью "прямого" SQL, а затем с помощью функции: 
PROCEDURE ut_te_employee.deptcount
 IS
 BEGIN
    -- Выполнение кода напрямую.
    SELECT COUNT (*)
      INTO g_rowcount1
      FROM employee
     WHERE department_id = 30;
 
    -- Сравнение с вызовом процедуры:
    g_rowcount2 :=
       te_employee.deptcount (30);
Теперь получены два значения, которые можно сравнить с помощью процедуры-обработчика utAssert.eq. На самом деле решается вопрос, совпадают ли эти два значения? 
    -- Проверка результатов
    utassert.eq (
       'deptcount завершена успешно',
       g_rowcount2,
       g_rowcount1
    );
 END;
Пакет utAssert содержит множество процедур-обработчиков результатов, т.е. процедур, которые позволяют пользователю легко: 

проверять некоторые условия (Совпадают ли два значения? Совпадают ли две таблицы? Совпадают ли два файла? и т.д.); а также: 

обращаться к функциям и структурам пакета utResult для отслеживания результатов тестирования. 

Итак, давайте рассмотрим процедуру utAssert.eq: 
PROCEDURE eq (
    msg_in IN VARCHAR2,
    check_this_in IN VARCHAR2,
    against_this_in IN VARCHAR2,
    null_ok_in IN BOOLEAN := FALSE,
    raise_exc_in IN BOOLEAN := FALSE
 )
 IS
 BEGIN
    IF utplsql.tracing
    THEN
       utplsql.pl (
          'Сравнивается "' || 
          check_this_in || 
          '" с "' ||
          against_this_in ||
          '"'
       );
    END IF;
 
    this (
       expected (msg_in, 
          check_this_in, 
          against_this_in),
       check_this_in = against_this_in,
       null_ok_in,
       raise_exc_in
    );
 END;
Обработчику utAssert.eq передается текст сообщения, два значения, которые необходимо сравнить, параметр, показывающий, следует ли считать совпадающими два NULL-значения, и логический параметр обработки ошибки. В теле подпрограммы выполняется дополнительное отслеживание (очень полезное, чтобы видеть, каким образом создан код, и необходимое для понимания, что происходит в процессе его выполнения) а затем вся работа перекладывается на основную процедуру обработки "this", т.е. "Обработать это". Заметьте, что второй аргумент utAssert.this представляет собой булево выражение. Итак, рассмотрим процедуру this: 
 PROCEDURE this (
    msg_in IN VARCHAR2,
    check_this_in IN BOOLEAN,
    null_ok_in IN BOOLEAN := FALSE,
    raise_exc_in IN BOOLEAN := FALSE,
    register_in IN BOOLEAN := TRUE
 )
 IS
 BEGIN
    IF    NOT check_this_in
       OR (    check_this_in IS NULL
           AND NOT null_ok_in)
    THEN
       IF register_in
       THEN
          utresult.report (msg_in);
       ELSE
          utplsql.pl (msg_in);
       END IF;
 
       IF raise_exc_in
       THEN
          RAISE test_failure;
       END IF;
    END IF;
 END;
 
Другими словами, если выражение check-this возвращает FALSE или NULL (причем NULL не означает, что условие выполнено), то необходимо что-то делать. Говоря "Что-то", предполагается: если найдена ошибка, надо вызвать процедуру utResult.report для вывода отчета об ошибке. Иначе просто отобразить на экране сообщение. Или, при необходимости вызова исключения (которое приведет к остановке теста), вызвать это исключение. 
Таким образом, процедура, скрытая в обработчике и являющаяся частью "внутреннего API" пакета utPLSQL, – это механизм получения отчета и трассировки. 
Отчет о результатах тестирования 
Для начала рассмотрим процедуру utResult.report, а затем механизм вывода на экран (красный и зеленый свет). Ниже показана процедура report: 
PROCEDURE report (
    msg_in IN VARCHAR2)
 IS
    indx PLS_INTEGER := 
       NVL (results.LAST, 0) + 1;
 BEGIN
    results (indx).name := 
       utplsql.currcase.name;
    results (indx).indx := 
       utplsql.currcase.indx;
    results (indx).msg := msg_in;
 END;
Из текста видно, что в виде параметра передается только строка сообщения. Однако внутри процедуры report происходит обращение к содержимому "глобальной" записи utPLSQL.currcase, соответствующая информация которой переносится в массив результатов. И снова это не самый лучший метод. Лучше было бы передать этой процедуре текущую информацию в виде параметров. 
Таким образом, по завершении выполнения процедурой utPLSQL.test каждого теста, зарегистрированного процедурой setup, массив результатов будет пустым (зеленый свет) или содержать данные, по которым можно судить об ошибке. А теперь рассмотрим процедуру utResult.show, которая вызывается из utPLSQL.test: 
PROCEDURE show
 IS
    indx PLS_INTEGER := results.FIRST;
 BEGIN
    IF success
    THEN
       utplsql.pl ('Успешное завершение: "' || 
          utplsql.currpkg || '"');
    ELSIF failure
    THEN
       utplsql.pl ('Ошибки при выполнении: "' || 
          utplsql.currpkg || '"');
 
       LOOP
          EXIT WHEN indx IS NULL;
          utplsql.pl (
             results (indx).name || ': ' ||
                results (indx).msg
          );
          indx := results.NEXT (indx);
       END LOOP;
    END IF;
 END;
где success и failure – это внутренние функции пакета utResult, которые не содержат ничего, кроме: 
FUNCTION success
    RETURN BOOLEAN
 IS
 BEGIN
    RETURN (resultcount = 0);
 END;
 
 FUNCTION failure
    RETURN BOOLEAN
 IS
 BEGIN
    RETURN (resultcount > 0);
 END;
Удаление среды тестирования 
Завершающий этап, выполняемый utPLSQL.test, - это удаление всех структур данных, файлов и т.д., которые могли быть созданы в процессе выполнения тестов или являться результатами их работы. По тому же принципу, какого придерживается utPLSQL, необходимо создать процедуру в тестовом пакете со следующим заголовком: 

PROCEDURE teardown; 

Ниже приведен пример процедуры teardown, которая при помощи встроенного динамического SQL удаляет таблицы: 
 PROCEDURE teardown
 IS
 BEGIN
    BEGIN
       EXECUTE IMMEDIATE 'DROP TABLE ut_ins';
    EXCEPTION
       WHEN OTHERS
       THEN
          NULL;
    END;
 
    BEGIN
       EXECUTE IMMEDIATE 'DROP TABLE ut_del';
    EXCEPTION
       WHEN OTHERS
       THEN
          NULL;
    END;
 END;
В этой процедуре используется динамический SQL, так как к моменту выполнения процедуры setup эти таблицы могут и не существовать. А если в тестовом пакете будут содержаться прямые ссылки на эти структуры данных, то он не будет скомпилирован. 
Процедура teardown вызывается из utPLSQL.test через процедуру runprog, показанную ранее: 

runprog ('teardown', TRUE); 

Динамическое тестирование 
Как можно видеть, встроенный динамический SQL широко применяется в utPLSQL для достижения высокого уровня гибкости. В этой автоматизированной утилите для тестирования нет модных классов элементов интерфейса для описания API. Однако эту особенность удалось эмулировать путем "глобализации" API и использования динамического SQL для формирования и выполнения подпрограмм. Все, что требовалось сделать, это следовать правилам описания подпрограмм в тестовом пакете. 
Я рекомендую попробовать utPLSQL. Используйте его для улучшения тестирования. Если тестирование выполняется достаточно часто, то вы сможете убедиться, что при использовании автоматизированного пакета для тестирования код будет написан лучше и быстрее, чем когда-либо раньше. Поэтому реализация архитектуры utPLSQL представляет интерес для разработки наиболее гибких и адаптируемых утилит. Надеюсь, что у вас тоже есть идеи, которые могли бы быть полезны всем разработчикам, и вы можете реализовать их подобно utPLSQL. Я надеюсь услышать их от вас. 
 









jAntivirus