Использование TSQLT FakeTable для тестирования таблицы, созданной хранимой процедурой - PullRequest
0 голосов
/ 22 апреля 2020

Я учусь писать модульные тесты для работы. Мне посоветовали использовать TSQLT FakeTable для тестирования некоторых аспектов таблицы, созданной хранимой процедурой.

В других модульных тестах мы создаем временную таблицу для хранимой процедуры, а затем проверяем временную таблицу. Я не уверен, как использовать FakeTable в тесте.

EXEC tSQLt.NewTestClass 'TestThing'; 
GO                                                 
CREATE OR ALTER PROCEDURE TestThing.[test API_StoredProc to make sure parameters work] 
AS 
BEGIN
    DROP TABLE IF EXISTS #Actual;
    CREATE TABLE #Actual   ----Do I need to create the temp table and the Fake table? I thought I  might need to because I'm testing a table created by a stored procedure.
    ( 
        ISO_3166_Alpha2 NVARCHAR(5),
        ISO_3166_Alpha3 NVARCHAR(5),
        CountryName NVARCHAR(100),
        OfficialStateName NVARCHAR(300),
        sovereigny NVARCHAR(75),
        icon NVARCHAR(100)
    );
    INSERT #Actual
    (
        ISO_3166_Alpha2,
        ISO_3166_Alpha3,
        CountryName,
        OfficialStateName,
        sovereigny,
        icon
    )


    EXEC Marketing.API_StoredProc @Username = 'AnyValue',                              -- varchar(100)
                                                 @FundId = 0,                                 -- int
                                                 @IncludeSalesForceInvestorCountry = NULL,    -- bit
                                                 @IncludeRegisteredSalesJurisdictions = NULL, -- bit
                                                 @IncludeALLCountryForSSRS = NULL,            -- bit
                                                 @WHATIF = NULL,                              -- bit
                                                 @OUTPUT_DEBUG = NULL                         -- bit


   EXEC tsqlt.FakeTable @TableName = N'#Actual',        -- nvarchar(max)  -- How do I differentiate between the faketable and the temp table now? 
                        @SchemaName = N'',       -- nvarchar(max)
                        @Identity = NULL,        -- bit
                        @ComputedColumns = NULL, -- bit
                        @Defaults = NULL         -- bit

  INSERT INTO #Actual 
  (
        ISO_3166_Alpha2,
        ISO_3166_Alpha3,
        CountryName,
        OfficialStateName,
        sovereigny,
        icon
    )
    VALUES 
      ('AF', 'AFG', 'Afghanistan', 'The Islamic Republic of Afghanistan', 'UN MEMBER STATE', 'test')

SELECT * FROM #actual 

END;
GO
EXEC tSQLt.Run 'TestThing';

То, что я пытаюсь сделать с помощью приведенного выше кода, в основном просто для того, чтобы заставить работать FakeTable. Я получаю сообщение об ошибке: «FakeTable не может разрешить имя объекта #Actual»

В конечном итоге я хочу проверить параметры в хранимой процедуре. Только определенные записи должны быть возвращены, если, скажем, IncludeSalesForceInvestorCountry имеет значение 1. То, что должно быть возвращено, со временем может измениться, поэтому мне посоветовали использовать FakeTable.

1 Ответ

3 голосов
/ 23 апреля 2020

В вашем сценарии вам не нужно подделывать никакие временные таблицы, просто подделайте таблицу, на которую ссылается Marketing.API_StoredPro c, и заполните ее значениями, которые вы ожидаете получить, а некоторые - нет. , Добавьте то, что вы ожидаете увидеть в #expected таблице, вызовите Marketing.API_StoredProc, дампируя результаты в #actual таблицу, и сравните результаты с tSQLt.AssertEqualsTable.

Хорошей отправной точкой может быть рассмотрение того, как tSQLT.FakeTable работает и в реальных условиях использования.

Как вы знаете, каждый модульный тест выполняется в рамках своей собственной транзакции, запущенной и откатанной инфраструктурой tSQLT. Когда вы вызываете tSQLt.FakeTable в модульном тесте, он временно переименовывает указанную таблицу, а затем создает точно названный факсимильный аппарат этой таблицы. Временная копия допускает NULL в каждом столбце, не имеет первичных или внешних ключей, идентификатора столбца, проверки, ограничений по умолчанию или уникальных ограничений (хотя некоторые из них могут быть включены в факсимильную таблицу в зависимости от параметров, передаваемых в tSQLt.FakeTable). Во время тестовой транзакции любой объект, который ссылается на таблицу имен, будет использовать поддельную, а не реальную таблицу. В конце теста tSQLt откатывает транзакцию, поддельная таблица удаляется, а исходная таблица возвращается в прежнее состояние (все это происходит автоматически). Вы можете спросить, в чем смысл этого?

Представьте, что у вас есть таблица [OrderDetail], в которой столбцы включают OrderId и ProductId в качестве первичного ключа, столбец OrderStatusId плюс несколько других столбцов NOT NULL. DDL для этой таблицы может выглядеть примерно так:

CREATE TABLE [dbo].[OrderDetail]
(
  OrderDetailId int IDENTITY(1,1) NOT NULL
, OrderId int NOT NULL
, ProductId int NOT NULL
, OrderStatusId int NOT NULL
, Quantity int NOT NULL
, CostPrice decimal(18,4) NOT NULL
, Discount decimal(6,4) NOT NULL
, DeliveryPreferenceId int NOT NULL
, PromisedDeliveryDate datetime NOT NULL
, DespatchDate datetime NULL
, ActualDeliveryDate datetime NULL
, DeliveryDelayReason varchar(500) NOT NULL
/* ... other NULL and NOT NULL columns */
, CONSTRAINT PK_OrderDetail PRIMARY KEY CLUSTERED (OrderId, ProductId)
, CONSTRAINT AK_OrderDetail_AutoIncrementingId UNIQUE NONCLUSTERED (OrderDetailId)
, CONSTRAINT FK_OrderDetail_Order FOREIGN KEY (OrderId) REFERENCES [dbo].[Orders] (OrderId)
, CONSTRAINT FK_OrderDetail_Product FOREIGN KEY (ProductId) REFERENCES [dbo].[Product] (ProductId)
, CONSTRAINT FK_OrderDetail_OrderStatus FOREIGN KEY (OrderStatusId) REFERENCES [dbo].[OrderStatus] (OrderStatusId)
, CONSTRAINT FK_OrderDetail_DeliveryPreference FOREIGN KEY (DeliveryPreferenceId) REFERENCES [dbo].[DeliveryPreference] (DeliveryPreferenceId)
);

Как видите, эта таблица имеет зависимости внешнего ключа от таблиц Orders, Product, DeliveryPreference и OrderStatus. Продукт, в свою очередь, может иметь внешние ключи, которые, среди прочего, ссылаются на ProductType, BrandCategory, Supplier. Таблица Orders содержит ссылки на внешние ключи для Customer, Address и SalesPerson. Все таблицы в этой цепочке имеют многочисленные столбцы, определенные как NOT NULL и / или ограниченные CHECK и другими ограничениями. Некоторые из этих таблиц имеют больше внешних ключей.

Теперь представьте, что вы хотите написать хранимую процедуру (OrderDetailStatusUpdate), задачей которой является обновление статуса заказа для одной строки в таблице OrderDetail. Он имеет три входных параметра @OrderId, @ProductId и @OrderStatusId. Подумайте, что вам нужно сделать, чтобы настроить тест для этой процедуры. Вам необходимо добавить как минимум две строки в таблицу OrderDetail, включая все столбцы NOT NULL. Вам также необходимо добавить родительские записи во все таблицы, на которые ссылаются FK, а также во все таблицы выше этой иерархии, чтобы гарантировать, что все ваши вставки соответствуют всем допускаемым обнуляемостью и другим ограничениям для этих таблиц. По моим подсчетам, это как минимум 11 таблиц, которые нужно заполнить, все для одного простого теста. И даже если вы прикусите пулю и выполните все эти настройки, в какой-то момент в будущем кто-то может (вероятно, будет) прийти и добавить новый столбец NOT NULL в одну из этих таблиц или изменить ограничение, которое вызовет ваш тест провал - и этот сбой на самом деле не имеет ничего общего с вашим тестом или хранимой процедурой, которую вы тестируете. Один из базовых c принципов разработки, основанной на тестах, заключается в том, что тест должен иметь только причину неудачи, я считаю десятки.

tSQLT.FakeTable на помощь.

Что такое минимум, который вам действительно нужно сделать, чтобы настроить тест для этой процедуры? Вам нужно две строки в таблице OrderDetail (одна обновляется, другая нет), и единственные столбцы, которые вам «нужно» учитывать, это OrderId и ProductId (идентификационный ключ) плюс OrderStatusId - обновляемый столбец. Остальные столбцы, хотя и важны в общем проекте, не имеют отношения к тестируемому объекту. В своем тесте для OrderDetailStatusUpdate вы должны выполнить следующие шаги:

  • Вызовите tSQLt.FakeTable 'dbo.OrderDetail'
  • Создайте таблицу #expected (со столбцами OrderId, ProductId и OrderStatusId) и заполните ее двумя строками, которые, как вы ожидаете, закончатся (одна будет иметь ожидаемый OrderStatusId, а другая может быть NULL)
  • Добавить две строки в теперь смоделированной таблице OrderDetail (только для OrderId и ProductId)
  • Вызовите тестируемую процедуру OrderDetailStatusUpdate, передавая OrderID и ProductID для одной из вставленных строк плюс идентификатор OrderStatusId, на который вы меняете.
  • Используйте tSQLt.AssertEqualsTable, чтобы сравнить ожидаемую таблицу с таблицей OrderDetail. Это утверждение будет сравнивать только столбцы в таблице #expected, остальные столбцы в OrderDetail будут проигнорированы

Создание этого теста действительно быстрое, и единственная причина, по которой он может потерпеть неудачу, заключается в том, что что-то уместное чтобы тестируемый код изменился в базовой схеме. Изменения в любых других столбцах таблицы OrderDetail или любой из таблиц parent / grand-parent не приведут к сбою этого теста.

Поэтому причина использования tSQLt.FakeTable (или любого другого типа фиктивного объекта) обеспечить действительно надежную изоляцию теста и просто подготовить данные теста.

...