|
Инфраструктура автоматического тестирования PL/SQL-кода (PLSQLUnitLite)
Введение
|
    Многие разработчики часто не задаются вопросом о том, как тестировать создаваемое программное обеспечение.
Однако, в реальных условиях за ограниченное время выполнения проекта, качественный продукт создать практически
невозможно. Особенно дорого обходится сопровождение плохо документированного и сложного в использовании
программного обеспечения. Зачастую воспроизвести падение приложения не удается или это требует
больших усилий по прохождению всего бизнес-процесса (а зачастую и несколько раз). Таким образом, разработчики
боятся исправлять существующий код, действуя по принципу "не трогай, пока работает", а если и исправляют, то
делают лишь заплатки, которые исправлять еще страшнее... Разорвать этот порочный круг можно
лишь переписав систему заново. Однако я предлагаю способ лучше, точнее пытаюсь открыть глаза на
преимущества существующих подходов, например, разработки через тестирование (Test-Driven Development),
позволяющей в частности автоматизировать процесс тестирования бизнес-логики приложений, по аналогии
с инфраструктурами тестирования JUnit (для Java) или NUnit (для C#).
|
Цели и задачи автотестирования
|
1. |
Обеспечить автоматизацию тестирования, которое в силу своей сложности зачастую вовсе не проводится. Необходимо упростить задачу тестирования функциональности, устранить нудную деятельность по осуществлению одного и того же сценария посредством пользовательского интерфейса, как следствие, снизить влияние человеческого фактора в тестировании. |
2. |
Обеспечить постоянный регрессионный контроль над функциональностью, повысить качество создаваемых систем и вносимых в систему изменений. Запускать проверку автотестов каждый вечер, по окончании рабочего дня. |
3. |
Обособить и перенести на сервер реализацию бизнес-процессов, что позволит отделить пользовательский интерфейс от реализации, упростит внесение изменений в код и интеграцию изменений в "боевые" релизы. |
4. |
Упростить понимание в реализации требуемой бизнес-логики посредством написания алгоритмов и интерфейсов через автотест, и как следствие, повысить качество создаваемых алгоритмов и интерфейсов. Обеспечить безопасный рефакторинг существующего кода. |
5. |
Закрепить сведения о корректной функциональности, то есть варианты использования, в коде автотеста. |
|
О пользе применения автотестов
|
1. |
При помощи автотестирования ликвидируются случаи регрессии функциональности. |
2. |
Автотест принуждает перенести реализацию бизнес-логики на сервер, что само по себе чрезвычайно полезно и удобно как с точки зрения внедрения и сопровождения, так и с точки зрения оптимизации. |
3. |
Автотест ликвидирует страх, связанный с внесением в работающую систему изменений функциональности - у нас есть тест, он скажет где, что и когда "поехало". Не нужно проводить длительные исследования на предмет того, какой участок кода от чего зависит. Не нужно требовать от сотрудников сверхаккуратности. |
4. |
Автотест - это ответ на вопрос "как должен работать этот алгоритм?", который задается любым разработчиком при реализации этого алгоритма, то есть автотест позволяет разработчику лучше разобраться в том, что от него требуется. Это итерационный процесс: есть входные данные, есть выходные, используя заглушки, постепенно вырисовывается интерфейс к алгоритму, которые постепенно реализуется, а тест всегда дает ответ на вопрос "правильно ли реализован алгоритм". |
5. |
Автотест представляет собой отличную отладочную среду - вносим в бизнес-логику точки наблюдения за переменными и запускаем тест, ответ отыскивается моментально, не нужно посредством пользовательского интерфейса нудно проводить одну и ту же операцию по формированию входных параметров, не нужно подбирать отдельные идентификаторы для проверки процедур. |
|
Термины
|
1. |
Автотест - обособленный модуль, например, реализованный на PL/SQL, удовлетворяющий определенной сигнатуре, позволяющей запускать его на выполнение в автоматическом режиме, содержащий в себе вызовы методов бизнес-логики и логику проверки предусловий и постусловий этих методов. |
2. |
Предусловия метода - предикаты, которые должны принимать истинные значения перед выполнением кода метода (проверка на верные входные параметры). Проверка предусловий осуществляется внутри метода. Проверка предусловий метода в автотесте осуществляется путем передачи параметров, неверных с точки зрения алгоритма метода. |
3. |
Постусловия метода - предикаты, которые должны принимать истинные значения после выполнения метода (проверка на то, что метод отработал правильно). Проверка постусловия в автотесте осуществляется после вызова метода путем вычисления соответствующего предиката. |
|
Использование механизма автотестирования
|
После изменения алгоритма некоторой функциональности запускается автотест, по результату выполнения которого разработчик делает вывод - повредили ли функциональности внесенные изменения или нет, если да, то алгоритм исправляется до тех пор, пока не заработают все тесты, порывающие изменяемую функциональность. |
При разработке новой функциональности или кардинальной переработке существующей автотест пишется первым. По ходу разработки автотеста формируются требования к программному интерфейсу функциональности. Программный интерфейс реализуется в виде заглушек, а затем постепенно приобретает вид законченной функциональности. |
Каждую ночь выполняется весь доступный пакет автотестов, а результаты их выполнения отсылаются почтовым уведомлением на список рассылки разработки. Релиз программной системы не может считаться состоявшимся, до тех пор, пока не заработают все автотесты. |
Модуль автотеста реализуется в виде PL/SQL-пакета со строго определенной сигнатурой:
1. |
Пакет располагается в схеме AUTOTEST. |
2. |
В начале тела пакета расположен комментарий, описывающий назначение теста и содержащий информацию о разработчике и дате разработке автотеста (см. Приложение). Необходимо максимально полно (детальон) описывать назначение теста, так как по комментарию определяется покрываемая тестом функциональность. |
3. |
В пакете определены три процедуры, таким образом, чтобы их можно было вызывать извне.
a. |
Initialize - подготовка к выполнению теста, например, вход в систему под нужным пользователем. |
b. |
RunTest - логика автотеста. |
c. |
Finalize - реализуется при необходимости, предназначен для удаления каких-либо данных после выполнения теста. |
|
4. |
Логика автотеста должна содержать следующие проверки:
1. |
Проверка на генерацию сообщений о недопустимых параметрах (проверка предусловий) - перехват исключений Oracle, генерируемых
бизнес-логикой при передачи некорректных параметров или отклонении от задуманного хода выполнения алгоритма. |
2. |
Проверка истинности утверждения о результате работы функции (проверка постусловий) - проверка, что состояние системы переведено
в требуемое (ожидаемое с точки зрения бизнес-процесса) состояние, например, экземпляр объекта создан, то есть идентификатор записи > 0. |
|
|
|
Архитектура инфраструктуры автотестирования
|
1. |
AUTOTEST.LOG - пакет, реализующий поддержку протоколирования сообщений по ходу выполнения автотеста.
- |
Содержит низкоуровневые процедуры записи и чтения протокола, формируемого по ходу выполнения теста. Используется пакетами AUTOTEST.PLSQLTEST и AUTOTEST.FACTORY |
|
2. |
AUTOTEST.FACTORY - пакет, реализующий фабрику экземпляров автотестов и вспомогательные процедуры для получения метаданных пакетов автотестов.
- |
GetAutoTestPackages - возвращает список доступных к исполнению автотестов, список оформлен в виде XML. |
- |
RunTest - запуск автотеста по полному имени пакета (включая схему). |
|
3. |
AUTOTEST.PLSQLTEST - пакет, реализующий базовый класс всех автотестов и процедуры проверки предусловий и постусловий.
- |
AssertTrue - проверка постусловия на истинность. |
- |
AssertFalse - проверка постусловия на ложность. |
- |
AssertFailure - указание, что тест не сработал. |
- |
AssertException - вывод в протокол стек вызовов, приведших к генерации исключения. |
- |
AssertExcpectedException - проверка предусловий метода, перехват ожидаемого исключения. |
- |
AssertComment - вывод в протокол комментария о ходе выполнения теста. |
|
|
Пример запуска и результата выполнения автотеста
|
    
Ниже приведен пример автотеста, который устанавливается вместе с остальными пакетами инфраструктуры.
Тест запускается, после чего приводится результат выполнения автотеста, расположенный в буфере DBMS Output.
---------------------- исходный автотест --------------------------
...
function Multiply( a_num number, a_mult number ) return number
is
begin
return a_num * a_mult;
end;
function LnFunction( a_num number ) return number
is
begin
if a_num = 0 then raise NO_DATA_FOUND; end if;
return ln( a_num );
end;
procedure RunTest
is
l_temp number;
begin
autotest.plsqltest.AssertTrue( 5 * 3 = multiply( 5, 3), 'Проверка умножения');
begin
autotest.plsqltest.AssertComment( 'Проверяем реакцию функции Ln на недопустимый параметр' );
l_temp := LnFunction( 0 );
autotest.plsqltest.AssertTrue( false, 'Функция Ln проглотила недопустимый параметр' );
exception when NO_DATA_FOUND then
autotest.plsqltest.AssertExpectedException( sqlcode );
end;
end;
...
-------------------------------------------------------------------
---------------------- запуск автотеста ---------------------------
declare
begin
autotest.factory.runtestdbmsoutput('autotest.example');
rollback;
end;
-------------------------------------------------------------------
------------------ результат в dbms output ------------------------
Ok: Проверка умножения
Проверяем реакцию функции Ln на недопустимый параметр:
Ok: Сгенерировано ожидаемое исключение: ORA-01403: no data found
Время выполнения: .01 c.
Тест выполнился успешно
-------------------------------------------------------------------
|
Дистрибутив инфраструктуры
|
    
Развертывание инфраструктуры автотестирования заключается в заливке в базу данных сценария, генерирующего
все необходимые объекты: таблицы для трассировки вызовов, таблицы протоколирования, права и пакеты,
реализующие инфраструктуру автотестирования.
 
|
Дополнительные возможности
|
    
Запуск автотестов при помощи SQL-сценария не позволяет удобным образом организовать автоматизированный
механизм запуска тестов каждую ночь и доставки почтового уведомления на соответствующий список рассылки.
Конечно, можно использовать инструментарий Oracle: Jobs (задания), собственные пакеты по рассылке почтовых уведомлений.
Однако, для быстрого старта предлагаю использовать возможности скриптовых компонентов и, например, страниц ASP.
|
    
Для организации доступа к механизму автотестирования нам потребуется скриптовый компонент, который позволит:
получить список тестов в XML-формате, запустить тест и получить протокол его выполнения.
|
Скриптовый компонент для запуска автотеста
|
    
Ниже приведен программный интерфейс скриптового компонента, позволяющего запускать автотесты, например с ASP-страницы.
 
ProgId: Autotest.PLSQLUnit
|
setDb - задание имени базы данных с автотестами
|
getTests - возвращает список доступных автотестов в XML-виде
|
executeTest - запускает тест на выполнение и возвращает объект автотеста. У объекта автотеста используется метод GetLog() для получения протокола о ходе выполнения теста
|
 
|
Запуск автотеста с внутреннего Web-сайта
|
    
Очень удобно было бы организовать запуск автотестов с внутреннего Web-сайта, таким образом, любой
разработчик сможет запустить тест, не вникая в детали работы инфраструктуры. Ниже приводится пример ASP-страницы
plsqlunit.asp, на которой реализован запуск автотеста посредством скриптового компонента Autotest.PLSQLUnit.
<% Language=VBScript %>
<%
Response.AddHeader "Content-Type", "text/html; charset=windows-1251"
' создаем объект автотестирования
Set t = CreateObject("Autotest.PLSQLUnit")
' укажем базу данных, на которой выполняются автотесты
t.setDB "CORP9"
' отображаем список тестов на странице
Set xml = CreateObject("Msxml2.DOMDocument")
xml.loadXML( t.getTests("AUTOTEST") )
Set tests = xml.selectNodes("tests/test")
for i = 0 to tests.length - 1
Set package = tests.item(i).selectSingleNode("header/class")
response.write "<a href='plsqlunit.asp?test="& package.text &"'>"& package.text &"</a>"
next
If Request("test") <> "" Then
Response.Write "<div> </div><div>Результат выполнения автотеста "& Request("test") &": </div><hr>"
Set test = t.executeTest( Request("test") )
xml.loadXML( test.GetLog() )
Set lines = xml.selectNodes("log/line")
for i = 0 to lines.length - 1
Response.Write "<div>"& lines.item(i).text & "</div>"
next
' отображаем время выполнения теста
Set executiontime = xml.selectSingleNode("log/executiontime")
If IsObject(executiontime) Then
Response.Write "<div>Время выполнения: "& executiontime.text &" с.</div>"
End If
' отображаем результат
Set result = xml.selectSingleNode("log/result")
If IsObject(result) Then
If result.text = "1" Then
resultText = "тест выполнен успешно"
Else
resultText = "тест не сработал"
End If
Response.Write "<hr><div>Результат: "& resultText &"</div>"
End If
End If
%>
| Результат запуска теста с ASP-страницы
|
AUTOTEST.EXAMPLE   Результат выполнения автотеста AUTOTEST.EXAMPLE:
Ok:Проверка умножения Проверяем реакцию функции Ln на недопустимый параметр:null Ok:Сгенерировано ожидаемое исключение: ORA-01403: no data found Время выполнения: .01 с.
Результат: тест выполнен успешно |
|
Другие варианты инфраструктур автотестирования
|
http://www.ounit.com - полноценная реализация методики разработки через тестирование, однако, достаточно тяжеловесное решение, которое потребуется и будет удобно в использовании далеко не каждому разработчику. |
http://utplsql.sourceforge.net - open source проект, в который вылилась разработка oUnit. |
|
Литература
|
Кент Бек. Экстремальное программирование: разработка через тестирование. Библиотека программиста. - 2003, 224 с. |
|