[Перевод] Использование фейковых функций в tSQLt для тестирования T-SQL кода
Применение практик модульного тестирования для кода базы данных улучшает качество и архитектуру кода, а также позволяет своевременно обнаруживать и устранять ошибки.
Модульные тесты характеризуются двумя моментами:
тестирование минимального модуля, атомарной части кода;
изоляция тестируемого кода от зависимостей (при возможности).
Изоляция зависимостей — очень важная техника, о которой мы поговорим далее в этой статье. В частности, обсудим, как использовать фейковые функции (заглушки) при тестировании T-SQL кода с использованием фреймворка tSQLt. Для решения этой задачи tSQLt предлагает хранимую процедуру FakeFunction. Давайте разберемся с этим подробнее.
Если вы не знакомы с tSQLt, то можете начать знакомство с этой статьи.
Обзор FakeFunction
Часто логика тестируемого кода может быть довольно сложной, в том числе включать в себя вызовы других функций. При этом результат вызываемой функции может влиять на результат теста, поскольку поведение тестируемого алгоритма зависит от результата функции. Это очень неприятная ситуация, которую мы хотим избежать при модульном тестировании. Происходит нарушение паттерна изоляции зависимостей модульного теста.
Давайте рассмотрим пример, как использовать функцию-заглушку (fake function) в тесте. Скалярная функция UDefFuncOddorEven
, приведенная ниже, выполняет некоторые вычисления по модулю и возвращает четный или нечетный получился результат.
CREATE OR ALTER FUNCTION dbo.UDefFuncOddorEven (@n int)
RETURNS bit
AS
BEGIN
DECLARE @ModuleRes INT
SET @ModuleRes = (@n%11)
SET @ModuleRes = (@n%9)
RETURN (@ModuleRes % 2)
END
Далее эта функция используется в хранимой процедуре, логика работы которой зависит от значения, возвращаемого этой функцией.
DROP TABLE IF EXISTS OrderOnline
GO
CREATE TABLE OrderOnline
(Id INT PRIMARY KEY IDENTITY(1,1),
OrderName VARCHAR(100),
CustomerName VARCHAR(100))
GO
CREATE OR ALTER PROC SetOrders
@OName AS VARCHAR(100),
@CName AS VARCHAR(100)
AS
BEGIN
DECLARE @RandomVal AS INT
SET @RandomVal= FLOOR(RAND()*1000)
DECLARE @ufResult AS BIT
SELECT @ufResult=dbo.UDefFuncOddorEven(@RandomVal)
IF @ufResult=1
BEGIN
INSERT INTO OrderOnline (OrderName,CustomerName) VALUES (@OName,@CName)
END
END
GO
SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='OrderOnline'
SELECT * FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_NAME='SetOrders'
В хранимой процедуре SetOrders
генерируется случайное число, а затем, в зависимости от результата функции UDefFuncOddorEven
, выполняется или не выполняется операция INSERT
. Наша цель — проверить вставку данных, но SetOrders
сильно связана с функцией UDefFuncOddorEven
, которая напрямую влияет на поток выполнения кода SetOrders
. Короче говоря, мы должны решить проблему зависимости. Хранимая процедура FakeFunction
позволяет на время теста заменить исходную функцию фиктивной для контроля значения, возвращаемого функцией. Как правило, возвращаемые значения фейковых функций могут быть жестко заданы, что делает их реализацию очень простой.
Синтаксис FakeFunction
tSQLt.FakeFunction [@FunctionName = ] 'function name'
, [@FakeFunctionName = ] 'fake function name'
Хранимая процедура FakeFunction принимает два параметра:
@FunctionName — исходное имя функции, которую мы хотим подменить.
@FakeFunctionName — имя фейковой функции, которая заменяет исходную функцию в течение работы теста.
Оба параметра типа nvarchar (max).
Пишем фейковую функцию
Сначала создадим фейковую функцию, которую будем использовать вместо оригинальной. Сделаем ее как можно более простой, чтобы не усложнять тест. Здесь еще обратим внимание на один момент, касающийся именования фейковых функций: добавим к имени оригинальной функции «Fake» и возвращаемое значение «Return_1», чтобы по имени можно было легко понять, что делает эта функция.
CREATE OR ALTER FUNCTION dbo.UDefFuncOddorEven_Fake_Return_1 (@n int)
RETURNS bit
AS
BEGIN
RETURN 1
END
Как видите, мы заменили сложное вычисление в исходной функции простым жестко заданным значением.Мы всегда знаем, какое значение возвращает фейковая функция.
Используем фейковую функцию
Мы хотим проверить выполнение INSERT в хранимой процедуре SetOrders
: вставляются ли правильные значения в таблицу OrderOnline
? Давайте напишем тест с помощью фреймворка tSQLt. Тест будет выглядеть следующим образом.
EXECUTE tsqlt.NewTestClass 'TestFakeFunction'
GO
EXECUTE tSQLt.NewTestClass 'TestFakeFunction'
GO
CREATE OR ALTER PROCEDURE TestFakeFunction.[Test SetOrders_StoredProcedure_InsertFunction]
AS
BEGIN
DROP TABLE IF EXISTS expected
DROP TABLE IF EXISTS actual
EXEC tSQLt.FakeTable 'OrderOnline'
SELECT TOP(0) * INTO expected FROM OrderOnline
SELECT TOP(0) * INTO actual FROM OrderOnline
EXEC tSQLt.FakeFunction 'dbo.UDefFuncOddorEven' ,'dbo.UDefFuncOddorEven_Fake_Return_1'
INSERT INTO expected
(OrderName , CustomerName)
VALUES ('Pizza','Ryan Romero')
EXECUTE SetOrders 'Pizza' ,'Ryan Romero'
INSERT INTO actual
SELECT * FROM OrderOnline
EXEC tSQLt.AssertEqualsTable expected,actual
END
GO
EXEC tSQLt.Run 'TestFakeFunction.[Test SetOrders_StoredProcedure_InsertFunction]'
Далее рассмотрим этот тест построчно.
Создание фейковой таблицы
EXEC tSQLt.FakeTable 'OrderOnline'
Здесь мы создаем фейковую таблицу OrderOnline
, так как во время теста данная таблица должна быть пустой и содержать только строки, которые мы вставили в тесте.
Фактически мы убираем зависимость хранимой процедуры SetOrders
от настоящей таблицы OrderOnline
.
Создание таблиц для ожидаемых (expected) и фактических (actual) результатов
SELECT TOP(0) * INTO expected FROM OrderOnline
SELECT TOP(0) * INTO actual FROM OrderOnline
В конце теста мы сравниваем фактический результат с ожидаемым с помощью tSQLt.AssertTables. Для этого нам необходимо создать две таблицы: expected (ожидаемый результат) и actual (фактический результат).
Самый простой способ создать эти таблицы — взять за основу фейковую таблицу OrderOnline
.
Использование фейковой функции
EXEC tSQLt.FakeFunction 'dbo.UDefFuncOddorEven', 'dbo.UDefFuncOddorEven_Fake_Return_1'
Заменяем исходную функцию фейковой, чтобы точно знать возвращаемый результат.
Заполняем таблицу ожидаемых значений (expected)
INSERT INTO expected
(OrderName , CustomerName)
VALUES ('Pizza','Ryan Romero')
Заполняем таблицу expected
ожидаемыми значениями.
Заполняем таблицу фактических значений (actual)
EXECUTE SetOrders 'Pizza' ,'Ryan Romero'
INSERT INTO actual
SELECT * FROM OrderOnline
Здесь результат выполнения хранимой процедуры SetOrders
вставляется в фейковую таблицу OrderOnline
, данные из которой мы переносим в таблицу actual
для дальнейшего сравнения.
Запускаем тест
EXEC tSQLt.Run 'TestFakeFunction.[Test SetOrders_StoredProcedure_InsertFunction]'
Если значения таблиц actual
и expected
не совпадают, то тест упадет.
Обратите внимание, если хранимая процедура FakeFunction
не найдет оригинальную или фейковую функцию, то будет ошибка.
Еще один момент, который следует учитывать при работе с FakeFunction — должны совпадать параметры оригинальной и фейковой функций, иначе будет ошибка "Parameters of both functions must match!"
.
Заключение
В этой статье мы продолжили наше путешествие по модульному тестированию T-SQL и фреймворку tSQLt. Мы научились использовать фейковые функции в тестах с помощью FakeFunction
.
Модульное тестирование кода бэкенда прочно вошло в нашу жизнь, но код базы данных по-прежнему мало кто тестирует. Приглашаем всех желающих на открытый урок, на котором поговорим о модульном тестировании кода SQL Server и использовании для этого tSqlt. Занятие пройдёт уже сегодня в 20:00. Записаться можно по ссылке.