Selenium czyli jak zautomatyzować proces końcowego testowania aplikacji webowej.

Siemanko

W dzisiejszym poście pragnę nieco przybliżyć temat automatycznych testów end2end i pokazać przykładowy teścik.

Jak wszyscy pracujący m.in. przy webie wiemy, że ostateczne testy wykonują zazwyczaj ludzie klikając myszką i stukając w klawiaturę. Bez względu na fakt pokrycia kodu testami jednostkowymi, które przed release’em mienią się na zielono. Jeśli Twoim lub Twojej firmy mottem nie jest hasło „Testujemy na produkcji!” to na pewno Wasz dział testerski (o ile go macie) lub Wy sami musicie klikać jak małpka te same user stories i interpretować co się dzieje. Jeśli release’y są np. co pół roku to spoko – duże wydarzenie więc wszystkie ręce na pokład testerskiego okrętu. Jednak jeśli release’y są co np. tydzień (hot fixy itp.) to aby być pewnym, że zmiany w nadchodzącym release’ie nie popsuły niczego w aktualnym trzeba wszystko od początku do końca przeklikać co przy zaawansowanych aplikacjach może zająć b. długo. O kosztach takiego postępowania (pieniężnych i emocjonalnych) chyba nie muszę pisać. Czy jest coś co możemy z tym zrobić? Owszem, jest.

User story w teście jednostkowym

Selenium umożliwia przetestowanie działania aplikacji internetowej z poziomu testu jednostkowego. Udostępnia ono „drivery” do większości przeglądarek inetrnetowych i tak jak „Karma” (jeśli pracujesz przy froncie to wiesz o co kaman) otwiera okno przeglądarki i klika, wpisuje, przesuwa, robi dokładnie to co zakodujesz w teście, a że jest SDK dla .NETu to jesze wynik wyświetli Ci się na czerwono lub zielono w Test Explorer’ze w VS.

Test case

Na pokaz zróbmy taki test SEO w google. Chcemy sprawdzić czy po wpisaniu „Selenium” w google główna strona selenium będzie pierwszym wynikiem.

test1

Klikamy Run Test. Selenium otworzy przeglądarkę (w tym przypadku Chrome), wpisze „Selenium” i kliknie szukaj. Następnie pobierze adres strony pierwszego wyniku i zostanie on przyrównany do oczekiwanego.

ezgif-3-2306a82b10

Ok, poszło. Ale my jesteśmy programistami i nie mamy takich pomysłów co można dziwnego zrobić z aplikacją jak testerzy. Wspomniani natomiast patrząc na ciąg tych znaczków w XPath też mogą nie kumać czaczy i Oni kolejnych testów nie napiszą. Dlatego w tym momencie do akcji wchodzi POM (nie, nie powolny objazd miasta) – Page Object Model.

Page Object Model

bumblebee_logo

To pattern umożliwiający testerom nie posiadającym wiedzy programistycznej na skuteczne i bezproblemowe pisanie testów w Selenium. Ten pattern można stosować dzięki BumbleBee. Udostępnia ona wiele implementacji tagów html’owych, które posiadają user, a raczej tester friendly API.

POM dla głownej strony wyszukiwania.

page1

POM dla strony z wynikami wyszukiwania

page2

Za POM odpowiedzialny jest programista i ma w nim dostarczyć testerowi dokładnie to co user może kliknąć na stronie. W naszym uproszczonym przypadku wyłuskałem przy użyciu BumbleBee tylko pole wyszukiwania, submit button oraz pierwszą stronę wyników. Teraz tester, gdy ma dostarczony taki POM, po krótkim przeszkoleniu może pisać test case’y jako testy jednostkowe bo pole wyszukiwania ma metodę AppendText(), a button ma metodę Submit(). Trzeba tylko znać angielski aby sie połapać.

test2

Podsumowanie

To oczywiście tylko drobna zajawka, gdyż sam całkiem niedawno dopiero się dowiedziałem o istnieniu tego cuda. Po więcej odsyłam do Selenium i BumbleBee. Kodzik stworzony na potrzeby posta jak zwykle na moim github’ie

Pjona!

Reklamy

Jak zmapować JObject do konkretnej klasy za pomocą AutoMappera.

Siemanko

Wykorzystując generyczne możliwości C# oraz bibliotekę AutoMapper do mapowania modeli na podstawie konwencji, można zaimplementować np. operacje CRUDowe dla wielu modeli bez potrzeby powtarzania kodu konkretnej implementacji dla konkretnego modelu. Ostatnio, implementując coś w rodzaju kolejki eventów oraz ich obsługi, musiałem się na chwilę zatrzymać przy mapowaniu za pomocą AutoMappera zdeserializowanego (Newtonsoft.Json) obiektu do obiektu konkretnej klasy.

Zwykłe CreateMap<> nie działa.

Na początku stworzyłem najprostszy profil mapowania.

    public class SomeClassProfile : Profile {
        public SomeClassProfile() {
            CreateMap<JObject, SomeClass>();
        }
    }

To jednak nie zadziała i efektem mapowania będzie obiekt z default’owymi wartościami propert.

notworking

Pewnie się zastanawiasz czemu od razu nie deserializuje do konkretnej klasy – otóż w realnym problemie, który rozwiązywałem przy deserializacji jeszcze nie wiedziałem jakiego typu jest to obiekt.

Trochę poguglałem i okazało się, że trzeba swtorzyć niecio bardziej osobliwy mapping z wykorzystaniem JsonSerializer

    public class SomeClassProfile : Profile {
        public SomeClassProfile() {
            CreateMap<JObject, SomeClass>().ConstructUsing((jObject) => {
                var someClassObject = new SomeClass();

                var serializer = new JsonSerializer();
                serializer.Populate(jObject.CreateReader(), someClassObject);

                return someClassObject;
            });
        }
    }

Teraz wszystko bangla jak należy.

working

Wrzucam to tutaj głównie z myślą o sobie, abym w przyszłości mógł zrobić szybkiego copy-paste’a jeśli mi się zapomni, ale nie wykluczone, że przyda się to także i Tobie.

Pjona!

Xamarin.Android na trzy sposoby – Code Behind, MVP i MVVM.

Siemanko.

W dzisiejszym poście trzy sposoby pisania natywnej aplikacji dla androida w Xamarin.Android. Trzy zupełnie identyczne aplikacje pod względem funkcjonalnośći i UI jednak zupełnie inne pod względem architektury kodu i struktury projeku. Sama aplikacja banalnie prosta bo wpis ten traktuje o podejściu do pisania aniżeli do pisania konkretnych funkcjonalności.

Screenshot_2017-05-01-10-48-54

Tak. Jeden guzik i pole tekstowe do wyświetlania liczby dotychczasowych kliknięć.

Jedziemy.

Code Behind

Tu obsługę zdarzeń na widoku oraz logikę biznesową piszemy w bezpośrednio w Activity.cs.

[Activity(Label = "AndroidCodeBehind", MainLauncher = true, Icon = "@drawable/icon")]
 public class MainActivity : Activity
 {
    private TextView _clicksCountTxt;
    private Button _helloWorldBtn;
    private int _clicksCount;

    protected override void OnCreate(Bundle bundle)
    {
       base.OnCreate(bundle);
       SetContentView(Resource.Layout.Main);
       SetView();
    }

    private void SetView()
    {
       _clicksCountTxt = FindViewById(Resource.Id.clicksCountTxt);

       _helloWorldBtn = FindViewById(Resource.Id.helloWorldBtn);
       _helloWorldBtn.Click += (s, e) =>
       {
          _clicksCount++;
          _clicksCountTxt.Text = _clicksCount.ToString();
       };
    }
 }

To jest najszybsze i najprostsze rozwiązanie lecz najmniej eleganckie i nieprofesjonalne. Przy dużej ilości zdarzeń na widoku i konieczności ich obsługi b. szybko zrobi się bałagan i przede wszystkim taki kod jest właściwie nietestowalny.

MVP

Wzorzecz Model View Presenter pozwala na odseparowanie warstwy prezentacji od logiki biznesowej. Widoki implementują interfejs, w którym są zadeklarowane wszystkie właściwości i eventy, a prezenter subskrybuje te zdarzenia i w odpowiedzi na nie ustawia konkretne property.

Interfejs widoku

public interface IMainActivityView
{
   event EventHandler ButtonClicked;
   int ClicksCount { get; set; }
}

MainActivity musi implementowac w.w. interfejs.

[Activity(Label = "AndroidMVP", MainLauncher = true, Icon = "@drawable/icon")]
 public class MainActivity : Activity, IMainActivityView
 {
    private TextView _clicksCountTxt;
    private Button _helloWorldBtn;
    private MainActivityPresenter _presenter;
    public event EventHandler ButtonClicked;

    public int ClicksCount
    {
       get => int.Parse(_clicksCountTxt.Text);
       set => _clicksCountTxt.Text = value.ToString();
    }

    protected override void OnCreate(Bundle bundle)
    {
      base.OnCreate(bundle);
      SetContentView(Resource.Layout.Main);
      SetView();
    }

   private void SetView()
   {
      _clicksCountTxt = FindViewById(Resource.Id.clicksCountTxt);
    
       _helloWorldBtn = FindViewById(Resource.Id.helloWorldBtn);
       _helloWorldBtn.Click += (s, e) =>
       {
          ButtonClicked?.Invoke(this, EventArgs.Empty);
       };

       _presenter = new MainActivityPresenter(this);
    }
 }

Zostaje jeszcze prezenter

public class MainActivityPresenter
 {
    private readonly IMainActivityView _view;

    public MainActivityPresenter(IMainActivityView view)
    {
       _view = view;
       _view.ButtonClicked += _view_ButtonClicked;
    }

    private void _view_ButtonClicked(object sender, EventArgs e)
    {
       _view.ClicksCount++;
    }
 }

Teraz widok tylko rzuca zdarzenie informujące o kliknięciu przycisku i nie jest w żaden sposób odpowiedzialny za to co ma się z tym wiązać. Całą logikę wykonuje prezenter i na koniec ustawia wyliczoną liczbą kliknięć. Taki kod jest już testowalny bo wystarczy, że napiszemy fake’ową implementację dla interfejsu widoku i już możemy testować działanie prezentera. Idąc dalej, taką aplikację mogą swobodnie programować dwie osoby – jedna UI, a druga logikę biznesową – wystarczy, że wspólnie uzgodnią kontrakt, który wprowadza intefejs widoku i w żaden sposób nie powinni sobie przeszkadzać na takim poziomie abstrakcji.

MVVM

Tutaj użycie patternu MvvM jest możliwe dzięki framework’owi MvvM Cross. Jest to na prawdę potężny framework dający olbrzymie możliwości oraz wygodę związaną z tzw. two way data binding. Tutaj kod naszego Activity jest najkrótszy.

[Activity(MainLauncher = true, Icon = "@drawable/icon")]
 public class MainView : MvxActivity
 {
    protected override void OnCreate(Bundle bundle)
    {
       base.OnCreate(bundle);
       SetContentView(Resource.Layout.Main);
    }
 }

Tak. Nic tu nie ma poza wskazaniem layout’u do wyrenderowania. Tym razem zmienił się nieco kod samego layout’u, a mianowicie zostały dodane do kontrolek atrybuty dzięki, który Mvvm Cross może zbindować własciwości bądź zdarzenia do naszego ViewModelu.

Dla buttona

local:MvxBind=”Click IncreaseCount”

Dla text view

local:MvxBind=”Text ClicksCount, Converter=StringToIntConverter”

Natomiast kod naszego ViewModelu wygląda tak:

public class MainViewModel : MvxViewModel
 {
    public MainViewModel()
    {
       ClicksCount = 0;
    }

    private int? _clicksCount;

    public int? ClicksCount
    {
       get => _clicksCount;
       set => SetProperty(ref _clicksCount, value);
    }

    private ICommand _increaseCount;

    public ICommand IncreaseCount => _increaseCount ?? (_increaseCount = new MvxCommand(Increase));

    private void Increase()
    {
       ClicksCount++;
    }
 }

W tym przypadku widok i view model nie wiedzą o sobie zupełnie nic – bo nie muszą!

Mvvm Cross to na prawdę kawał dobrego kodu jednakże w mojej ocenie jest to odejście od natywnego kodowania tzn. oczywiście nadal jest to aplikacja natywna jednak programista nie pracuje z czystym SDK Androidowym tylko z „nakładką” i przez to nie ma 100% kontroli nad tym co się dzieje pod maską.

Wszystkie trzy projekty wrzuciłem na mojego GitHuba.

Pjona!

RabbitMQ, .NET Core, Nancy Fx, MongoDb – przykład kolejkowania zdarzeń.

Siemanko.

Jak już wspominałem we wcześniejszych postach od pewnego czasu staram się zgłebiać wiedzę na temat systemów rozproszonych i podejścia DDD. Im głebiej w las tym bardziej się jaram i zarazem dostrzegam ułomności standardowego monolitycznego podejścia do budowania aplikacji z pseudo warstwami abstrakcji, które i tak w końcu zamieniają się w spaghetti code (niestety). W tym poście opiszę przykład asynchronicznej komunikacji między aplikacjami poprzez serwer RabbitMQ.

Założenia

Dwie osobne aplikacje .NET Core z wykorzystaniem Nancy FX (bo tak, ale może być Mvc). Pierwsza aplikacja po wejściu na „index” publikuje zdarzenie do kolejki RabbitMQ z DateTime.Now jako danymi (może być cokolowiek).

Druga aplikacja po uruchomieniu „rejestruje” się w kolejce RabbitMQ jako subskrybent i zapisuje każde dane z odczytanego zdarzenia (czyli DateTime.Now w moemencie publikacji). Po wejściu na jej „index” wyświetla listę wszystkich DateTime.Now z odczytanych zdarzeń.

Crew propgramu to to, że druga aplikacja wcale nie musi być uruchomiona aby pierwsza mogła działać i robić swoje (czyli w tym przypadku tylko publikować zdarzenia na każde wejście na „index”) i żeby nic nie zostało utracone.  W momencie kiedy druga aplikacja wystartuje odczyta wszystkie wiadomości z kolejki i zapisze je w swojej bazie danych.

rabbit1

Instalacja

  1. Pobrać i zainstalować Erlang (potrzebne do rabita) – http://www.erlang.org/downloads
  2. Pobrac i zainstalować RabbitMQ – https://www.rabbitmq.com/download.html
  3. Po instalacji Rabbita upewnić się czy masz ustawione wszystkie zmienne – https://www.rabbitmq.com/install-windows-manual.html
  4. Odpalić w konsoli – rabbitmq-plugins enable rabbitmq_management
  5. Odpalić w przeglądarce – http://localhost:15672
  6. Zalogować się – login: guest, hasło: guest

Jesli wszystko poszło dobrze to powinieneś zobaczyć panel zarządzania

2

Producent

To co musi zrobić producent to podłączyć się do kolejki (zostanie utworzona jeśli jej nie ma) i opublikować zdarzenie:

private readonly IModel channel;
private readonly IConnection connection;
private readonly string queueName;

public EventSender()
{
   queueName = "helloWorldQueue";
   var factory = new ConnectionFactory() { HostName = "localhost" };
   this.connection = factory.CreateConnection();
   this.channel = connection.CreateModel();
   this.channel.QueueDeclare(queue: queueName,
   durable: false,
   exclusive: false,
   autoDelete: false,
   arguments: null);
}

public void SendEvent(string message)
{
   var body = Encoding.UTF8.GetBytes(message);

   this.channel.BasicPublish(exchange: "",
                             routingKey: queueName,
                             basicProperties: null,
                             body: body);
}

Po uruchomieniu i wejściu na „index”

3

wystarczy wywołać seriwisik publikujący zdarzenie. Po klikukrotnym odświeżeniu zajrzyjmy do rabbita:

4

Jest 8 wiadomości w kolejce, a aplikacja obsługująca te wiadomości nie jest jeszcze uruchomiona – ba! nawet jeszcze jej nie ma ;).

Subskrybent

Aplikacja przy starcie rejestruje się w kolejce

private readonly IMessagesService messagesService;
private IModel channel;
private IConnection connection;
private readonly string queueName;
private EventingBasicConsumer consumer;

		public EventConsumer(IMessagesService messagesService)
		{
			this.messagesService = messagesService;
			this.queueName = "helloWorldQueue";
		}

		public void Start()
		{
			var factory = new ConnectionFactory() { HostName = "localhost" };
			this.connection = factory.CreateConnection();
			this.channel = connection.CreateModel();
			this.channel.QueueDeclare(queue: queueName,
				durable: false,
				exclusive: false,
				autoDelete: false,
				arguments: null);

			this.consumer = new EventingBasicConsumer(this.channel);
			consumer.Received += (model, ea) =>
			{
				var body = ea.Body;
				var message = Encoding.UTF8.GetString(body);

				messagesService.InsertMessage(message);
			};
			channel.BasicConsume(queue: this.queueName,
				noAck: true,
				consumer: consumer);
		}

Niech obiekt tej klasy będzie singletonem aby był ciągle podłączony do kolejki i mógł odczytywać wiadomości na bieżąco. Imlementacja IMessagesService to warstwa dostępu do danych. Każdą odczytaną wiadomość zapisuje do bazy (w tym przypadku użyłem Mongo). Po wejściu na „index” subskrybenta wszystkie zapisane wiadomości zostaną wyświetlone:

6

Podsumowanie

Cały kodzik obu aplikacji dostępny tutaj.

Pjona!

Azure Function czyli po co Ci serwer?

Siemanko

Ostatnio (dopiero?) podczas zgłębiania wiedzy o mikroserwisach usłyszałem o serverless – czyli architekturze webowej, która nie wymaga klasycznego serwera. Opiera się ona na wywoływaniu funkcji będących w chmurze. Oczywiście my te funkcje musimy napisać i wrzucić do chmury. Obsługę funkcji wg mojej wiedzy oferuje Amazon, Azure i Google Cloud. Jako entuzjasta Microsoft’u założyłem konto na Azure z darmowymi 170cioma euro do wykorzystania przez pierwsze 30 dni. W kilka minut (łącznie z rejestracją) napisałem prostą funckje, która zwraca htmla (wszystko w aplikacji Azure’a w przeglądarce).

using System;
using System.Net;
using System.Net.Http.Headers;

public static async Task Run(HttpRequestMessage req, TraceWriter log)
{
    log.Info("C# HTTP trigger function processed a request.");
    await Task.FromResult(true);

    var pageTemplate = "{0}"; //html document template in string which can't be pasted here
    var pageBody = "Hello World from Azure Function which was made in less time than your coffe!";

    var response = new HttpResponseMessage();
    response.Content = new StringContent(String.Format(pageTemplate, pageBody));
    response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/html");

    return response;
}

Wraz z kliknięciem „Uruchom”

uruchom

Funkcję już można odpalać pod tym adresem.

Dla mnie bomba!

Więcej na stronie Azure’a.

Pjona!

Nancy – czemu my się dopiero poznaliśmy?

Siemanko

Ostatnio zakupiłem polecaną przez kilka znanych osobistości w polskim community książkę „Microservices in .NET Core” gdyż temat mnie interesuje i postanowiłem przyjrzeć sie temu bliżej. We wstępie autor informuje iż w stacku technologicznym wykorzystywanym w przykladach w książce góruje Nancy, której autor jest współtwórcą. Czym jest Nancy mniej więcej wiedziałem bo dość często przewija się to słowo w artykułach, które zdarza mi się czasem przeczytać 😉 Jednak to „mniej więcej” oznaczało jedynie tyle, że wiedziałem o istnieniu tego czegoś ale niekoniecznie co to coś robi ;). Z racji zamiaru przyswojenia całej wiedzy z w.w. książki postanowiłem dziś przyjrzeć się Nancy i po raz pierwszy spróbowac użyć.

Bajecznie prosta i lekka aplikacja webowa

public class IndexModule : NancyModule 
    {
        public IndexModule() 
        {
            Get["/"] = parameters => 
            {
                return View["index"];
            };
        }
    }

Powiedzmy, że to jest odpowiednik kontrolera w Asp.Net.

Teraz w klasie „Program.cs” wystartujmy naszą aplikacje webową

static void Main(string[] args) 
       {
            var uri = new Uri("http://localhost:8500");

            var config = new HostConfiguration();
            config.UrlReservations.CreateAutomatically = true;

            using (var host = new NancyHost(config, uri)) 
            {
                host.Start();

                Console.ReadLine();
            }
        }

Teraz po uderzeniu pod adres http://localhost:8500 (u mnie) dostaniemy stronkę z htmlem z pliczku „index.sshtml”. To wszystko!

Proste? Proste! I jakie lekkie!

nancy

Zużycie pamięci działającej aplikacji Nancy

Zrobiłem dla porównania na szybko aplikacje Asp.Net Core z jednym kontrolerem i widokiem (z template’u z Visual Studio). Różnica w zasobach jest znacząca.

asp

Zużycie pamięci aplikacji Asp.Net Core z jednym kontrolerem i widokiem

 

[EDIT Start 24.03.2017]

W komentarzach zarzuca mi się, że porównuje dwie wersje frameworka. Oczywiście nie miałem tego na celu tylko chciałem pokazać  jaka jest różnica w zasobach między aplikacją webową zrobioną „na szybko” z temaplate’u z VS (i tu poprostu padło na .core), a aplikacją „na szybko”, która można zrobić z Nancy.

W celu sprostowania zrobiłem pustą aplikację Asp.Net Core z dodanym Mvc oraz drugą pustą aplikację z dodanym Owin’em i Nancy. Aplikacje zwracają tylko jeden widok „Hello Wolrd”. Różnica w zasobach jest rzędu 50% na korzyść aplikacji z wykorzystaniem Nancy. Zajmę się tym konkretniej i opiszę to w kolejnym poście.

[Edit End]

 

Zakochałem się! 😉

Pjona!

Paintball Arena. Prototyp – kolizja gracza z przeszkodą. #dajsiepoznac

Siemanko

W poprzedniej części poruszaliśmy trochę graczem. Jednak wtedy gracz mógł przechodzić również przez przeszkody co jest oczywiście zjawiskiem niepożądanym. Dziś temu zaradzimy.

Komponent Collider2D

Collider2D to komponent, który pozwala na kolizję między obiektami gry i ich obsługę. Na tę chwilę wymagane jest aby gracz nie mógł przejść przez przeszkode czyli aby się na niej zatrzymał. Jest kilka klas Collidera w zależności od kształtu obiektu gry. Dla obiektu gracza użyłem CircleCollider2D natomiast dla przeszkód BoxCollider2D. Nazwy mówią same za siebie. W naszym przypadku ważne jest aby collidery obiektów przeszkód miały ustawioną wartość IsKinematic na true. Jeśli tego nie zrobimy to to obiekty przeszkód zaczną się przemieszczać po kolizji, a tego nie chcemy bo balony są przecież mocno zaśledziowane w ziemii 😉

[RequireComponent(typeof(BoxCollider2D))]
public class BunkerBase : Unit
{
	private BoxCollider2D BoxCollider { get { return GetComponent(); } }
	protected override void Start()
	{
		Rigid.isKinematic = true;
		base.Start();
	}
}

Podsumowanie

Możemy już się poruszać i chować za balonami. To może teraz by coś postrzelać? 🙂

Pjona!