Archiwa tagu: testy automatyczne

Narzędzia do automatyzacji aplikacji desktopowych Windows Forms

Jakich narzędzi można użyć do automatyzacji aplikacji desktopowych, działających w systemie Windows (Windows Forms, WPF)?

Na szczęście istnieje wiele rozwiązań, dzięki którym pisanie testów automatycznych może być całkiem skuteczne. Poniżej kilka propozycji. Większość z nich jest darmowa – poza licencją na Visual Studio (w przypadku pierwszej grupy, opartej o .NET Framework lub rozwiązań Microsoftu).

Inspect - świetne narzędzie pomagające w automatyzacji aplikacji desktopowych

Zapraszam do lektury:

Czytaj dalej

Page Object Pattern + Fluent Interface

Page Object Pattern jest jednym z częściej używanych wzorców w testach interfejsowych. Nie bez powodu – pozwala świetnie odzwierciedlić architekturę aplikacji w testach, zapobiega także niepotrzebnemu powtarzaniu kodu, jeżeli pewne struktury powtarzają się w wielu miejscach aplikacji (na przykład wspólne dla każdej podstrony menu).

Jeden Page Object powinien odwzorowywać jeden logiczny fragment aplikacji. Posługując się przytoczonym wyżej przykładem menu, klasa „Menu” będzie zawierać wszystkie pozycje w menu (a jeśli ma ono strukturę hierarchiczną, także i zagnieżdżone, kolejne Page Objecty).

Każda metoda w klasie udostępnia oferowane przez obiekt usługi (np. rozwinięcie i zwinięcie podmenu, wybór pozycji itd.).

W tym wzorcu metoda zawsze zwraca Page Object (z czego korzysta kolejny wzorzec: Fluent Interface). Dzięki temu w testach można stworzyć bardzo elegancko wyglądającą ścieżkę wędrówki pomiędzy podstronami.

Z pozoru te same akcje mogą zwracać różne wyniki, np. dwie metody:


public AdminPage LoginWithCorrectCredentials(string userName, string password);

public ErrorPage LoginWithIncorrectCredentials(string userName, string password);

Asercje wykonywane są w testach, nie w klasach Page Objectowych.

Sterownik (WebDriver) może być przekazywany w konstruktorze Page Object.

Dobre praktyki w testach Selenium

Dobre praktyki w automatycznych testach w Selenium:

  • Podstawowa zasada – używanie aktywnego czekania (WebDriverWait, wait.Until, ImplicitlyWait itd.) zamiast Thread.Sleep, który spowalnia testy, nie dając jednocześnie gwarancji, że zastosowana przerwa jest odpowiednio długa (jest wiele artykułów na ten temat w sieci).
  • Wyszukiwanie elementów na stronie za pomocą selektorów CSS (By.CssSelector) lub identyfikatorów ID lub name (By.Id, By.Name) – taka metoda jest o niebo szybsza niż wyszukiwanie przez XPATH.
  • Używanie wzorca Page Object.

Oraz kilka dobrych praktyk, które mogą być stosowane ogólnie w testach, nie tylko Selenium:

  • DRY – Don’t Repeat Yourself – niepowielanie tego samego kodu poprzez tworzenie np. zestawów funkcji bibliotecznych, dobre rozplanowanie architektury testów, korzystanie z dostarczanych przez framework metod jak Test Setup, Teardown (np. ustawienie przeglądarki przed testami, zamknięcie przeglądarki po testach).
  • Behaviour Driven Testing – testy będą „bliżej autentycznego użytkownika”.
  • Logowanie błędów! Bardzo przydatne w przypadku testów uruchamianych na środowiskach Continuous Integration, gdzie jeśli testy nie mają logów, jedyną informacją, jaką dostarczą będzie „test nie przeszedł, coś nie działa”. Warto logować ścieżkę wykonanych akcji wraz z parametrami metod oraz informacje o środowisku na którym uruchomiono testy: system, przeglądarka, baza danych itd., godzina – a nuż błąd pojawia się na przykład pomiędzy 23 a 1 dnia następnego?, itd.
  • Screenshoty – jeżeli jest taka możliwość (a w Selenium jest) – stanowią wspaniałe źródło informacji o błędach. Nie wszystko da się wyczytać z logu – informacja typu „nie da się kliknąć kontrolki x” niewiele mówi – tymczasem „obraz wyraża więcej niż tysiąc słów”, a na screenie dokładnie widać, co się wydarzyło.

Może się wydawać (nadal!), że testy są niepotrzebne, są stratą czasu. Tymczasem są one w stanie wykryć defekty już na bardzo niskim poziomie (testy jednostkowe). Jeżeli aplikacja boryka się z problemami dużego długu technicznego, złej architektury a klasy cechują się wysokim sprzężeniem, próby modyfikacji aplikacji „na siłę”, byle tylko pokryć ją testami mogą niestety narobić więcej szkody, niż pożytku. W takim przypadku lepiej sprawdzi się metoda małych kroków. Implementując nowe funkcjonalności w aplikacji warto rozważyć piszanie testów jednostkowych tylko do nowych partii aplikacji (TDD). Przymierzając się natomiast do dużych zmian w całej aplikacji warto przygotować jak najwięcej wysokopoziomowych testów czarnoskrzynkowych (funkcjonalnych, integracyjnych). Nawet jeśli ich implementacja byłaby kosztowna w sensie czasowym lub finansowym, warto to zrobić. Optymalnym rozwiązaniem jest zautomatyzowanie owych testów. Minimalnym – spisanie wszystkich wymagań stawianych aplikacji (np. w formie proponowanej przez BDD).

Inspirację do tworzenia nowych przypadków testowych można czerpać również z błędów zgłaszanych przez użytkowników. To bardzo dobre źródło informacji o tym, w jaki sposób użytkownicy naprawdę używają aplikacji. Dobrze zgłoszony błąd zawiera pełną ścieżkę czynności potrzebnych do zreprodukowania błędu. Po jego naprawieniu, warto ją zautomatyzować.

How to check if element is invisible in Ruby Selenium client

How to check if element is invisible in Ruby Selenium client? In Java or C# answer is easy. There is a mechanism called ExpectedCondition, which has got ready for use method: ExpectedConditions.invisibilityOfElementLocated. Example:

driverWait.until(ExpectedConditions.invisibilityOfElementLocated(element));

Unfortunatelly, Ruby client hasn’t got anything like this. Quick workaround – try to find element. When element is not displayed, catch exception and return false:

def check_if_not_exist(locator)
  begin
    wait = Selenium::WebDriver::Wait.new :timeout => 10
    wait.until { @driver.find_element(:name, 'elementName').displayed? }
  rescue
    false
  end
end

Not very elegant and quick, but works.

 

Sprawdzanie czy checkbox jest zaznaczony w Selenium (C#)

Każdy z obiektów znalezionych przez Selenium jest standardowym obiektem typu WebElement. Udostępnia on następujące metody (których wartość zwracana jest typu bool):

  • displayed – element jest widoczny.
  • enabled – element jest aktywny – można go kliknąć, wpisać mu wartość itd. W praktyce każdy element dostępny na stronie ma stan enabled, gdyż jest to stan domyślny. „Wyłączyć” można go jedynie poprzez celowe dodanie do HTML atrybutu disabled. Element z takim atrybutem będzie „wyszarzony”.
  • checked – element jest zaznaczony. Dotyczy pól typu input – zarówno typu radio jak i checkbox.

Kawałek kodu HTML-owego, do którego zastosuję Selenium:


<html>
<body>
<form>
<input type="checkbox" />
</form>
</body>
</html>

Oraz fragment kodu C# z wykorzystaniem Selenium:


[Test]
public void CheckboxIsChecked()
{
  driver.url="file:///C:/DOCUMENTS/test.html";
  var el = driver.FindElement(By.TagName("input"));
  var before = el.Selected;//false
  el.Click();
  var after = el.Selected;//true
}

Asert w NUnit

Jednym z bardziej elementarnych składników testów są asercje, umożliwiające sprawdzenie, czy spełnione są warunki testu.

W NUnicie dostępnych jest wiele różnych asercji. Najprostsza to:

Assert.AreEqual

To jednak nie wszystko. NUnit oferuje cały wachlarz asercji – warto spojrzeć w dokumentację [equalConstraint][assertions].

Bardzo fajną opcją jest na przykład sprawdzenie, czy kolekcja jest posortowana:

var notSorted = new List<decimal>();

notSorted.Add(12.3);

notSorted.Add(5.5);

notSorted.Add(100);

Assert.That(notSorted, Is.Ordered);//Fail

Poza klasycznymi asercjami NUnit oferuje też np. StringAsert, CollectionAsert itd.

Artykuł w PasjaOnline: Jak ugryźć testowanie oprogramowania

„Jak ugryźć testowanie oprogramowania” to jeden z moich artykułów w serwisie PasjaOnline. Stanowi wstęp do serii artykułów poświęconych testowaniu. Więcej na stronie:

http://pasjaonline.pl/jak-ugryzc-testowanie-oprogramowania/

PasjaOnline to dość młody serwis skupiający miłośników programowania (zwłaszcza aplikacji webowych).

 

RSpec – ciekawostki

RSpec: flaga fail_fast – uruchamia testy „do pierwszego faila”. Przerywa wykonywanie testów, jeżeli któryś test nie przejdzie (nawet jeśli mamy testy w wielu plikach, zachowują się jakby były scalone w jeden). https://www.relishapp.com/rspec/rspec-core/v/2-0/docs/configuration/fail-fast

Analogicznie do JUnitowego Ignore, w RSpec możemy pominąć niektóre testy. Zamiast it należy wstawić skip (w podsumowaniu test pojawi się jako pending).

Minimalne wymagania ustawień Appium

Dzisiejszy wpis dotyczy konfiguracji Appium do testów aplikacji natywnych.

Aby Appium mogło zostać uruchomione, należy ustawić kilka podstawowych ustawień (capabilities). Można je zapisać w pliku (np. appium.txt) i odczytywać podczas uruchomienia testów lub przekazywać podczas uruchamiania serwera Appium.

Minimalne wymagane ustawienia w części [caps], to:

  1. dla systemu iOS:
    1. podpięty iPhone, aplikacja *.ipa:
      udid = "1234567890abcdefghijklmnopqrstuvwxyz0123" – wartość obowiązkowa, po której Appium rozpoznaje, czy powinno działać na prawdziwym urządzeniu, czy na symulatorze.
      app = "~/builds/ApplicationName.ipa" – wartość obowiązkowa.
    2. podpięty iPhone, aplikacja już zainstalowana na urządzeniu:
      deviceName = "iPhone-name"
      udid = "1234567890abcdefghijklmnopqrstuvwxyz0123"
      bundleId = "com.company.package"
    3. symulator – bez znaczenia, czy aplikacja jest już zainstalowana, czy Appium musi ja dopiero zainstalować – konieczne jest podanie parametru app, samo bundleId nie jest wystarczające.
      deviceName = "iPhone 5 iOS 8.1" – wartość obowiązkowa. Musi być wpisana dokładna nazwa symulatora, bez udid.
      app = "/Users/d9k/Library/Developer/Xcode/DerivedData/ApplicationName-qwertyuiopasdfghjklzxcvbnmqw/Build/Products/Debug-iphonesimulator/ApplicationName.app" – wartość obowiązkowa
      udid – w zasadzie nie można go używać, gdyż parametr ten sugeruje serwerowi Appium, że ma do czynienia z podłączonym urządzeniem.
  2. dla systemu Android:
    1. podpięty telefon z Androidem lub emulator, bez zainstalowanej aplikacji:
      platformName = "Android"
      deviceName ="dev" – wartość obowiązkowa, pomimo że podczas testowania aplikacji Android jest ignorowana (można wpisać cokolwiek).
      app = "someApp.apk" – wartość obowiązkowa.
    2. podpięty telefon z Androidem lub emulator, z zainstalowaną aplikacją:
      platformName = "Android"
      appPackage = "com.company.package"
      appActivity = ".SomeActivity"
      deviceName = "dev" – j.w.