Первая часть этой второй статьи C. Фернстайна
“Построение утилиты анализа кода” была опубликована в:
/ru/oramag/octnov2003/dev_feuer_codech_21.html
]
В этой статье, я не определяю точно полный набор результатов для всех случаев тестирования; будет достаточно, я думаю, дать вам почувствовать общий процесс. Давайте рассмотрим пару таких случаев из таблицы 2, это позволит вам ощутить тип информации, которую необходимо тестировать. Рассмотрим процедуру samefamily1. Вот спецификация этой перегрузки:
CREATE OR REPLACE PACKAGE allargs_test
IS
PROCEDURE samefamily1 (arg IN NUMBER);
PROCEDURE samefamily1 (arg IN INTEGER);
Очевидно, что типы данных аргумента arg слишком похожи; они оба являются частью числового семейства. Следовательно, это неоднозначная перегрузка, которая будет вызывать проблемы. Результатом запуска Codecheck для samefamily1 должно быть что-то вроде этого:
allargs_test.samefamily: неверная перегрузка
Вот так, это выглядит весьма основательно. Мы только говорим: да или нет? Является ли она правильной или неправильной? В этом случае, я полагаю, так. Но давайте посмотрим на другой, более интересный сценарий. Рассмотрим заголовок для noparms2:
CREATE OR REPLACE PACKAGE allargs_test
IS
PROCEDURE noparms2 (
arg1 IN VARCHAR2 := NULL,
arg2 IN VARCHAR2 := NULL);
PROCEDURE noparms2 (
arg1 IN VARCHAR2 := NULL,
arg2 IN VARCHAR2 := NULL,
arg3 IN VARCHAR2 := NULL);
Что мне скажет Codecheck об этих двух программах? Позвольте мне посчитать неверные или двусмысленные способы, которыми может быть вызвана процедура allargs_test.noparms2:
allargs_test.noparms2;
allargs_test.noparms2 ('abc');
allargs_test.noparms2 ('abc', 'def');
Codecheck должен определить, что существуют три различных неверных вызова для процедуры noparms2. Это связано с тем, что хвостовые значения по умолчанию позволяют вызывать noparms2 с различным значением параметров. Это хороший пример, более сложный, чем первый случай тестирования. И, конечно, может существовать сколь угодно много более сложных случаев, зависящих от количества перегружаемых программ, количества параметров и присутствия значений по умолчанию.
Как можно использовать utPLSQL, чтобы проверить результаты типа представленных выше автоматически? Тестирующая среда предлагает широкий набор программ утверждения (assertion). Например, можно проверить, являются ли две числовые величины равными, что мы уже видели в примере с betwnstr:
utassert.eq ('zero start', check_this, against_this);
Однако, можно выполнить и гораздо более сложные утверждения. Можно проверить, является ли результат NULL. Можно проверить, являются ли две таблицы, два запроса, два файла, два канала базы данных, или две коллекции равными. Можно проверить, было ли вызвано конкретное исключение. Можно даже, в последней версии (2.0.10.2) utPLSQL (и благодаря вкладу Райнера Медерта), анализировать результат из DBMS_OUTPUT, чтобы увидеть удовлетворяет ли он моим ожиданиям.
Можно ли использовать какую-либо из вышеприведенных проверок утверждения для тестирования Codecheck? Я упоминал раньше, что, моим первым побуждением при написании такой утилиты было отображать результат на экране. (На самом деле, ранний прототип Codecheck, который вы можете найти в скрипте args_analysis.pkg, использовал эту технику). Возможно, затем я смогу использовать процедуру utAssert.eqoutput.
Рассмотрев этот вариант, я решил, что хотя теоретически возможно, что эта программа утверждения будет работать, на самом деле, не стоит создавать тестовый набор, построенный вокруг DBMS_OUTPUT. Почему нет? Что, если я (или кто-нибудь еще, кто использует и улучшает Codecheck) решит изменить формат результата? Придется менять код в пакете тестирования модуля. Таким образом, тестирование корректности Codecheck через DBMS_OUTPUT размоет границу между данными (результатом анализа) и презентацией (способом, которым я вывожу результаты на экран). Поскольку это действительно плохая идея, я поищу другие возможности.
Конечно, было бы проще определить корректность работы Codecheck, если бы результаты были более структурированы, чем строка текста, показанная на экране. Давайте вновь посмотрим на результаты allargs_test.noparms2:
allargs_test.noparms2;
allargs_test.noparms2 ('abc');
allargs_test.noparms2 ('abc', 'def');
Что они действительно показывают, так это то, что ни в одном из вышеприведенных вызовов, PL/SQL не сможет сказать, какую из двух процедур noparms2 я хочу запустить. Другой способ представления этой информации приведен
в таблице 3.
|
ТАБЛИЦА 3: Альтернативное представление неверных перегрузок
|
|
|
Перегрузка 1 |
Перегрузка 2 |
|
Программа |
Первый параметр |
Последний параметр |
Первый параметр
|
Последний параметр |
|
noparms2 |
0 |
0 |
0 |
0 |
|
noparms2 |
1 |
1 |
1 |
1 |
|
noparms2 |
1 |
2 |
1 |
2 |
Я отслеживаю, какую пару перегрузок я сравниваю, и затем, внутри каждой перегрузки, какая последовательность аргументов является неоднозначной. Можно сохранить эту информацию (как контрольные данные, которые я ожидаю, так и тестовые данные, являющиеся результатом работы Codecheck) в таблицу базы данных или набор. Затем, можно будет использовать, скажем, utAssert.eqTable, чтобы проверить результаты работы Codecheck по контрольной таблице, которую я подготовлю и заполню ожидаемыми результатами. Я считаю, что гораздо проще заполнять и работать с реляционными таблицами, чем с коллекциями, поэтому давайте продолжим наше путешествие, после того как я подытожу свои размышления:
Я не хочу использовать DBMS_OUTPUT для проверки корректности, но я хочу отображать результаты на экране, чтобы пользователь мог сразу видеть их. Как-никак, это прямая обязанность утилиты.
Я хочу, чтобы Codecheck заполнял таблицу базы данных этими результатами, чтобы можно было полно и аккуратно тестировать утилиту.
Теперь настало время для проектирования таблицы базы данных (или двух, или трех), принимая во внимание требования (и список пожеланий) к моей постоянной структуре данных:
Хранение контрольных и тестовых данных. Их не следует держать в одной таблице, и, действительно, я собираюсь разделить их. Так будет проще управлять данными и выполнять тесты.
Отслеживание всей информации, в которой нуждается utPLSQL, чтобы запустить свои тесты и сообщить о результатах.
Минимизация количества кода, который мне придется написать в тестовом пакете для интеграции с utPLSQL.
Я собираюсь начать с последнего пункта. Как я уже упоминал ранее, объем тестового кода довольно часто превышает объем самого приложения. Это отлично, но я уверен, что не смогу добиться полного тестирования, без создания сгенерированных или спроектированных тысяч строк кода. Возможно ли это?
На самом деле, я бы хотел, чтобы мой тестовый пакет для Codecheck выглядел примерно так (на псведо-коде):
BEGIN
FOR каждого тестового случая
LOOP
Запуск для сценария тестового случая
Сравнение результатов тестирования с контрольной таблицей
END LOOP;
END;
Иначе говоря, я бы хотел избежать жесткого кодирования логики всех тестовых случаев напрямую в тестовом пакете (ut_betwnstr является одним из примеров такого подхода) и, воспользоваться, вместо этого, подходом мягкого кодирования, в котором для управления тестированием будет использоваться информация, хранящаяся в таблицах базы данных.
Перейдём от псевдокода к реальности PL/SQL. Вот мое представление о том, на что должна быть похожа процедура тестирования модуля для Codecheck:
PROCEDURE ut_overloadings
IS
BEGIN
FOR testcase IN (
SELECT * FROM testcases)
LOOP
Codecheck.overloadings (
testcase.program_name);
utassert.eqTable (
testcase.program_name,
results_table,
control_table);
END LOOP;
END;
В этом коде для каждого описанного случая тестирования, вызывается перегрузка Codecheck для программы, связанной с текущим случаем тестирования. Затем, после выполнения Codecheck, вызывается программа утверждения (assertion program) для сравнения результатов с контрольными данными (которые я ожидаю).
И это должен быть весь мой тестовый код! Такой подход позволяет перенести сложность тестирования с тестового пакета в таблицы базы данных. Все что необходимо сделать - это создать структуры данных и заполнить их правильной информацией, и затем – уф – я имею полное модульное и регрессивное тестирование для Codecheck. Если появятся другие случаи тестирования или другие разновидности параметров, которые нужно будет проверить, я просто добавлю строки в таблицу и вновь запущу тест. Никакого кодирования не требуется.
О, вам это нравится! Но разве это возможно и выполнимо? Абсолютно.
Сначала я должен создать таблицу заголовков случаев тестирования:
CREATE TABLE cc_testcases (
id INTEGER,
owner VARCHAR2 (100),
package_name VARCHAR2 (100),
object_name VARCHAR2 (100),
description VARCHAR2 (2000)
);
Столбец id – это первичный ключ, генерируемый из последовательности. Владелец, имя пакета и имя объекта (owner, package_name и object_name) соответствуют элементам имени программы, предоставленным удобной процедурой DBMS_UTILITY.NAME_RESOLVE, которая разбирает и проверяет имена программ. Данные столбца description используются в качестве названия случая тестирования для utPLSQL. Эта таблица позволяет мне получить все необходимое для цикла в процедуре тестирования модуля (представленной выше).
Однако, мне нужно больше информации, чтобы определить ожидаемые данные и отследить результаты деятельности Codecheck. Вспомните
таблицу 3, мне необходимо сохранять информацию о двух различных перегрузках, и их начальных и конечных позициях аргументов. Вот дочерняя таблица ожидаемых результатов (контрольная таблица):
CREATE TABLE cc_ovld_outcomes (
id INTEGER,
testcase_id INTEGER,
overload1 VARCHAR2 (100),
startarg1 INTEGER,
endarg1 INTEGER,
overload2 VARCHAR2 (100),
startarg2 INTEGER,
endarg2 INTEGER);
Столбец id,как и в случае с cc_testcases, является первичным ключом, значение которого генерируется из последовательности. Столбец testcase_id является ссылкой на таблицу заголовков. Остальные столбцы описывают результат. Пожалуйста, обратите внимание, что столбцы overloadN описаны как VARCHAR2, для согласования с представлением ALL_ARGUMENTS, в котором, по каким-то причинам, используется VARCHAR2для столбца OVERLOAD.
Эти две таблицы позволяют мне записать, до запуска каких-либо тестов, различные случаи, которые необходимо тестировать и ожидаемые результаты, причём результаты указывают на неверную перегрузку, которую Codecheck должен обнаружить.
Необходимо также записывать результаты конкретного теста или выполнения для Codecheck. Эти данные похожи на содержимое cc_ovld_outcomes, но Codecheck ничего не знает об ID, сгенерированных из последовательности. Вместо этого, он работает с именем анализируемого объекта.
Таким образом, структура таблицы результатов тестирования (которую Codecheck заполняет сам) имеет следующий вид:
CREATE TABLE cc_ambig_ovld_results (
object_name VARCHAR2 (100),
package_name VARCHAR2 (100),
owner VARCHAR2 (100),
overload1 VARCHAR2 (100),
startarg1 INTEGER,
endarg1 INTEGER,
overload2 VARCHAR2 (100),
startarg2 INTEGER,
endarg2 INTEGER);
Чтобы облегчить запуск utAssert – процедуры сравнения содержимого контрольной и тестовой таблиц, я также создам представление, которое спрячет мастер-детальную структуру двух контрольных таблиц. Это представление по структуре будет соответствовать таблице результатов тестирования:
CREATE OR REPLACE VIEW cc_ovld_control
AS
SELECT tc.object_name, tc.package_name, tc.owner,
oo.overload1, oo.startarg1, oo.endarg1,
oo.overload2, oo.startarg2, oo.endarg2
FROM cc_testcases tc, cc_ovld_outcomes oo
WHERE tc.ID = oo.testcase_id;
Теперь легко можно сравнить содержимое cc_ambig_ovld_results и cc_ovld_control. Позже в этой серии статей о создании Codecheck, после того как я пройду стадию реализации, я вернусь к пакету тестирования ut_codecheck и представлю финальную версию процедуры тестирования модуля, основанную на cc_ambig_ovld_results и cc_ovld_control. Все эти объекты базы данных определены в скрипте cc_install_data_objects.sql.
Облегчение программирования
Теперь, когда у меня есть таблицы и представления, я могу заполнить управляющие таблицы ожидаемыми результатами. Я могу написать операторы INSERT, но это чрезвычайно непривлекательный способ. К счастью, я еще помню, как использовать Oracle Forms (отчасти). Я потратил час или около того, смастерив кое-как очень простую мастер-детальную форму
(см. Рисунок. 2), которая позволяет легко настраивать контрольные таблицы и управлять их содержимым. Действительно, с помощью этой формы можно эффективно преобразовывать содержимое обычных таблиц
(см. Таблицу 2 [в предыдущей части - ред OM/RE]) в реляционные таблицы.
 |
|
Рисунок 2: Простая форма, построенная, чтобы облегчить программирование случаев тестирования и результатов Codecheck. |
Что следует далее
Имея все эти фрагменты, я чувствую, что можно уверенно двигаться дальше в реализации Codecheck, и что её легко можно будет протестировать. Теперь, продумав работающую версию этой утилиты, я займусь оставшимися задачами модуля тестирования, завершу создание пакета тестирования, загружу информацию о случаях тестирования через мою форму, и начну запускать тесты.
Стивен Фернстайн ( steven@stevenfeuerstein.com) является одним из ведущих мировых экспертов в языке Oracle PL/SQL. Он является автором или соавтором девяти книг на PL/SQL, включая “Oracle PL/SQL Programming” третье издание и “Oracle PL/SQL Best Practices” (O'Reilly; http://oracle.oreilly.com/). Стивен является главным техническим консультантом для Quest Software, разрабатывает программное обеспечение с 1980, и работал в Oracle с 1987 по 1992. Он является также бывшим президентом совета директоров Crossroads фонда, который выдаёт гранты общественным группам района Чикаго, борющимся за социальную, расовую и экономическую справедливость
(www.CrossroadsFund.org).