tSQLt – testy jednostkowe bazy danych w SQL Server

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:

1

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:

2

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:

3

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:

4

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.

Trace vs Debug

W .NET istnieją dwie przydatne klasy do logowania wszelkich informacji: Trace oraz Debug. Często różnica nie jest jasna oraz klasy są ze sobą mylone. Co się dokładnie dzieje, gdy napiszemy następujący fragment kodu?

   Trace.WriteLine("Trace test");
   Debug.WriteLine("Debug test");

Efekt wydaje się podobny, w okienku debug zobaczymy wykonane logi:

1

Najlepiej zajrzeć do źródeł powyższych klas. Debug.WriteLine wygląda następująco:

   [System.Diagnostics.Conditional("DEBUG")]        
   public static void WriteLine(string message) 
   {
        TraceInternal.WriteLine(message);
   }

Z kolei Trace.WriteLine:

        [System.Diagnostics.Conditional("TRACE")]
        public static void WriteLine(string message) {
            TraceInternal.WriteLine(message);
        }

Pozostałe metody Trace oraz Debug wyglądają analogicznie. Wszystkie z nich (zarówno Debug jak i Trace) korzystają z TraceInternal. Jedyna różnica to atrybut Conditional. Nie trudno domyślić się, że Debug będzie widoczny tylko w trybie Debug. Jeśli przejdziemy do Release, Debug.WriteLine nie zostanie skompilowany czyli nie zobaczymy nic na wyjściu. Z kolei symbol “TRACE” jest domyślnie zawsze włączany w proces kompilacji, co możemy sprawdzić we właściwościach projektu:

2

Warto również zauważyć, że za pomocą obydwu klas można zapisywać logi w dowolnych miejscach, również w plikach czy bazach danych. Wystarczy odpowiednio skonfigurować Listeners:

ConsoleTraceListener myWriter = new ConsoleTraceListener();
Trace.Listeners.Add(myWriter);

AKKA.NET – zdalni aktorzy

Jak wspomniałem w jednym z wcześniejszych już wpisów, nie ma znaczenia, gdzie aktor jest zlokalizowany. Dzięki AKKA.NET jest to szczegół  konfiguracyjny. Jeśli pewnego dnia, stwierdzimy, że wykonywanie obliczeń na jednym komputerze nie wystarcza, wtedy po prostu  zmieniamy konfigurację, aby hostować danego aktora gdzieś indziej. Framework zadba o komunikację (TCP) między węzłami znajdującymi się w innych sieciach. W ten sposób, bardzo łatwo jest skalować logikę w następujący sposób: wątek->proces->komputer->sieć komputerów.

W dokumentacji znajdziemy szczegóły, ale moim zdaniem brakuje tam prostego przykładu polegającego na wysłaniu wiadomości z węzła A do B.

Zacznijmy od stworzenia struktury projektu. Warto stworzyć jedną bibliotekę, która będzie zawierać wyłącznie kod aktorów. W naszym przypadku będzie to EchoServerActor:

    public class EchoServerActor : ReceiveActor
    {
        public EchoServerActor()
        {
            Receive<EchoMsg>(msg =>
            {
                Sender.Tell($"Server:{AppDomain.CurrentDomain.FriendlyName},{msg.Text}");
            });
        }
    }

    public class EchoMsg
    {
        public string Text { get; set; }
    }

Jak widać, kod nie różni się niczym od implementacji “lokalnych” aktorów. Po odebraniu wiadomości, wyświetlamy na ekranie nazwę domeny oraz przesłaną wiadomość. Wyświetlenie aktualnej domeny będzie pomocne, w analizie gdzie kod został tak naprawdę wykonany. EchoServeActor będzie służył nam jako “serwer”. W praktyce, komunikacja odbywa się klient-klient i nie należy projektować systemów w sposób scentralizowany. Awaria jednego aktora nie powinna spowodować paraliżu całego systemu. W poście jednak, chcemy napisać jak najprostszy fragment kodu, stąd te uproszczenie.

Jako “klient” posłuży nam następująca klasa:

    public class EchoReceiverActor : ReceiveActor
    {
        public EchoReceiverActor()
        {
            Receive<string>(msg =>
            {
                Console.WriteLine($"Received on {AppDomain.CurrentDomain.FriendlyName}:{msg}");
            });

            Receive<EchoMsg>(msg =>
            {
                var remoteActor = Context.ActorSelection("akka.tcp://Server@localhost:8081/user/EchoServerActor");
                remoteActor.Tell(msg);
            });
        }
    }

Po odebraniu wiadomości “EchoMsg” uzyskujemy referencję za pomocą ścieżki (więcej szczegółów tutaj). W praktyce, nie chcemy umieszczać w kodzie aktora, informacji o jego lokalizacji – powinno to mieć miejsce np. w pliku konfiguracyjnym. Powyższy kod jest zatem złapaniem bardzo ważnej zasady o neutralności fizycznej lokalizacji aktora. W ramach wpisu chcę napisać kod jednak jak najprościej tylko można. W każdym razie, po odebraniu EchoMsg, EchoReceiverActor wyśle wiadomość do zdalnego aktora.

Z kolei jeśli przyjdzie wiadomość w formie czystego tekstu (string), wtedy wyświetlamy ją. Nie trudno domyślić się, że w naszym przykładzie taka wiadomość będzie pochodzić od zdalnego aktora.

Innymi słowy, najpierw po stronie klienta wysyłamy EchoMsg do EchoReceiverActor. Aktor z kolei prześle tą wiadomość zdalnie do EchoServerActor, który z kolei odpowie tekstem do EchoReceiverActor.

Przejdźmy teraz do konfiguracji “serwera”:

         var config = ConfigurationFactory.ParseString(@"
akka {  
    actor {
        provider = ""Akka.Remote.RemoteActorRefProvider, Akka.Remote""
    }
    remote {
        helios.tcp {
            transport-class = ""Akka.Remote.Transport.Helios.HeliosTcpTransport, Akka.Remote""
            applied-adapters = []
            transport-protocol = tcp
            port = 8081
            hostname = localhost
        }
    }
}
");

        using (var system = ActorSystem.Create("Server", config))
        {
           system.ActorOf<EchoServerActor>("EchoServerActor");

           Console.ReadLine();
        }

Konfiguracja w prawdziwych projektach umieszczana jest w App\Web.Config, tak aby mnożna ją było zmienić bez potrzeby rekompilacji. Widzimy, że serwer będzie nasłuchiwać na porcie 8081. Zostanie również stworzony system o nazwie “Server” oraz pojedynczy aktor o nazwie “EchoServerActor”.

Klient z kolei wygląda następująco:

            Console.ReadLine();

            var config = ConfigurationFactory.ParseString(@"
akka {  
    actor {
        provider = ""Akka.Remote.RemoteActorRefProvider, Akka.Remote""
    }
    remote {
        helios.tcp {
            transport-class = ""Akka.Remote.Transport.Helios.HeliosTcpTransport, Akka.Remote""
		    applied-adapters = []
		    transport-protocol = tcp
		    port = 0
		    hostname = localhost
        }
    }
}
");

    using (var system = ActorSystem.Create("Client", config))
    {
         var receiver = system.ActorOf(Props.Create<EchoReceiverActor>());

         receiver.Tell(new EchoMsg { Text = "Hello world" });
         Console.ReadLine();
    }

W przypadku “klienta” nie musimy określać portu. Jak wiemy, wszystko działa na zasadzie klient-klient, ale w kodzie sami nie musimy nic samemu wysyłać do klienta – stąd nie ma to znaczenia. Wartość zero oznacza, że port zostanie wybrany automatycznie. Następnie wysyłamy wiadomośc EchoMsg do aktora EchoReceiverActor. Jak wiemy z powyższego kodu, EchoReceiverActor jest hostowany w systemie “Client”. Następnie wyśle on wiadomość do zdalnego systemu “Server”.

Po uruchomieniu serwera, zobaczymy, że faktycznie system Server nasłuchuje na 8081:

1

Z kolei po uruchomieniu klienta, zobaczymy, że nasłuchuje on na automatycznie wybranym porcie 8591:

2

Z powyższego screenu również widać, że wiadomość została z powrotem przesłana do klienta. Dzięki wyświetleniu nazwy domeny, widzimy kiedy wiadomość została odebrana przez serwer (echo) i przesłana do kienta.

DbUp – aktualizacja baz danych

DbUp jest prostą biblioteką, przeznaczoną do aktualizacji baz danych. Jeśli korzystamy z ORM, zwykle wtedy dany framework posiada już analogiczną funkcjonalność. Na przykład, EntityFramework wspiera migrację, która umożliwia automatyczną aktualizacje tabel i procedur.

Z drugiej strony, nie zawsze jest potrzeba korzystania z tak ciężkich rozwiązań. Bardzo popularną biblioteką do odczytu danych z baz jest Dapper.  Niestety nie posiada on mechanizmu podobnego do Entity Framework migrations.

W takich przypadkach, DbUp jest bardzo przydatny. Aktualnie wspiera kilka baz danych m.in. SQL Server, MySQL i FireBird. Idea jest bardzo prosta – wystarczy w projekcie stworzyć folder z listą skryptów do wykonania.  Załóżmy, że mamy 2 skrypty :

CREATE TABLE [dbo].Persons
(
   [Id] INT NOT NULL PRIMARY KEY, 
    [FirstName] NCHAR(10) NULL, 
    [LastName] NCHAR(10) NULL
)

CREATE TABLE [dbo].Articles
(
	[Id] INT NOT NULL PRIMARY KEY, 
    [Title] NCHAR(10) NULL, 
    [Content] NCHAR(500) NULL
)

Każdy ze skryptów powinien zostać zapisany w osobnym pliku, np.:

1

Następnie należy ustawić Build Action na Embedded Resource:

2
Oczywiście jeśli chcemy, aby DbUp wykonał powyższe skrypty, należy zainstalować odpowiedni pakiet za pomocą:
Install-Package DbUp

W celu uruchomienia aktualizacji wystarczy:

            var connectionString = @"Server=DESKTOP-IK0BKOF\SQLEXPRESS;database=testdb; Trusted_connection=true";

            var upgrader =
                DeployChanges.To
                    .SqlDatabase(connectionString)
                    .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly())
                    .LogToConsole()
                    .Build();

            var result = upgrader.PerformUpgrade();

            if (!result.Successful)
                Console.WriteLine(result.Error);

DbUp automatycznie połączy się z bazą i wykona skrypt po skrypcie:

3

Jeśli ponownie spróbujemy wykonać aktualizację, wtedy DbUp zorientuje się, że nie trzeba już żadnych skryptów wykonywać:

4

Załóżmy, że po pewnym czasie dodaliśmy kolejną tabelę:

CREATE TABLE [dbo].Contacts
(
	[Id] INT NOT NULL PRIMARY KEY, 
    [FirstName] NCHAR(10) NULL, 
    [LastName] NCHAR(10) NULL
)

5
Po uruchomieniu, DbUp doda wyłącznie nową tabelę (Contacts):

6

Pozostaje wyjaśnij, w jaki sposób DbUp wie, które skrypty należy wykonywać? Zaglądając do bazy danych, zobaczymy, że nowa tabela “SchemaVersions” została utworzona. Wykonując Select na niej, przekonamy się,  że DbUp przechowuje listę skryptów, które zostały wykonane:

7

Oprócz aplikacji konsolowej, można korzystać również ze skryptów PowerShell (szczegóły w dokumentacji). Innym, częstym podejściem jest wykonywanie aktualizacji przed uruchomieniem aplikacji.

Jak widać, DbUp to bardzo proste narzędzie, wykonujące po prostu listę skryptów. Przydaje się jednak w częstych releasach,  gdzie należy pamiętać historię zmian w DB.

Exceptionless – centralne przechowywanie logów

Standardowo w aplikacjach używa się takich frameworków jak log4net czy nlog w celu logowania kluczowych informacji jak i  wyjątków. Bardzo szybko staje się jasne (szczególnie w przypadku micro-serwisów), że analizowanie plików tekstowych z logami jest czasochłonne.

Z tego względu dobrze mieć centralne repozytorium logów i łatwy do niego dostęp. Większość rozwiązań umożliwia dzisiaj indeksowanie oraz łatwe przeszukiwanie danych. Jednym z bardziej znanych produktów jest splunk. Umożliwia agregację logów z różnych źródeł i szybkie wyszukiwanie za pomocą zaawansowanych zapytań.  Za pomocą splunk łatwe jest wygenerowanie raportów np. pokazujących jaki typ wyjątku był najczęściej wyrzucany w określonym czasie.  Niestety rozwiązanie jest dość drogie.

Exceptionless nie jest alternatywą dla splunk. Jeśli jednak potrzebujemy wyłącznie centralnego repozytorium logów z możliwością wykonania prostych zapytań, wtedy ExceptionLess jest tanią lub nawet darmową alternatywą. ExceptionLess jest open-source, można zatem pobrać kod źródłowy i bez problemu hostować to na wewnętrznym serwerze. Możliwe jest również hostowanie na ich serwerach i wtedy cena zależy od liczby projektów i innych parametrów.  Dla testów jednak (jeden projekt), można stworzyć darmowe konto.

Spróbujmy zatem napisać proste demo. Zaczynamy od stworzenia darmowego konta na https://be.exceptionless.io/signup. Następnie tworzymy nowy projekt:

1

Po wybraniu typu projektu (ASP.NET MVC), dostaniemy informacje, co należy wykonać dalej:

2

Jak widzimy, musimy zainstalować odpowiedni pakiet za pomocą Install-Package Exceptionless.Mvc. Ze screenu wynika również, że musimy ustawić APIKey reprezentujący projekt, który właśnie stworzyliśmy. Wystarczy ustawić odpowiedni element w Web.config

  <exceptionless apiKey="API_KEY_HERE" />

I to naprawdę wszystko! Przyglądając się Web.config zobaczymy również, że dodatkowy moduł został dołączony:

  <system.webServer>
    <validation validateIntegratedModeConfiguration="false" />
    <modules>
      <add name="ExceptionlessModule" type="Exceptionless.Mvc.ExceptionlessModule, Exceptionless.Mvc" />
    </modules>
  </system.webServer>

Dzięki temu, nieobsłużone wyjątki zostaną zapisane automatycznie w bazie ExceptionLess. Spróbujmy zatem wyrzucić jakiś wyjątek w kontrolerze:

        public ActionResult Index()
        {
            throw new ArgumentNullException("Any message");

            return View();
        }

Przechodząc teraz do zakładki “Exceptions”, zobaczymy, że wyjątek został przechwycony przez ExceptionLess i zapisany:

3

Klikając na wpisie, przejdziemy do szczegółów:

4

U góry aplikacji widzimy również opcje wyszukiwania, która wspiera m.in. znak “*”:

5

Dane są indeksowane, zatem wyszukiwanie jest szybkie.

W ustawieniach konta, możemy również skonfigurować powiadomienia o np. krytycznych wyjątkach lub otrzymywać codzienny raport. ExceptionLess nie służy wyłącznie do przechowywania wyjątków, ale do logowania jakichkolwiek zdarzeń. Do każdego zdarzenia można dołączać tag, dzięki temu exceptionless jest przydatny w monitorowaniu działania aplikacji. Za pomocą raportów, w łatwy sposób możemy dowiedzieć się, które elementy aplikacji są najczęściej wykorzystywane.

Blue Green Release

Proces releasowania oprogramowania jest chyba jednym z ważniejszych wyzwać w czasach oprogramowania bazującego na usługach. 20 lat temu,  oprogramowanie zwykle było sprzedawane na nośnikach danych i częstotliwość wydawania nowych wersji była bardzo niska (np. dwie aktualizacje na rok).

Myślę, że 5-10 lat temu, standardem stało się releasowanie co iterację, np. raz na dwa tygodnie.  Od kilku lat jednak, wiele firm wdraża zmiany kilkakrotnie dziennie i taki proces można dopiero nazwać “continuous deployment”.

Temat bardzo szeroki, ale dzisiaj chciałbym przyjrzeć się tylko wzorcowi blue-green. Wiemy, że wdrożenie nowego kodu jest bardzo ryzykowne, nawet gdy mamy dobre pokrycie testami. W praktyce, istnieje wiele innych problemów takich jak konfiguracja zapory ogniowej czy odpowiednie uprawnienia.  Skoro chcemy wdrażać często, nie jesteśmy w stanie sobie pozwolić na jakąkolwiek przerwę w działaniu systemu.

Wzorzec blue-green zakłada, że mamy do dyspozycji przynajmniej dwa serwery: blue oraz green. W dowolnym momencie tylko jeden z nich jest aktywny oraz posiada najnowszą wersję. Ponadto do dyspozycji powinniśmy mieć load balancer albo router, który będzie przełączał przekierowania do serwera blue albo green. Na początku oczywiście, przy pierwszym wdrożeniu nie ma znaczenia, który z serwerów wybierzemy. Sytuacja zatem może wyglądać następująco:

1

Blue zawiera pierwszą wersje kodu. Nie ma tu nic specjalnego. Załóżmy jednak, że mamy nową wersję (v2) i chcemy w bezpieczny sposób ją wdrożyć.  Do dyspozycji mamy zapasowy serwer – w tym przypadku green. Umieszczamy tam zatem nową wersję:

2

Mamy teraz dwie wersje systemu, ale użytkownicy wciąż są przekierowani do v1. Mamy teraz czas na sprawdzenie czy nowy kod nie powoduje żadnych problemów. Oba węzły to serwery produkcyjne, zatem mają dostęp do tych samych zasobów. Jeśli jesteśmy pewni, że wszystko działa, zmieniamy konfigurację balancera, aby przekierowywał ruch do serwera zielonego:

3

Jeśli stwierdzimy, że nie wszystko jednak działa tak jak należy i np. musimy dokonać rollback’u, wtedy sewer niebieski wciąż zawiera poprzednią wersję. Wystarczy ponownie zmienić przekierowanie na balancerze i ruch będzie przekierowywany do V1.

Kolejne wdrożenia polegają na tej samej zasadzie. Wersja v3 będzie zainstalowana na serwerze z poprzednią wersją, czyli serwerze niebieskim w tym przypadku:

4

Innymi słowy, po każdym wdrożeniu, balancer będzie zmieniany tak, aby na drugi węzeł wskazywać. W dowolnym momencie, jeden z serwerów będzie zawierał aktualną wersję, a drugi poprzednią. W przypadku wdrażania nowego kodu, serwer z poprzednią wersją można określić jako staging albo preprod. Jak widać,  nie ma znaczenia, który serwer to “blue”, a który “green”. Powyższy wzorzec ma wiele nazw i myślę, że nazwa może być trochę myląca – “green” nie znaczy, że jest to zawsze aktywny serwer. Aktywny serwera, co każde wdrożenie jest przełączany między niebieskim, a zielonym węzłem.

Podsumowując dzięki blue\green deployment zyskujemy:

  • zero downtime deployment – aplikacja zawsze będzie dostępna dla użytkowników.
  • Rollback – wrócenie do poprzedniej wersji sprowadza się wyłącznie do zmiany konfiguracji routera\balancera.
  • Bezpieczne i bezstresowe wdrożenia.

Visual Studio 2015 – okno C# interactive

W Visual Studio 2015 Update 1 można znaleźć nowe okno, a mianowicie C# interactive. Często chcemy przetestować kawałek kodu w izolacji od aplikacji nad którą aktualnie pracujemy. Zwykle korzystałem z LinqPad, ale posiadanie takiej funkcji od razu w Visual Studio jest wygodniejsze. Wystarczy z menu głównego wybrać View->Other Windows->C# Interactive:

1

Następnie możemy wykonać jakikolwiek kod, np.:

2

Jak widzimy, jeśli nie zakończymy linii kodu średnikiem, wtedy zawartość zostanie po prostu wyświetlona. Wpisując #help dostaniemy opis podstawowych instrukcji\operacji:

3

Ponadto, okno wspiera bardzo dobrze Intellisense:

4

Dobrze, że można również deklarować przestrzenie nazw, co nie jest może bardzo intuicyjne, ale za to przydaje się:

5

Nic nie stoi na przeszkodzie, aby zadeklarować własną klasę:

6

Możliwe jest również załadowanie zewnętrznych bibliotek. Jak widać, jest to bardzo przydatny notatnik C#, a do tego jak już piszemy kod w Visual Studuio, odpalenie okna jest dużo szybsze niż załadowanie LinqPad czy stworzenie aplikacji konsolowej w celu przetestowania czegoś.

HTTP – testy wydajnościowe w JMeter

JMeter jest darmową aplikacją bardzo przydatną podczas  wykonywania “load testing”. Interfejs użytkownika co prawda jest bardzo mało intuicyjny, ale po pewnym czasie można przyzwyczaić się. Aplikacja, po uruchomieniu prezentuje się następująco:
1

Oczywiście na oficjalnej stronie można znaleźć pełną dokumentację, więc moim celem nie jest opisywanie każdego elementu. Jako próbkę, po prostu spróbujmy stworzyć test, który będzie łączył się z jakąś stroną (np. Google) i symulował zapytania wykonywane przez użytkownika. W tym celu, najpierw dodajemy tzw. Thread Group:

2

Thread group służy do symulacji ruchu. Po dodaniu elementu, będziemy mogli skonfigurować częstotliwość zapytań:

3

Number of threads oraz ramp-up period to chyba najważniejsze parametry. Pierwszy z nich to liczba użytkowników (wątków). Drugi z kolei to czas w którym zapytania mają zostać wykonane. Jeśli zatem ustalimy Number of threads na 1000, a ramp-up period na 30, wtedy zostanie wykonanych 1000 akcji (np. zapytań) w ciągu 30 sekund.

Kolejny przydatny element to “Sampler”. W naszym przypadku skorzystamy z HTTP Request:

4

Po dodaniu elementu zobaczymy,  że możemy skonfigurować nazwę serwera, jak i przekazywane parametry. Załóżmy, że chcemy wykonać proste zapytanie GET do Google:

5

Z powyższego screena widać, że praktycznie każdą składową można skonfigurować. Nie ma zatem problemu z wysłaniem POST z bardziej zaawansowanymi parametrami.

Po wykonaniu zapytania, warto coś zrobić z dostarczoną odpowiedzią. Pierwszym krokiem może być wyświetlenie wyników np. w formie drzewa:

6

Przydatne jest również wygenerowanie Summary Report:

7

Niezbędna jest również walidacja odpowiedzi. Musimy wiedzieć, kiedy nasz test powinien zakończyć się sukcesem albo błędem. Z tego względu dodajmy asercję odpowiedzi:

8

Załóżmy, że jeśli odpowiedź przyjdzie z kodem HTTP 200, wtedy test zakończyć powinien się sukcesem:

9

Mamy już wszystkie elementy. Wystarczy, że skonfigurujemy Thread Group (liczbę użytkowników oraz ramp-up) i możemy uruchomić testy. Po uruchomieniu i wykonaniu testów, przejdźmy do dodanego wcześniej Results Tree:

10

Każdą odpowiedź możemy zobaczyć, zarówno w formie dokumentu HTML jak i czystego tekstu. Z kolei Summary Report zawiera informacje takie jak średni czas wykonania zapytania czy procent błędów:

11

Z powyższego screena wiemy, że wszystkie zapytania zakończyły się sukcesem, a średni czas wykonania to 481 milisekund.

Oczywiście to tylko namiastka możliwości JMeter. W praktyce będziemy musieli użyć więcej elementów. Zwykle serwisy mają mechanizm autoryzacji i wtedy będziemy musieli przechowywać ciasteczka pomiędzy różnymi zapytaniami. Zamiast wysyłać pojedyncze zapytanie, być możemy będziemy chcieli symulować pewną sekwencję zapytań.

Myślę, że przydatny skrót to CTRL+E, który powoduje wykasowanie aktualnych raportów.

Inną przydatną opcją jest możliwość nagrywania sekwencji za pomocą przeglądarki. Wtedy JMeter będzie służył jako serwer proxy i wszelkie zapytania wykonane przez użytkownika w przeglądarce, będą tworzyć odpowiednie elementy w JMeter.

HTTP 2.0 Server Push

Dzisiaj kolejny element HTTP 2.0, tym razem wymagający zmiany kodu po stronie aplikacji. Tak jak już z wszystkimi opisanymi wcześniej zmianami, ma to na celu zmniejszenie opóźnienia (latency) wynikającego z liczby zapytań.

Doskonale wiemy, że każda strona ma referencje do innych zasobów takich jak CSS czy pliki graficzne. Wcześniej zajęliśmy się już HTTP Multiplexing, który znacząco niweluje problem.

W jednym z poprzednich wpisów pokazałem również jak w HTTP 1.1 programiści radzili sobie z zasobami. Częstym obejściem, było umieszczenie plików graficznych inline. Powodowało to, że jak tylko plik HTML lub CSS został ściągnięty, plik graficzny stawał się od razu dostępny (ponieważ był częścią tego samego pliku).

Z tej techniki tak naprawdę wywodzi się Server Push. Wiemy, że w celu wyświetlenia strony “A”, musimy również wysłać zasób “B”. Dlaczego zatem nie wysłać zasobu B od razu zaraz po A? Inline jest pewną implementacją tego, ale jak wiemy dość kłopotliwą. Server push pozwala z kolei, “wepchnąć” dowolny zasób (nie koniecznie plik graficzny) w strumień danych płynący od serwera do klienta. Oczywiście serwer (np. IIS), nie będzie znał relacji w poszczególnych aplikacjach między zasobami. Wynika z tego, że to nasza rola (ewentualnie framework’a)  jest poinformować o tym klienta.

Służą do tego tzw. “Push Promise”, czyli obietnice klienta odnoście relacji między zasobami. Jeśli projektując system wiemy, że wyświetlając stronę A, zaraz będzie załadowana  biblioteka jQuery,  wtedy wydajemy obietnice. Push promise to prosta metoda, która zwraca listę zależności. Przeglądarka następnie przeczyta taką listę obietnic i stwierdzi czy warto je akceptować. Czasami te zasoby mogą już znajdować się w cache przeglądarki, wtedy należy przerwać taką transmisje. W przeciwnym wypadku przeglądarka zaakceptuje je i w praktyce zostaną one odebrane zaraz po przesłaniu strony – bez zbędnych opóźnień.

W ASP.NET dostępna jest już metoda PushPromise:

public void PushPromise(
	string path,
	string method,
	NameValueCollection headers
)

Drugie przeładowanie jest nieco prostsze w użyciu:

public void PushPromise(
	string path
)

Warto zauważyć, że w taki sposób możemy kontrolować czas życia obiektu. W przypadku inline nie było takiej możliwości – zawsze zasób był przesyłany. Jeśli korzystamy z ServerPush, zasób będący w cache przeglądarki zostanie anulowany i nie przesyłany. Podobnie za pomocą parametru headers (nagłówki), możemy dowolnie aktualizować lub usuwać dany zasób z cache (nagłówek Cache-Control).

Z powyższego opisu wynika jeszcze jeden wniosek – zwracana obietnica zasobu musi nadawać się do cachowania. Przeglądarka ma prawo i będzie wspomniany zasób wykorzystywać w innych podstronach. Tak samo, projektując stronę, nie powinniśmy polegać wyłącznie na Server Push. To jedynie usprawnienie w wydajności, a nie element nowej architektury – wiele przeglądarek wciąż tego nie wspiera.

Server Push to wyłącznie mechanizm uprzedzenia przeglądarki i dostarczania zależności wraz z plikiem głównym. Nie ma to nic wspólnego z Web Sockets i innymi technikami komunikacja dwustronnej.

HTTP 2.0 w IIS (ASP.NET)

IIS w Windows 10 wspiera już od jakiegoś czasu nową wersję protokołu.  W zasadzie prawdopodobnie nic nie musimy robić, jeśli posiadamy prawidłową wersję IIS. Oczywiście użytkownicy muszą posiadać również odpowiednią wersję przeglądarki internetowej. Ich kompatybilność można sprawdzić tutaj. Jak widać, Edge, Chrome, Firefox, Opera czy iOS Safari radzą sobie najlepiej. W przypadku IE, najnowsza wersja wspiera HTTP 2.0 tylko częściowo.

Jeśli jeszcze nie zainstalowaliśmy IIS na Windows 10, wtedy przejdźmy najpierw do “Turn Windows features on or off”, a następnie zaznaczamy Internet Information Service:

1

Aktualnie IIS wspiera wyłącznie HTTPS (TLS). Oznacza to, że jeśli chcemy użyć nieszyfrowanego połączenia to HTTP 1.1 będzie wciąż używany. Dodajmy więc kolejny binding HTTPS:

2

I to wszystko co musimy zrobić… Jak widać,  nic specjalnego nie należy konfigurować. Wystarczy odpowiednia wersja IIS oraz przeglądarki po stronie klienta. Odpalmy zatem stronę zarówno z HTTP, jak i HTTPS. Spodziewamy się, że nieszyfrowane połączenie wciąż będzie HTTP 1.1:

3

W przypadku HTTPS,HTTP 2.0 będzie użyty: 4

Skrót H2 oznacza oczywiście HTTP 2.0

W przyszłym poście opiszę kolejny element HTTP 2.0, w tym przypadku Server Push, który wymaga pewnych modyfikacji w aplikacji ASP.NET. W celu przetestowania tej aplikacji, potrzebny będzie zarówno serwer IIS wspierający HTTP 2.0, jak i odpowiednia przeglądarka (np. Chrome).