Несколько лет занимаюсь программированием баз данных и столько же вижу как непросто идет тестирование функционала, реализованного в самой базе. Такая задача требует от тестировщика не только знаний SQL, но, порой, и понимания тонкостей работы тестируемого функционала. А разработчики не всегда стремятся описать свое решение во всех деталях.
Стало интересно — как сейчас тестируется логика базы данных. Думаю, неудивительно, что на тот момент поиски в интернете успехом не увенчались. То ли из-за недостаточной настойчивости, то ли из-за желания сделать «такое же, но свое, другое». А имеющиеся варианты не устраивали из-за сложности использования. Например, были и clr-сборки с unit-тестами. Про HP QTP узнал уже позже.
И эта деятельность привела к созданию конструктора тестов, воплотившегося в плагине к eclipse для тестирования базы. В статье будут описаны основы работы с конструктором.
Не хочу теорию, хочу сразу результат!
Теория
В основу алгоритма вошли принципы:
- тест состоит из последовательности шагов (простых sql-скриптов)
- шаг — это единичная операция, принимающая одни данные на вход и возвращающая другие
- шаги можно объединять в последовательность для создания более сложного модуля
- меньше кода при создании теста, больше работы мышкой
- тесты будут храниться в базе
И как бы я, будучи тестировщиком, хотел бы создавать тесты:
- Я хочу 1 раз написать простой скрипт, дать ему название, задать параметры, и сохранить для повторного использования. И пусть программа сама находит параметры из текста скрипта. А я уж задам им тип данных и направление — входной это параметр или выходной.
- Хочу объединять несколько скриптов в один компонент. Допустим, нужно положить запись в базу, дождаться её обработки и получить результат этой обработки. Компонент — основная логическая единица для создания теста.
- И пусть компоненты можно будет объединять в сам тест. Тот самый, который и будет проверять ту или иную бизнес-операцию.
Хорошо, с этим понятно. А где же проверка? Ведь результат теста — это сравнение ожидаемого и фактического результатов. Поэтому, и компонент и тест хранят для своих параметров контекст (подробнее и с примером ниже), который и укажет — это ожидаемое или эталонное значение. При этом, параметры должны быть с одинаковым именами. И, когда в работе компонента или теста произойдет изменение значения такого параметра, должна выполниться проверка на равенство или неравенство этих значений. Если проверка не прошла, весь тест завершится неуспешно.
Что из этого получилось
- Установка и настройка
- Создание проекта
- Создание скрипта
- Объединение скриптов в компонент
- Создание теста из компонентов
Установка и настройка
Плагин умеет работать с MS SQL Server.
- ссылка на плагин к eclipse и исходники в конце статьи. Ставим плагин
- создаем пустую базу и выполняем на ней скрипт из репозитория (databasescript.sql)
- Задаем в настройках 2 базы — для хранения тестов и для выполнения тестов (Window Preferences Настройки SQL тестов)
Создание проекта
Выбираем настроенные базы и задаем пароли. По завершении жмем «Проверить введенные параметры». Если проверки пройдут успешно, станет доступной кпопка Finish. Жмем её для завершения создания проекта.
По умолчанию нужная view не открывается, делаем это руками: Window -> Show View -> Other -> SQL Test Project Explorer. Созданный проект предстанет в таком виде:
Создание скрипта
Для порядка, через контекстное меню на разделе «Скрипты» создадим папку «Пример», в которой и будут лежать наши примеры. И создадим через контекстное меню на папке «Пример» скрипт «Получить серверное время», который вернет дату и время сервера. Откроем папку «Пример» и, двойным кликом, откроем скрипт. Впишем этот текст скрипта и нажмем кнопку «Найти параметры».
SELECT GETDATE() AS [:NOW]
Параметры находятся по сигнатуре :param. А, чтобы получить значение из скрипта, следует вернуть DataSet с названием колонки — именем параметра [:param].
Выберем параметру тип DATE и направление «Выходной». Сохраняем скрипт и ждем рядом кнопку «Выполнить». В колонке «Тестовое значение» отобразится результат:
Это простой пример. Теперь усложним задачу. Нам предстоит в тесте подождать 5 секунд, а потом убедиться, что мы выполнялись ровно 5 секунд. Это довольно искусственная задача, но для понимания возможностей самый раз.
Для решения задачи нам предстоит сделать несколько скриптов, чтобы потом объединить в компонент. Вот эти скрипты:
- Задать эталонный интервал в 5 секунд
- Получить серверное время #1
- Дождаться истечения 5 секунд
- Получить серверное время #2
- Сравнить времена и убедиться, что разница составляет 5 секунд
Для (2) и (4) шагов у нас уже есть скрипт. (3) оставим на последок как наиболее сложный. Простой WAITFOR DELAY мы не будем использовать, так как это может быть любая длительная операция, которая может завершиться в любой момент времени, а не по истечении фиксированного интервала.
(1) может выглядеть совсем просто. Единственный параметр выходной, типа INTEGER:
SELECT 5 as [:interval]
И (5) так же не сложно. Первые два параметра будут входными, типа DATE. Третий — выходной, типа INTEGER:
SELECT DATEDIFF(SECOND, :from, :to) as [:interval]
Настал черед (3) скрипта, задача которого отработать быстро и сообщить — выполнилось ли условие и можно идти дальше, или повторить его выполнение чуть позже.
SELECT 1 as [:ret]
WHERE DATEDIFF(SECOND, :from, GETDATE()) >= :interval
Этот скрипт в "ret" вернет единицу, если количество секунд с момента :from по текущий момент не меньше параметра «interval», и ничего не вернет в ином случае. Вот это и будем проверять.
Выбираем тип скрипта «Событие», в выпадающем списке «Параметр» выбираем «ret», и в поле «Значение параметра» впишем 1. Программа будет выполнять скрипт этого типа, пока «ret» не станет равным 1. Повторный запуск будет через 100мс после завершения.
Вот мы и подошли к объединению созданных скриптов в компонент, в котором и будет выполняться проверка на равенство ожидаемого и фактического интервалов.
Объединение скриптов в компонент
По аналогии с созданием скриптов, создадим и откроем компонент «Проверить интервал». Перетащим в область «Скрипты» окна компонента мышкой из списка скриптов (в Project Explorer) созданные только что скрипты. Если перенесли что-то лишнее, кликаем мышкой и жмем кнопку «Удалить». У меня получилась такая последовательность скриптов:
- Задать эталонный интервал
- Получить серверное время
- Дождаться завершения таймаута
- Получить серверное время
- Получить разницу во времени
В разделе «Параметры» открытого компонента добавим 4 новых параметра:
Параметр | Контекст | Комментарий |
---|---|---|
interval | Эталон | Ожидаемое значение. Будет получено из (1) скрипта |
interval | Тест | Фактическое значение. Будет получено из (5) скрипта |
start | Локальный | Время начала теста. Будет получено из (2) скрипта |
finish | Локальный | Время завершения теста. Будет получено из (4) скрипта |
Осталось задать соответствие между параметрами компонента и перенесенных скриптов. Для этого кликаем на первый скрипт «Задать эталонный интервал» в списке скриптов. В таблице «Параметры скрипта» выделяем параметр «interval» и, через контекстное меню мышки, задаем связь с параметром компонента: Эталонinterval.
Параметр | С чем связан |
---|---|
Получить серверное время | |
NOW | [Локальный] start |
Дождаться завершения таймаута | |
ret | пропускаем |
from | [Локальный] start |
interval | [Эталон] interval |
Получить серверное время | |
NOW | [Локальный] finish |
Получить разницу во времени | |
from | [Локальный] start |
to | [Локальный] finish |
interval | [Тест] interval |
Сохраняем компонент. У меня получился вот такой результат.
Перед запуском компонент должен быть сохранен, так как программа перед запуском его подгружает из базы. Нажимаем кнопку «Запустить» в окне компонента. Откроется новое окно с результатом теста, если оно не было открыто ранее.
В данном результате отображены все данные по работе компонента: какие скрипты выполнялись, какие данные пришли на вход и получились на выходе. Если было сравнение параметров, то, как в нашем случае, оно тоже показано.
Попробуем «поломать» тест и посмотрим, как это отобразится. Изменим скрипт «Получить разницу во времени» на
SELECT DATEDIFF(SECOND, :from, :to) + 1 as [:interval]
Вернем на 1 больше, чем должно быть. Сохраним скрипт и снова запустим компонент.
Открыв детали, при должной сноровке можно даже найти место ошибки:
Не забудем вернуть корректное значение запроса «Получить разницу во времени».
Название | Значение |
---|---|
Глобальный | Глобальная область видимости. Переменная компонента с этим контекстом доступна и вне компонента, в тесте например. При отлаживании компонента, в такой переменной можно задавать значение в колонке «Тестовое значение» таблицы «Параметры» |
Локальный | Локальная переменная. Вне компонента её не видно |
Тест | Тестовое значение. По аналогии с глобальной |
Эталон | Эталонное значение. По аналогии с глобальной |
Создание теста из компонентов
Тест строится из компонентов точно так же, как и компонент из скриптов. Поэтому не буду подробно его описывать. Сделаем тест «Проверка интервалов» и перетащим в него 2 раза компонент «Проверить интервал». Сохраним и запустим тест.
Заключение
В данной статье описан простейший, искусственный сценарий для теста базы. Но нет ничего сложного и в реальных сценариях, где надо сохранить данные в базу, дождаться их обработки, и получить результат, сравнив с эталоном. Мне было интересно — получится ли убрать рутину ручного труда при создании и выполнении тестов в такой, весьма специфический области. Думаю, отчасти была решена эта задача. И для меня это был опыт знакомства с java и eclipse plugin development. Пусть это и еще один велосипед во всеобщий велопарк.
Ссылки
GoogleDrive: Плагин для eclipse (проверено на eclipse 4.5)
Проект плагина на GitHub
Автор: laminy