Jedną z największych korzyści z tSQLt jest moim zdaniem izolacja danych. Załóżmy, że mamy na następującą tabelę:
USE [testdb]
GO
/****** Object: Table [dbo].[Articles] Script Date: 2016-02-20 2:24:25 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Articles](
[Id] [int] NOT NULL,
[Title] [nchar](10) NULL,
[Content] [nchar](500) NULL,
PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
Następnie w naszym teście chcemy przetestować jakąś metodę operującą właśnie na tabeli Article. Z tego względu, musimy umieścić w niej jakieś dane. Przykład testu:
USE [testdb];
GO
SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO
-- Comments here are associated with the test.
-- For test case examples, see: http://tsqlt.org/user-guide/tsqlt-tutorial/
ALTER PROCEDURE [samples].[test tableIsolation]
AS
BEGIN
INSERT INTO dbo.Articles
( Id ,
Title ,
Content
)
VALUES ( 1 , -- Id - int
N'test title' , -- Title - nchar(10)
N' test content' -- Content - nchar(500)
);
END;
Widzimy, że nie tworzymy mock’a. Każde odpalenie testu spowoduje dodanie danych do tabeli. Na szczęście wszystkie testy odpalane są transakcjach, zatem po wykonaniu testu, nastąpi rollback.
Może wydawać się to w porządku, ale operowanie na prawdziwej tabeli jest złe, ponieważ co prawda wszelkie zmiany dokonane przez test zostaną anulowane, to test będzie widział dane, które już istniały w tabeli przed odpaleniem testu. Z tego względu stworzenie “fake” tabeli jest lepszym rozwiązaniem:
ALTER PROCEDURE [samples].[test tableIsolation]
AS
BEGIN
EXEC tSQLt.FakeTable 'dbo.Articles'
INSERT INTO dbo.Articles
( Id ,
Title ,
Content
)
VALUES ( 1 , -- Id - int
N'test title' , -- Title - nchar(10)
N' test content' -- Content - nchar(500)
);
END;
Za pomocą FakeTable, tworzymy odizolowany mock tabeli. Efekt będzie taki, że:
– dane w tabeli Articles zapisane przed wykonaniem testu, nie są widocznie w trakcie wykonywania testu
– dane zapisane w tabeli Articles w trakcie testu, nie są widocznie na zewnątrz po wykonaniu testu.
Bez fake, wyłącznie drugi punkt byłby prawdziwy, ponieważ rollback by anulował wszystkie nowo dodane wiersze. W testach jednostkowych chcemy mieć zawsze pełną izolacje tak, że dane dodane do tabel poza testem, nie mają wpływu na wynik wykonania testów.
Jak zatem działa FakeTable? To zwykła procedura T-SQL, zatem możemy ją sprawdzić w kodzie:
ALTER PROCEDURE [tSQLt].[FakeTable]
@TableName NVARCHAR(MAX),
@SchemaName NVARCHAR(MAX) = NULL, --parameter preserved for backward compatibility. Do not use. Will be removed soon.
@Identity BIT = NULL,
@ComputedColumns BIT = NULL,
@Defaults BIT = NULL
AS
BEGIN
DECLARE @OrigSchemaName NVARCHAR(MAX);
DECLARE @OrigTableName NVARCHAR(MAX);
DECLARE @NewNameOfOriginalTable NVARCHAR(4000);
DECLARE @OrigTableFullName NVARCHAR(MAX); SET @OrigTableFullName = NULL;
SELECT @OrigSchemaName = @SchemaName,
@OrigTableName = @TableName
IF(@OrigTableName NOT IN (PARSENAME(@OrigTableName,1),QUOTENAME(PARSENAME(@OrigTableName,1)))
AND @OrigSchemaName IS NOT NULL)
BEGIN
RAISERROR('When @TableName is a multi-part identifier, @SchemaName must be NULL!',16,10);
END
SELECT @SchemaName = CleanSchemaName,
@TableName = CleanTableName
FROM tSQLt.Private_ResolveFakeTableNamesForBackwardCompatibility(@TableName, @SchemaName);
EXEC tSQLt.Private_ValidateFakeTableParameters @SchemaName,@OrigTableName,@OrigSchemaName;
EXEC tSQLt.Private_RenameObjectToUniqueName @SchemaName, @TableName, @NewNameOfOriginalTable OUTPUT;
SELECT @OrigTableFullName = S.base_object_name
FROM sys.synonyms AS S
WHERE S.object_id = OBJECT_ID(@SchemaName + '.' + @NewNameOfOriginalTable);
IF(@OrigTableFullName IS NOT NULL)
BEGIN
IF(COALESCE(OBJECT_ID(@OrigTableFullName,'U'),OBJECT_ID(@OrigTableFullName,'V')) IS NULL)
BEGIN
RAISERROR('Cannot fake synonym %s.%s as it is pointing to %s, which is not a table or view!',16,10,@SchemaName,@TableName,@OrigTableFullName);
END;
END;
ELSE
BEGIN
SET @OrigTableFullName = @SchemaName + '.' + @NewNameOfOriginalTable;
END;
EXEC tSQLt.Private_CreateFakeOfTable @SchemaName, @TableName, @OrigTableFullName, @Identity, @ComputedColumns, @Defaults;
EXEC tSQLt.Private_MarkFakeTable @SchemaName, @TableName, @NewNameOfOriginalTable;
END
Możemy zobaczyć, że oryginalna tabela ma zmienianą nazwę, a potem tworzymy fake o takiej samej nazwie jak oryginalna tabela.
Na zakończenie bardzo ważna uwaga odnośnie FakeTable. Wszelkie “constraints” nie są wymuszane. W powyższym przypadku, klucz główny jest wymaganym polem i następujący kod normalnie spowoduje wyjątek.
INSERT INTO dbo.Articles (Title) VALUES('test')
Wyjątek:
“Cannot insert the value NULL into column ‘Id’, table ‘testdb.dbo.Articles’; column does not allow nulls. INSERT fails.”
Z kolei w teście powyższy kod nie spowoduje żadnych błędów. Jak wiemy, FakeTable to nowa tabela ponieważ stara ma zmienianą nazwę. Ta nowa tabela, nie ma dodanych żadnych “constraints”. W praktyce jest to bardzo korzystne. Testy jednostkowe powinny skupiać się wyłącznie na jednej funkcjonalności. Z tego względu, jeśli tabela ma 20 wymaganych kolumn, a my testujemy wyłącznie jedną, nie musimy pozostałych 19 wypełniać danymi.