> Oracle dev.
Инфраструктура автоматического тестирования 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.
Тест выполнился успешно

-------------------------------------------------------------------
Дистрибутив инфраструктуры
     Развертывание инфраструктуры автотестирования заключается в заливке в базу данных сценария, генерирующего все необходимые объекты: таблицы для трассировки вызовов, таблицы протоколирования, права и пакеты, реализующие инфраструктуру автотестирования.
 
Скачать: plsqlUnit.zip
Дополнительные возможности
     Запуск автотестов при помощи SQL-сценария не позволяет удобным образом организовать автоматизированный механизм запуска тестов каждую ночь и доставки почтового уведомления на соответствующий список рассылки. Конечно, можно использовать инструментарий Oracle: Jobs (задания), собственные пакеты по рассылке почтовых уведомлений. Однако, для быстрого старта предлагаю использовать возможности скриптовых компонентов и, например, страниц ASP.
     Для организации доступа к механизму автотестирования нам потребуется скриптовый компонент, который позволит: получить список тестов в XML-формате, запустить тест и получить протокол его выполнения.
Скриптовый компонент для запуска автотеста
     Ниже приведен программный интерфейс скриптового компонента, позволяющего запускать автотесты, например с ASP-страницы.
 
ProgId: Autotest.PLSQLUnit
setDb - задание имени базы данных с автотестами
getTests - возвращает список доступных автотестов в XML-виде
executeTest - запускает тест на выполнение и возвращает объект автотеста. У объекта автотеста используется метод GetLog() для получения протокола о ходе выполнения теста
 
Скачать: plsqlUnitScript.zip, перед использованием необходимо зарегистрировать компонент
Запуск автотеста с внутреннего 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 с.

 Evgeny Savitsky © 2002-2005