Несколько лет занимаюсь программированием баз данных и столько же вижу как непросто идет тестирование функционала, реализованного в самой базе. Такая задача требует от тестировщика не только знаний SQL, но, порой, и понимания тонкостей работы тестируемого функционала. А разработчики не всегда стремятся описать свое решение во всех деталях.
Стало интересно — как сейчас тестируется логика базы данных. Думаю, неудивительно, что на тот момент поиски в интернете успехом не увенчались. То ли из-за недостаточной настойчивости, то ли из-за желания сделать «такое же, но свое, другое». А имеющиеся варианты не устраивали из-за сложности использования. Например, были и clr-сборки с unit-тестами. Про HP QTP узнал уже позже.
И эта деятельность привела к созданию конструктора тестов, воплотившегося в плагине к eclipse для тестирования базы. В статье будут описаны основы работы с конструктором.
Не хочу теорию, хочу сразу результат!
В основу алгоритма вошли принципы:
И как бы я, будучи тестировщиком, хотел бы создавать тесты:
Хорошо, с этим понятно. А где же проверка? Ведь результат теста — это сравнение ожидаемого и фактического результатов. Поэтому, и компонент и тест хранят для своих параметров контекст (подробнее и с примером ниже), который и укажет — это ожидаемое или эталонное значение. При этом, параметры должны быть с одинаковым именами. И, когда в работе компонента или теста произойдет изменение значения такого параметра, должна выполниться проверка на равенство или неравенство этих значений. Если проверка не прошла, весь тест завершится неуспешно.
Плагин умеет работать с MS SQL Server.
Для порядка, через контекстное меню на разделе «Скрипты» создадим папку «Пример», в которой и будут лежать наши примеры. И создадим через контекстное меню на папке «Пример» скрипт «Получить серверное время», который вернет дату и время сервера. Откроем папку «Пример» и, двойным кликом, откроем скрипт. Впишем этот текст скрипта и нажмем кнопку «Найти параметры».
Параметры находятся по сигнатуре :param. А, чтобы получить значение из скрипта, следует вернуть DataSet с названием колонки — именем параметра [:param].
Выберем параметру тип DATE и направление «Выходной». Сохраняем скрипт и ждем рядом кнопку «Выполнить». В колонке «Тестовое значение» отобразится результат:
Это простой пример. Теперь усложним задачу. Нам предстоит в тесте подождать 5 секунд, а потом убедиться, что мы выполнялись ровно 5 секунд. Это довольно искусственная задача, но для понимания возможностей самый раз.
Для решения задачи нам предстоит сделать несколько скриптов, чтобы потом объединить в компонент. Вот эти скрипты:
Для (2) и (4) шагов у нас уже есть скрипт. (3) оставим на последок как наиболее сложный. Простой WAITFOR DELAY мы не будем использовать, так как это может быть любая длительная операция, которая может завершиться в любой момент времени, а не по истечении фиксированного интервала.
(1) может выглядеть совсем просто. Единственный параметр выходной, типа INTEGER:
И (5) так же не сложно. Первые два параметра будут входными, типа DATE. Третий — выходной, типа INTEGER:
Настал черед (3) скрипта, задача которого отработать быстро и сообщить — выполнилось ли условие и можно идти дальше, или повторить его выполнение чуть позже.
Этот скрипт в "ret" вернет единицу, если количество секунд с момента :from по текущий момент не меньше параметра «interval», и ничего не вернет в ином случае. Вот это и будем проверять.
Выбираем тип скрипта «Событие», в выпадающем списке «Параметр» выбираем «ret», и в поле «Значение параметра» впишем 1. Программа будет выполнять скрипт этого типа, пока «ret» не станет равным 1. Повторный запуск будет через 100мс после завершения.
Вот мы и подошли к объединению созданных скриптов в компонент, в котором и будет выполняться проверка на равенство ожидаемого и фактического интервалов.
По аналогии с созданием скриптов, создадим и откроем компонент «Проверить интервал». Перетащим в область «Скрипты» окна компонента мышкой из списка скриптов (в Project Explorer) созданные только что скрипты. Если перенесли что-то лишнее, кликаем мышкой и жмем кнопку «Удалить». У меня получилась такая последовательность скриптов:
В разделе «Параметры» открытого компонента добавим 4 новых параметра:
Осталось задать соответствие между параметрами компонента и перенесенных скриптов. Для этого кликаем на первый скрипт «Задать эталонный интервал» в списке скриптов. В таблице «Параметры скрипта» выделяем параметр «interval» и, через контекстное меню мышки, задаем связь с параметром компонента: Эталон\interval.
Перед запуском компонент должен быть сохранен, так как программа перед запуском его подгружает из базы. Нажимаем кнопку «Запустить» в окне компонента. Откроется новое окно с результатом теста, если оно не было открыто ранее.
В данном результате отображены все данные по работе компонента: какие скрипты выполнялись, какие данные пришли на вход и получились на выходе. Если было сравнение параметров, то, как в нашем случае, оно тоже показано.
Попробуем «поломать» тест и посмотрим, как это отобразится. Изменим скрипт «Получить разницу во времени» на
Вернем на 1 больше, чем должно быть. Сохраним скрипт и снова запустим компонент.
Не забудем вернуть корректное значение запроса «Получить разницу во времени».
Тест строится из компонентов точно так же, как и компонент из скриптов. Поэтому не буду подробно его описывать. Сделаем тест «Проверка интервалов» и перетащим в него 2 раза компонент «Проверить интервал». Сохраним и запустим тест.
В данной статье описан простейший, искусственный сценарий для теста базы. Но нет ничего сложного и в реальных сценариях, где надо сохранить данные в базу, дождаться их обработки, и получить результат, сравнив с эталоном. Мне было интересно — получится ли убрать рутину ручного труда при создании и выполнении тестов в такой, весьма специфический области. Думаю, отчасти была решена эта задача. И для меня это был опыт знакомства с java и eclipse plugin development. Пусть это и еще один велосипед во всеобщий велопарк.
GoogleDrive: Плагин для eclipse (проверено на eclipse 4.5)
Проект плагина на GitHub
Стало интересно — как сейчас тестируется логика базы данных. Думаю, неудивительно, что на тот момент поиски в интернете успехом не увенчались. То ли из-за недостаточной настойчивости, то ли из-за желания сделать «такое же, но свое, другое». А имеющиеся варианты не устраивали из-за сложности использования. Например, были и clr-сборки с unit-тестами. Про HP QTP узнал уже позже.
И эта деятельность привела к созданию конструктора тестов, воплотившегося в плагине к eclipse для тестирования базы. В статье будут описаны основы работы с конструктором.
Не хочу теорию, хочу сразу результат!
Теория
В основу алгоритма вошли принципы:
- тест состоит из последовательности шагов (простых sql-скриптов)
- шаг — это единичная операция, принимающая одни данные на вход и возвращающая другие
- шаги можно объединять в последовательность для создания более сложного модуля
- меньше кода при создании теста, больше работы мышкой
- тесты будут храниться в базе
И как бы я, будучи тестировщиком, хотел бы создавать тесты:
- Я хочу 1 раз написать простой скрипт, дать ему название, задать параметры, и сохранить для повторного использования. И пусть программа сама находит параметры из текста скрипта. А я уж задам им тип данных и направление — входной это параметр или выходной.
- Хочу объединять несколько скриптов в один компонент. Допустим, нужно положить запись в базу, дождаться её обработки и получить результат этой обработки. Компонент — основная логическая единица для создания теста.
- И пусть компоненты можно будет объединять в сам тест. Тот самый, который и будет проверять ту или иную бизнес-операцию.
Хорошо, с этим понятно. А где же проверка? Ведь результат теста — это сравнение ожидаемого и фактического результатов. Поэтому, и компонент и тест хранят для своих параметров контекст (подробнее и с примером ниже), который и укажет — это ожидаемое или эталонное значение. При этом, параметры должны быть с одинаковым именами. И, когда в работе компонента или теста произойдет изменение значения такого параметра, должна выполниться проверка на равенство или неравенство этих значений. Если проверка не прошла, весь тест завершится неуспешно.
Что из этого получилось
- Установка и настройка
- Создание проекта
- Создание скрипта
- Объединение скриптов в компонент
- Создание теста из компонентов
Установка и настройка
Плагин умеет работать с MS SQL Server.
- ссылка на плагин к eclipse и исходники в конце статьи. Ставим плагин
- создаем пустую базу и выполняем на ней скрипт из репозитория (database\script.sql)
- Задаем в настройках 2 базы — для хранения тестов и для выполнения тестов (Window \ Preferences \ Настройки SQL тестов)
Настройка баз![](https://habrastorage.org/files/5c7/628/0b3/5c76280b3a624eaaaa74246176230b9a.png)
![](https://habrastorage.org/files/5c7/628/0b3/5c76280b3a624eaaaa74246176230b9a.png)
Создание проекта
File -> New -> Test -> SQL Test Project![](https://habrastorage.org/files/b5d/469/3de/b5d4693de3dd463f8d2c88a4f179b535.png)
Выбираем настроенные базы и задаем пароли. По завершении жмем «Проверить введенные параметры». Если проверки пройдут успешно, станет доступной кпопка Finish. Жмем её для завершения создания проекта.
![](https://habrastorage.org/files/102/779/fa4/102779fa46c54fc7841bf69f415fd42d.png)
По умолчанию нужная view не открывается, делаем это руками: Window -> Show View -> Other -> SQL Test Project Explorer. Созданный проект предстанет в таком виде:
![](https://habrastorage.org/files/f45/174/860/f451748602bc4be7a93ea97dfd4bbce4.png)
![](https://habrastorage.org/files/b5d/469/3de/b5d4693de3dd463f8d2c88a4f179b535.png)
Выбираем настроенные базы и задаем пароли. По завершении жмем «Проверить введенные параметры». Если проверки пройдут успешно, станет доступной кпопка Finish. Жмем её для завершения создания проекта.
![](https://habrastorage.org/files/102/779/fa4/102779fa46c54fc7841bf69f415fd42d.png)
По умолчанию нужная view не открывается, делаем это руками: Window -> Show View -> Other -> SQL Test Project Explorer. Созданный проект предстанет в таком виде:
![](https://habrastorage.org/files/f45/174/860/f451748602bc4be7a93ea97dfd4bbce4.png)
Создание скрипта
Для порядка, через контекстное меню на разделе «Скрипты» создадим папку «Пример», в которой и будут лежать наши примеры. И создадим через контекстное меню на папке «Пример» скрипт «Получить серверное время», который вернет дату и время сервера. Откроем папку «Пример» и, двойным кликом, откроем скрипт. Впишем этот текст скрипта и нажмем кнопку «Найти параметры».
SELECT GETDATE() AS [:NOW]
Параметры находятся по сигнатуре :param. А, чтобы получить значение из скрипта, следует вернуть DataSet с названием колонки — именем параметра [:param].
Выберем параметру тип DATE и направление «Выходной». Сохраняем скрипт и ждем рядом кнопку «Выполнить». В колонке «Тестовое значение» отобразится результат:
Скрипт в окне![](https://habrastorage.org/files/3dc/635/f0b/3dc635f0bbae4f099c7dcd6bfb454765.png)
![](https://habrastorage.org/files/3dc/635/f0b/3dc635f0bbae4f099c7dcd6bfb454765.png)
Это простой пример. Теперь усложним задачу. Нам предстоит в тесте подождать 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мс после завершения.
Скрипт: ждем завершение таймаута![](https://habrastorage.org/files/8b3/c6b/7bb/8b3c6b7bbe9e44419865f2c78407ef76.png)
![](https://habrastorage.org/files/8b3/c6b/7bb/8b3c6b7bbe9e44419865f2c78407ef76.png)
Вот мы и подошли к объединению созданных скриптов в компонент, в котором и будет выполняться проверка на равенство ожидаемого и фактического интервалов.
Объединение скриптов в компонент
По аналогии с созданием скриптов, создадим и откроем компонент «Проверить интервал». Перетащим в область «Скрипты» окна компонента мышкой из списка скриптов (в Project Explorer) созданные только что скрипты. Если перенесли что-то лишнее, кликаем мышкой и жмем кнопку «Удалить». У меня получилась такая последовательность скриптов:
- Задать эталонный интервал
- Получить серверное время
- Дождаться завершения таймаута
- Получить серверное время
- Получить разницу во времени
В разделе «Параметры» открытого компонента добавим 4 новых параметра:
Параметр | Контекст | Комментарий |
---|---|---|
interval | Эталон | Ожидаемое значение. Будет получено из (1) скрипта |
interval | Тест | Фактическое значение. Будет получено из (5) скрипта |
start | Локальный | Время начала теста. Будет получено из (2) скрипта |
finish | Локальный | Время завершения теста. Будет получено из (4) скрипта |
Осталось задать соответствие между параметрами компонента и перенесенных скриптов. Для этого кликаем на первый скрипт «Задать эталонный интервал» в списке скриптов. В таблице «Параметры скрипта» выделяем параметр «interval» и, через контекстное меню мышки, задаем связь с параметром компонента: Эталон\interval.
Связываем параметры скрипта и компонента![](https://habrastorage.org/files/19b/550/8f2/19b5508f27934db2b52f02e2a44a3030.png)
![](https://habrastorage.org/files/19b/550/8f2/19b5508f27934db2b52f02e2a44a3030.png)
Аналогично устанавливаем связи и для оставшихся скриптов
Сохраняем компонент. У меня получился вот такой результат. Параметр | С чем связан |
---|---|
Получить серверное время | |
NOW | [Локальный] start |
Дождаться завершения таймаута | |
ret | пропускаем |
from | [Локальный] start |
interval | [Эталон] interval |
Получить серверное время | |
NOW | [Локальный] finish |
Получить разницу во времени | |
from | [Локальный] start |
to | [Локальный] finish |
interval | [Тест] interval |
Итоговый вид компонента![](https://habrastorage.org/files/eb8/d2c/e8c/eb8d2ce8c4e340b7a7f158402b25809f.png)
![](https://habrastorage.org/files/eb8/d2c/e8c/eb8d2ce8c4e340b7a7f158402b25809f.png)
Перед запуском компонент должен быть сохранен, так как программа перед запуском его подгружает из базы. Нажимаем кнопку «Запустить» в окне компонента. Откроется новое окно с результатом теста, если оно не было открыто ранее.
Результат выполнения теста![](https://habrastorage.org/files/301/594/41c/30159441c9b64d0fb161f98bc31997cf.png)
![](https://habrastorage.org/files/301/594/41c/30159441c9b64d0fb161f98bc31997cf.png)
В данном результате отображены все данные по работе компонента: какие скрипты выполнялись, какие данные пришли на вход и получились на выходе. Если было сравнение параметров, то, как в нашем случае, оно тоже показано.
Попробуем «поломать» тест и посмотрим, как это отобразится. Изменим скрипт «Получить разницу во времени» на
SELECT DATEDIFF(SECOND, :from, :to) + 1 as [:interval]
Вернем на 1 больше, чем должно быть. Сохраним скрипт и снова запустим компонент.
Результат выполнения компонента![](https://habrastorage.org/files/79a/036/af4/79a036af4535424abcb8e71701a9279e.png)
Открыв детали, при должной сноровке можно даже найти место ошибки:
![](https://habrastorage.org/files/76e/1d0/89c/76e1d089c35b49c384fad4c6f773c9c1.png)
![](https://habrastorage.org/files/79a/036/af4/79a036af4535424abcb8e71701a9279e.png)
Открыв детали, при должной сноровке можно даже найти место ошибки:
![](https://habrastorage.org/files/76e/1d0/89c/76e1d089c35b49c384fad4c6f773c9c1.png)
Не забудем вернуть корректное значение запроса «Получить разницу во времени».
Контексты параметров
Название | Значение |
---|---|
Глобальный | Глобальная область видимости. Переменная компонента с этим контекстом доступна и вне компонента, в тесте например. При отлаживании компонента, в такой переменной можно задавать значение в колонке «Тестовое значение» таблицы «Параметры» |
Локальный | Локальная переменная. Вне компонента её не видно |
Тест | Тестовое значение. По аналогии с глобальной |
Эталон | Эталонное значение. По аналогии с глобальной |
Создание теста из компонентов
Тест строится из компонентов точно так же, как и компонент из скриптов. Поэтому не буду подробно его описывать. Сделаем тест «Проверка интервалов» и перетащим в него 2 раза компонент «Проверить интервал». Сохраним и запустим тест.
Результат выполнения теста
Ожидаемый результат:
![](https://habrastorage.org/files/750/84c/43c/75084c43cca94455ad21fdca17404c3b.png)
![](https://habrastorage.org/files/750/84c/43c/75084c43cca94455ad21fdca17404c3b.png)
Заключение
В данной статье описан простейший, искусственный сценарий для теста базы. Но нет ничего сложного и в реальных сценариях, где надо сохранить данные в базу, дождаться их обработки, и получить результат, сравнив с эталоном. Мне было интересно — получится ли убрать рутину ручного труда при создании и выполнении тестов в такой, весьма специфический области. Думаю, отчасти была решена эта задача. И для меня это был опыт знакомства с java и eclipse plugin development. Пусть это и еще один велосипед во всеобщий велопарк.
Ссылки
GoogleDrive: Плагин для eclipse (проверено на eclipse 4.5)
Проект плагина на GitHub
mrz0diak
А чем DbFit не подошел?
laminy
Думаю, вопрос предпочтений — кому как удобней. Мой интерес в данном сучае — меньше SQL кода, повторное использование, наглядность в создании теста.