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.