Тестирование программ PL/SQL: технологии, рекомендации и автоматизация

Стас Белков

Стас Белков

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

Тестирование программ PL/SQLВсем нам нравится создавать что-то новое — собственно, это одна из причин, по которой мы занимаемся программированием. Мы берем интересную задачу и придумываем способ ее реализации на языке PL/SQL.

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


Оглавление статьи[Показать]


  •  Стремление к успеху. Мы настолько убеждены в том, что наш код будет работать правильно, что предпочитаем держаться подальше от плохих новостей — и даже от самой возможности их появления. Мы проводим поверхностное тестирование, убеждаемся, что в первом приближении все работает, и ждем, пока другие найдут ошибки, если они есть (а они есть, можете не сомневаться!).
  •  Сроки. Сейчас время Интернета, время выхода на рынок определяет успех. Все должно быть готово немедленно — и мы выпускаем предварительную бета-версию как готовый продукт, а пользователи мучаются с тестированием наших разработок.
  •  Некомпетентность руководства. Как правило, руководители информационных отделов ничего не смыслят в разработке ПО. И если у вас нет времени на создание полноценного проекта (включая проектирование, написание кода, тестирование, документирование и т. д.), то в результате получится убогая поделка с множеством ошибок.
  •  Затраты на организацию тестирования. Создание тестовых программ обычно считается пустой тратой времени, ведь всегда найдется более важная работа. Одним из следствий такого подхода является то, что значительная часть обязанностей по тестированию перекладывается на отдел контроля качества (если он есть). Конечно, участие профессионалов в области контроля качества может оказать огромное влияние на результат, однако и разработчики не должны снимать с себя ответственность за модульное тестирование своего кода.

Таким образом, программы почти всегда нуждаются в дополнительном тестировании. Как повысить его эффективность в мире PL/SQL?

Для начала мы рассмотрим типичный пример неудачного процесса тестирования. Затем будут представлены выводы относительно ключевых проблем ручного тестирования, и мы познакомимся со средствами автоматизированного тестирования кода PL/SQL.

 

Типичные неэффективные технологии тестирования

В ходе тестирования программы необходимо определить, какие изменения вносятся в ходе ее работы: возвращаемая функцией строка, обновленная процедурой таблица и т. д. Затем вы заранее формируете прогноз правильного поведения программы для заданного набора входных данных и конфигурации (тестового сценария). После выполнения программы фактические результаты (внесенные программой изменения) сравниваются с прогнозируемыми значениями. Если они совпадают — значит, программа работает. Если что-то отличается — значит, где-то произошел сбой.

Это очень хорошее общее описание процесса тестирования; остается выяснить, как определить все необходимые тестовые сценарии и реализовать тесты. Начнем с весьма типичного (и к сожалению, неэффективного) подхода к тестированию.

Допустим, мы пишем большое приложение, обрабатывающее большое количество строк. В PL/SQL имеется функция SUBSTR, которая возвращает заданную часть строки. Однако ее использование связано с некоторыми проблемами. Дело в том, что эту функцию удобно применять, когда вы знаете начальную позицию и длину строки. Однако очень часто известно лишь местоположение начального (start) и конечного (end) символов, а длину строки приходится вычислять. Но по какой формуле? Чтобы не мучиться с вычислениями (кстати говоря, правильный ответ end — start + 1), мы напишем функцию betwnstr, которая произведет все вычисления за нас:


FUNCTION betwnstr (string_in IN VARCHAR2 
                  ,start_in IN INTEGER 
                  ,end_in IN INTEGER
)
   RETURN VARCHAR2 
IS
BEGIN
   RETURN (SUBSTR ( string_in, start_in, end_in - start_in + 1));
END betwnstr;

К сожалению, объем работы весьма велик, а это всего лишь одна из множества написанных программ, которую необходимо протестировать. Мы пишем примитивный «тестовый сценарий» с использованием DBMS_OUTPUT.PUT_LINE и запускаем его:

BEGIN
   DBMS_OUTPUT.put_line (NVL (betwnstr ('abcdefg', 3, 5)
                           , '**Really NULL**'));
END;

cde

Работает... надо же! Но одного теста недостаточно. Давайте зададим конечное значение за пределами строки — например, 500. Функция должна вернуть остаток строки, как бы это сделала функция SUBSTR:

BEGIN
   DBMS_OUTPUT.put_line (NVL (betwnstr ('abcdefg', 3, 500)
                           , '**Really NULL**'));
END;

cdefg

Снова работает! Теперь нужно убедиться в том, что функция правильно работает со значениями NULL:

BEGIN
   DBMS_OUTPUT.put_line (NVL (betwnstr ('abcdefg', NULL, 5)
                           , '**Really NULL**'));
END;

**Really NULL**) );

Три из трех! Функция работает правильно, скажете вы? Нет — скорее всего, вы покачаете головой и скажете себе: «Такое тестирование даже в первом приближении не проверяет все возможные сценарии. Оно даже не изменяет значение первого аргумента! К тому же при каждом изменении входных значений предыдущий тест терялся».

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

 

разные значения аргументов

 

На основании этой таблицы строится простой сценарий следующего вида:


BEGIN
   DBMS_OUTPUT.put_line ('Test 1: ' || betwnstr (NULL, 3, 5));
   DBMS_OUTPUT.put_line ('Test 2: ' || betwnstr ('abcdefgh', 0, 5));
   DBMS_OUTPUT.put_line ('Test 3: ' || betwnstr ('abcdefgh', 3, 5));
   DBMS_OUTPUT.put_line ('Test 4: ' || betwnstr ('abcdefgh', -3, -5));
   DBMS_OUTPUT.put_line ('Test 5: ' || betwnstr ('abcdefgh', NULL, 5));
   DBMS_OUTPUT.put_line ('Test 6: ' || betwnstr ('abcdefgh', 3, NULL));
   DBMS_OUTPUT.put_line ('Test 7: ' || betwnstr ('abcdefgh', 3, 100));
END;

Каждый раз, когда нам потребуется протестировать betwnstr, мы просто выполним этот сценарий и проверим результаты. Для исходной реализации они будут выглядеть так:

SQL> @betwnstr.tst
Test 1:
Test 2: abcdef 
Test 3: cde 
Test 4:
Test 5:
Test 6:
Test 7: cdefgh

«Проверим результаты»... Легко сказать, но как это сделать? Как узнать, прошли ли тесты? Нам придется просматривать результаты строку за строкой и проверять их по таблице. К тому же при действительно тщательном тестировании у нас будет более 30 тестовых сценариев (не забыли об отрицательных значениях начальной и конечной позиции?). На просмотр результатов уйдет несколько минут, и это для совершенно тривиального кода. Сама мысль о распространении этой методики на «реальный» код выглядит устрашающе. А теперь представьте, что программа изменяет две таблицы и возвращает два аргумента OUT. Счет тестовых сценариев пойдет на сотни, да еще добавьте к этому проблемы настройки конфигурации и проверку правильности содержимого таблиц.

И все же многие разработчики выбирают этот путь при «тестировании» своего кода. Почти все проводимое тестирование обладает рядом ключевых недостатков:

  •  Тестовый код пишется вручную, что существенно ограничивает объем тестирования. У кого найдется время на написание всего необходимого кода?
  •  Неполное тестирование. Будем откровенны: при тестировании мы обычно ограничиваемся несколькими очевидными случаями, чтобы убедиться в том, что программа не имеет очевидных изъянов. Вряд ли это можно назвать полноценным тестированием.
  •  Одноразовые тесты. Было бы неплохо подумать о будущем и понять, что нам (или кому-то другому) еще неоднократно придется проводить те же тесты.
  •  Ручная проверка. Если мы будем полагаться только на собственную наблюдательность при проверке результатов, это займет слишком много времени и, скорее всего, приведет к ошибкам.
  •  Тестирование после разработки. Многие программисты говорят себе: «Вот допишу программу и займусь тестированием». Вроде бы вполне разумный подход, и все же он в корне неверен. Во-первых, наши программы никогда не «дописываются» — исправления вносятся до последней минуты, поэтому времени на тестирование вечно не хватает. Во-вторых, если вы начинаете думать о тестировании только после завершения работы над реализацией, вы подсознательно выбираете тесты, которые имеют наибольшую вероятность успеха, и избегаете тех, которые могут столкнуться с проблемами. Так уж устроено наше сознание.

Если вы хотите, чтобы тестирование было действительно эффективным и тщательным, необходимо действовать иначе. Тесты должны определяться так, чтобы они могли легко сопровождаться с течением времени. Мы должны иметь возможность легко проводить тестирование и, что еще важнее, — без продолжительного анализа определить результат: успех или неудача. При этом выполнение тестов не должно требовать написания больших объемов тестового кода.

Далее я сначала дам несколько советов относительно того, как организовать тестирование вашего кода. Затем будут рассмотрены средства автоматизации тестирования для разработчиков PL/SQL; особое внимание будет уделено utPLSQL и Quest Code Tester for Oracle.

 

Общие рекомендации по тестированию кода PL/SQL

Выбор инструмента тестирования зависит только от вас, но для повышения качества тестирования следует принять во внимание следующие факторы:

  •  Осознанная необходимость тестирования. Самые важные изменения должны произойти у вас в голове. От позиции «Надеюсь, эта программа сработает» необходимо перейти к позиции «Я должен быть способен доказать, что моя программа работает». Осознав необходимость тестирования, вы начнете писать более модульный код, который проще тестируется. Также вам понадобятся инструменты, позволяющие более эффективно проводить процесс тестирования.
  •  Продумайте тестовые сценарии до того, как возьметесь за написание программы, — сформулируйте их на листке бумаги или в программе, управляющей процессом тестирования. Очень важно, чтобы ваши представления о том, что же необходимо протестировать, нашли внешнее воплощение; в противном случае вы с большой вероятностью о чем-нибудь забудете. Приступая к работе над программой в понедельник, я легко могу представить 25 разных сценариев (требований), которые необходимо реализовать. Три дня спустя отведенное время заканчивается, я перехожу к тестированию — и как ни удивительно, могу вспомнить всего пять тестовых сценариев (да и то самых очевидных). Если вы составите список известных тестовых сценариев в начале работы, вам будет намного проще запомнить и проверить их. 
  •  Не беспокойтесь о стопроцентном тестовом покрытии. Вряд ли существует хоть одна нетривиальная программа, которая была полностью протестирована. Не ставьте себе цель обеспечить стопроцентное покрытие всех возможных тестовых сценариев. Скорее всего, это нереально, а результат только приведет вас в уныние. Самое важное в тестировании — взяться за него. И что с того, что в фазе 1 было реализовано всего 10% тестовых сценариев? Это на 10% больше, чем было прежде. А когда ваши тестовые сценарии (и связанный с ними код) окажутся на месте, дополнить их новыми возможностями станет проще.
  •  Интеграция тестирования в разработку. Нельзя откладывать тестирование до того момента, когда вы «закончите» работу над программой. Чем раньше вы начнете думать над ним, тем лучше. Составьте перечень тестовых сценариев, спланируйте тестовый код и запускайте тесты в процессе реализации, отладки и совершенствования программы. При любой возможности снова выполняйте тесты и убеждайтесь в том, что программа действительно двигается вперед. Если вам нужно красивое название (методология), которое убедит вас в ценности такого подхода, изучите широко распространенную (в объектно-ориентированных кругах) методологию разработки через тестирование (TDD, Test-Driven Development).
  •  Подготовьте регрессионные тесты. Все перечисленные предложения в сочетании с инструментами, о которых рассказано в следующем разделе, помогут вам организовать регрессионное тестирование. Такие тесты должны предотвратить регрессию, то есть нарушение работоспособности ранее работавшего кода при внесении изменений. Ужасно неприятно, когда при выпуске версии 2 продукта половина функций версии 1 вдруг перестает работать. «Как это могло произойти?» — вопрошают пользователи. И что им на это ответить? Честный ответ должен звучать так: «Извините, но у нас не было времени на регрессионные тесты. Когда мы вносим изменения в своем запутанном коде, на самом деле мы понятия не имеем, что при этом могло сломаться». Но после создания нормальных регрессионных тестов вы можете уверенно вносить изменения и выдавать новые версии.

 

Средства автоматизации тестирования программ PL/SQL

В наши дни разработчики PL/SQL могут использовать целый ряд автоматизированных инфраструктур и средств тестирования своего кода:

  •  utPLSQL — первая тестовая инфраструктура для PL/SQL, фактически «JUnit для PL/SQL». utPLSQL реализует принципы тестирования, принятые в методологии экстремального программирования, и автоматически выполняет написанный вручную тестовый код с проверкой результатов. В следующем разделе описан пример сеанса тестирования с использованием utPLSQL. За полной информацией обращайтесь по адресу http://utplsql.sourceforge.net.
  •  Code Tester for Oracle — коммерческая программа тестирования с самым высоким уровнем автоматизации тестов. Тестовый код генерируется на основании поведения, определяемого в пользовательском интерфейсе, после чего программа выполняет тесты и выводит результаты. Предоставляется Dell в составе пакета Toad Development Suite.
  •  Oracle SQL Developer — бесплатная среда разработки от Oracle со средствами интеграции модульного тестирования, близкий аналог Code Tester for Oracle.
  •  PLUTO — аналог utPLSQL, реализованный с использованием объектных типов Oracle.
  •  dbFit — эта инфраструктура использует совершенно иной подход к определению тестов: она строится на базе табличных сценариев и позволяет применять тесты FIT/FitNesse непосредственно к базе данных.

Вас заинтересует / Intresting for you:

Управление приложениями PL/SQL...
Управление приложениями PL/SQL... 4634 просмотров Stas Belkov Thu, 16 Jul 2020, 06:20:48
Встроенные методы коллекций PL...
Встроенные методы коллекций PL... 14740 просмотров sepia Tue, 29 Oct 2019, 09:54:01
Программирование динамического...
Программирование динамического... 4877 просмотров Максим Николенко Sun, 09 Sep 2018, 06:56:23
Работа с числами в PL/SQL на п...
Работа с числами в PL/SQL на п... 44848 просмотров Antoniy Mon, 28 May 2018, 16:45:11
Войдите чтобы комментировать

apv аватар
apv ответил в теме #9676 3 года 8 мес. назад
Превосходный обзор методик тестирования кода PL/SQL в ручном и автоматическом режиме! Автору огромная благодарность!