Często logika zawarta w procedurach jest dość skomplikowana. W zależności od projektu, może okazać się, że potrzebujemy testów jednostkowych. Dzięki tSQLt możemy testować tSQL w analogiczny sposób do nUnit+moq, czyli:
- Dane po wykonaniu testu są usuwane. Każdy test jest wykonywany w transakcji. Nie musimy się zatem martwić, że testując coś będziemy zaśmiecać bazę danych.
- Każdy element może być odizolowany, czyli możemy stworzyć mock dla tabeli, procedury lub funkcji.
- Testy wykonywane są w pełnej izolacji.
- Łatwa integracja z CI board.
tSQLt to zespól procedur, które umożliwiają wykonywanie testów bezpośrednio w bazie danych. Musimy zatem najpierw zainstalować je tak jak w przypadku c# był to NuGet. Pakiet możemy znaleźć tutaj. W zestawie znajdują się następujące skrypty:
- SetClrEnabled.sql – musimy najpierw włączyć CLR na bazie.
- tSQLt.class.sql – zestaw procedur potrzebny do wykonywania testów (m.in. asercje).
- Example.sql – przykłady testów. Oczywiście nie musimy tego instalować, ale stanowi to znakomitą dokumentację.
W celu napisania naszego pierwszego testu, uaktywniamy CLR i odpalamy następnie tSQL.class.sql. Po instalacji, zobaczymy, że wiele nowych procedur zostało dodanych:
Stwórzmy teraz prostą funkcję w TSQL:
CREATE FUNCTION DivideNumbers ( @a integer,@b integer ) RETURNS integer AS BEGIN declare @result integer; set @result = @a/@b; RETURN @result; END
Oczywiście przykład typowo teoretyczny, ale nie ma teraz to znaczenia.
Tak jak w nUnit zaczynamy od TestFixture:
EXEC tSQLt.NewTestClass 'divideTests';
Z punktu widzenia SQL Server, divideTests stanowi schema:
W tSQLt “TestClass” to po prostu testFixture. To w nim będziemy przechowywać nasze testy. Stwórzmy zatem pierwszy test:
create PROCEDURE divideTests.[testDivideNumbers] AS BEGIN declare @actualResult int declare @expectedValue int set @expectedValue=12 select @actualResult=dbo.DivideNumbers(144,12) exec tSQLt.AssertEquals @expected=@expectedValue, @actual=@actualResult END
Jak widzimy, jest to zwykła procedura, która została stworzona w odpowiednim schema, który jest skojarzony z TestClass (divideTests). Każdy test to standardowe etapy Arrange, Act oraz Assert. Ten ostatni wykonujemy za pomocą szeregu procedur dostarczonych przez tSQLt – w powyższym przypadku jest to AssertEquals.
Jeśli chcemy uruchomić wszystkie testy naraz wtedy wykonujemy:
exec tSQLt.RunAll
W rezultacie zobaczymy raport:
Zmieńmy teraz naszą funkcję, tak aby zawierała błąd:
ALTER FUNCTION [dbo].[DivideNumbers] ( @a integer,@b integer ) RETURNS integer AS BEGIN declare @result integer; set @result = @a/@b+1; -- blad RETURN @result; END
Po odpaleniu testów, dostaniemy tym razem błąd asercji:
Jeśli chcemy uruchomić wyłącznie jeden zestaw testów, wtedy lepiej użyć:
EXEC tSQLt.Run ‘divideTests’;
Oczywiście tworzenie lub testowanie powyższych funkcji mija się z celem. W przyszłych postach pokaże jak testować procedury, które bardzo często zawierają skomplikowaną logikę (co nierzadko jest złą praktyką, ale to inny już temat).
Prawdziwe korzyści z tSQLt zauważa się, gdy należy odizolować pewne tabele i operacje. Powyższy przykład to funkcja, która nie ma żadnych zależności więc tSQLt oprócz asercji nie wniósł nic nowego. Wpis miał na celu pokazać wyłącznie instalacje tSQLt, stworzenie test fixture (“TestClass”) oraz uruchamianie testów.
Najpierw piszesz o potrzebie pisania testów jednostkowych do logiki zawartej w procedurach składowanych, po czym nadmieniasz, że takie procedury to zła praktyka. Trochę mało konsekwentnie. Po pierwsze, nie bójmy się implementować logiki biznesowej w mozliwe najbardziej optymalny sposób – raz dobrze napisana procedura będzie nam służyć zgodnie z przeznaczeniem. Dla niektórych operacji na danych nie ma nic lepszego niż właśnie skompilowany kod procedury, zawartej w schemacie bazy (szybkość, enkapsulacja!). Wszystko kwestia analizy i poczynienia odpowiednich założeń biznesowych. Owszem, zawsze można prowadzić akademickie dyskusje na temat wymienialności źródła danych i elastycznych interfejsach, tylko po co?