Bazy danych · Programowanie

tSQLt czyli testy SQL

To już niestety ostatni wpis z cyklu pod patronatem Objectivity. Tematyka testów SQL mnie bardzo zainteresowała, więc chcę się jeszcze tym aspektem mojej pracy z Wami podzielić 🙂

Testy SQL

Pisząc aplikację zwykle piszemy testy. Raczej nie muszę wyjaśniać po co 🙂 Czy tworząc różne procedury lub funkcje na bazie danych też sprawdzamy ich poprawność pisząc testy? Tu już niekoniecznie. A szkoda, bo pisanie testów w tSQLt nie różni się za bardzo od pisania zwykłych testów jednostkowych. Oczywiście jest to SQL, a nie C# – ale cały koncept pozostaje taki sam. Reguła 3A (Arrange, Act, Assert) dalej jest aktualna: przygotowujemy dane, wywołujemy interesującą na logikę i sprawdzamy, czy wynik jest zgodny z oczekiwanym.

Instalacja tSQLt

Na początek trzeba zainstalować sobie tSQLt – warto iść zgodnie z dokumentacją. Na tej samej stronie jest również przycisk do pobrania paczki. W pobranej paczce jest plik Example.sql, który pozwala utworzyć testową bazę danych wraz z testami tSQLt.

Test

Test jest zwykłą procedurą złożoną z kilku kroków opisanych poniżej. Co ważne: żeby zostać rozpoznany jako klasa testowa, musi zaczynać się od słowa test np. [schema].[test status message includes the number of particles].

Test Setup

Czasami zdarza się, że przygotowanie testu wymaga wielu operacji: zamockowania tabel, wstawienia wielu wierszy itp. Żeby nie tworzyć wielkich testów z milionem linii, warto przygotować plik Setup, w którym te wszystkie dane przygotujemy. Co ważne, taki plik nie może mieć nazwy zaczynającej się od słowa test (bo nie jest stricte klasą testową, tylko pomocniczą). Przykładowa klasa pomocnicza dla testu opisywanego powyżej może więc nazywać się [schema].[status message includes the number of particles setup]. Niestety z powodu sortowania alfabetycznego według nazw w Object Explorer w bazie danych, Test i Test Setup mogą być rozdzielone wieloma innymi testami.

Arrange

Na początek potrzebujemy zamockować sobie jakieś dane. Skoro to baza danych – zapewne potrzebujemy wpisów w konkretnej tabeli. Najpierw więc tworzymy sobie sztuczną tabelę (z pominięciem constraints – możecie o tym poczytać w dokumentacji):

EXEC tSQLt.FakeTable 'Accelerator.Particle';

A następnie wstawiamy do niej jakieś wartości:

INSERT INTO Accelerator.Particle (Id, X, Y, Value) VALUES (1, 0.5, 0.5, 'MyValue');

Dodatkowo przygotujemy sobie tabelę na wyniki:

CREATE TABLE #Actual (
  Id    DECIMAL(10,2),
  X     DECIMAL(10,2),
  Y     DECIMAL(10,2),
  Value DECIMAL(10,2))

Act

Wywołujemy odpowiednią funkcję, którą chcemy przetestować (poniżej jest przedstawiona jej logika):

CREATE FUNCTION [Accelerator].[GetParticlesInRectangle](
  @X1 DECIMAL(10,2),
  @Y1 DECIMAL(10,2),
  @X2 DECIMAL(10,2),
  @Y2 DECIMAL(10,2)
)
RETURNS TABLE
AS RETURN (
  SELECT Id, X, Y, Value 
    FROM Accelerator.Particle
   WHERE X > @X1 AND X < @X2
         AND
         Y > @Y1 AND Y < @Y2
);

i jej wynik wstawiamy do tabeli tymczasowej #Actual

  SELECT Id, X, Y, Value INTO #Actual
  FROM Accelerator.GetParticlesInRectangle(0.0, 0.0, 1.0, 1.0);

AssertEqualsTable

Przygotowujemy tymczasową tabelę #Expected o strukturze identycznej do tabeli #Actual

SELECT TOP(0) * INTO #Expected FROM #Actual;

Wstawiamy konkretne, oczekiwane wartości do tej tabeli

INSERT INTO #Expected (Id, X, Y, Value) VALUES (1, 0.5, 0.5, 'MyValue');

A na koniec wykonujemy porównanie tabel #Expected i #Actual

EXEC tSQLt.AssertEqualsTable '#Expected', '#Actual';

AssertEquals

Innym sposobem testowania jest pobranie wartości zwracanej przez testowaną funkcję IsExperimentReady() (jej logika jest przedstawiona poniżej):

CREATE FUNCTION [Accelerator].[IsExperimentReady]()
RETURNS BIT
AS
BEGIN 
  DECLARE @NumParticles INT;
  
  SELECT @NumParticles = COUNT(1) FROM Accelerator.Particle;
  
  IF @NumParticles > 2
    RETURN 1;

  RETURN 0;
END;

Wynik takiej funkcji możemy zapisać do zmiennej:

SELECT @Ready = Accelerator.IsExperimentReady();

i sprawdzić, czy jest odpowiedni:

EXEC tSQLt.AssertEquals 1, @Ready;

Uruchamianie testu

Konkretny test można uruchomić wywołując komendę

EXEC tSQLt. Run 'schema.test_name'

Można również uruchomić wszystkie testy w obrębie schema

EXEC tSQLt. Run 'schema'

Oraz uruchomić wszystkie testy, jakie mamy w bazie

EXEC tSQLt. RunAll

Output testów

Przykładowy output wywołania więcej niż jednego testu wygląda następująco:

Dostajemy tabelkę z listą testów. Od góry są wyświetlane testy, które zakończyły się sukcesem, a na dole te, które zakończyły się niepowodzeniem. Przy wielu testach taka tabelka może nie być zbyt czytelna, ale można zawsze spojrzeć na ostateczny komunikat ze statusem sukcesu/błędu z podsumowaniem z ostatniej linijki:

Output testu

Output przykładowego testu, który się sypie wygląda następująco:

Może to być niewłaściwa wartość konkretnej propercji:

Widać, że oczekiwaliśmy wartości 1, a mamy 0.

Mogą to być również różniące się wiersze w tabeli:

Znak < wskazuje otrzymany rekord z tabeli #Actual, a znak > rekord przewidywany z tabeli #Expected. Widać, że różnią się wartością w kolumnie X.

Funkcja vs Procedura

W powyższych przykładach korzystaliśmy z funkcji, które zwracały tabelę lub jakąś wartość. Można również korzystać z procedur, które wykonują jakąś logikę, ale nic nie zwracają.

Czyszczenie bazy

Warto wspomnieć, że każdy test tSQLt jest domyślnie opakowany w transakcję, która wykonuje rollback po jego wykonaniu.

Podsumowanie

Jak widać, przetestowanie logiki, którą mamy w bazie danych (w postaci funkcji czy procedur) nie jest wcale takie trudne. Oczywiście czasem może to wymagać drobnych zmian, ale po stronie backendu czy frontendu jest dokładnie tak samo. Tworzenie testów tSQLt jest podobne do pisania testów jednostkowych – tyle, że zamiast kodu np. w C# piszemy kod SQL. W związku z tym warto pisać testy tSQLt, które sprawdzą poprawność logiki – jeśli mamy takową po stronie bazy danych.


Post powstał pod patronatem firmy, w której aktualnie pracuję: Objectivity.

Grafikę tytułową zaprojektował niezastąpiony zespół designu!


Podoba Ci się to, co tworzę? Chcesz dostawać informacje o:
– wydarzeniach, które organizuję lub wspieram (np. konferencje, meetupy, webinary)
– inicjatywach, które organizuję lub wspieram (np. GeekWeekWro, DevAdventCalendar)
– moich prelekcjach, kursach i szkoleniach
– wyróżnionych artykułach z mojego bloga

0% SPAMu, 100% informacji! Krótko i na temat.

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Wyloguj /  Zmień )

Zdjęcie na Google

Komentujesz korzystając z konta Google. Wyloguj /  Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Wyloguj /  Zmień )

Zdjęcie na Facebooku

Komentujesz korzystając z konta Facebook. Wyloguj /  Zmień )

Połączenie z %s