Akcja - automatyzacja! Część 2: JUnit / TestNG - porównanie

Akcja - automatyzacja! Część 2: JUnit / TestNG - porównanie
Poniższy artykuł jest krótkim materiałem porównawczym, którego rozwinięciem są publikowane materiały filmowe na naszym kanale YouTube - serdecznie zachęcamy do zapoznania się z nimi. Będą się one pojawiać na naszym kanale przez kolejne trzy dni, rozpoczynając od dziś.
 

 

 

 

 

Zaczynajmy!

Testowanie jest nieodłączną częścią cyklu wytwarzania oprogramowania w dobrze zbalansowanych zespołach wytwórczych. Pamiętajmy, że nie zawsze tak było. Testy jednostkowe, testy integracyjne, testy systemowe i inne nie zawsze były wykonywane. Dziś mamy szczęście, że żyjemy w czasach, w których testowanie ma znaczenie.

Warto tutaj wspomnieć szerzej o JUnit oraz TestNG, porównując ich główne funkcje i możliwości wykorzystania.

Zaczynając swoją przygodę z automatyzacją nie sposób pominąć tych dwóch popularnych bibliotek, dlatego postaramy się przybliżyć je Wam.

 

1. JUnit

Biblioteka testowa używająca adnotacji do identyfikacji metod określających test. Jest to projekt Open Source pierwotnie opracowany przez Kent Beck i Erich Gamma. W 2013 roku projekt został przekazany do zespołu JUnit.

Biblioteka jest dostępna do pobrania zarówno z repozytorium GitHub: https://github.com/junit-team/junit5/, jak i ze strony domowej: http://junit.org/junit5/.

W przeciwieństwie do poprzednich wersji, nowy JUnit 5 składa się z kilku modułów z trzech różnych podprojektów.

Adnotacje używane są do oznaczania metod jako testy oraz do ich konfigurowania.

Poniżej przedstawiamy podstawowe adnotacje w JUnit dla wersji 5.x. Wszystkie te adnotacje mogą być używane dla metod.

 

@Test Oznacza, że ​​metoda jest metodą testową. W przeciwieństwie do adnotacji @Test JUnit 4, adnotacja ta nie deklaruje żadnych atrybutów, ponieważ rozszerzenia testów w JUnit Jupiter działają w oparciu o własne dedykowane adnotacje. Takie metody są dziedziczone, chyba że zostaną nadpisane.
@BeforeEach Oznacza, że ​​metodę z adnotacjami należy wykonać przed każdą metodą z adnotacją @Test, @RepeatedTest, @ParameterizedTest lub @TestFactory w bieżącej klasie; analogicznie do @Before JUnit 4. Takie metody są dziedziczone, chyba że zostaną nadpisane.
@AfterEach Oznacza, że ​​metoda z adnotacjami powinna być wykonywana po każdej metodzie z adnotacją @RepeatedTest @Test, @RepeatedTest, @ParameterizedTest lub @TestFactory w bieżącej klasie; analogicznie do JUnit 4 @After. Takie metody są dziedziczone, chyba że zostaną nadpisane.
@BeforeAll Oznacza, że ​​metoda z adnotacjami powinna zostać wykonana przed wszystkimi: @RepeatedTest @Test , @RepeatedTest, @ParameterizedTest i @TestFactory w bieżącej klasie; analogicznie do @BeforeClass JUnit 4. Takie metody są dziedziczone (chyba że są ukryte lub przesłonięte ) i muszą być static (chyba że używany jest cykl życia instancji testowej "na klasę").
@AfterAll Oznacza, że ​​metoda z adnotacjami powinna zostać wykonana po wszystkich @RepeatedTest @Test , @RepeatedTest, @ParameterizedTest i @TestFactory w bieżącej klasie; analogiczny do JUnit 4 @AfterClass . Takie metody są dziedziczone (chyba że są ukryte lub przesłonięte) i muszą być static (chyba że używany jest cykl życia instancji testowej "na klasę").

 

Pełna dokumentacja opisująca również wszystkie pozostałe adnotacje dostępna jest tutaj: http://junit.org/junit5/docs/current/user-guide/ 

Przykład podstawowej klasy testowej JUnit:

import org.junit.jupiter.api.*;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import static org.junit.jupiter.api.Assertions.assertEquals;
 
public class JUnitTest
{
    protected static WebDriver  driver;
 
    @BeforeEach()
    public void BeforeEach()
    {
 
    }
    
    @AfterEach()
    public void AfterEach()
    {
 
    }
    
    @BeforeAll()
    public static void BeforeAll()
    {
        System.setProperty("webdriver.chrome.driver", "chromedriver.exe");
    }
    
    @AfterAll()
    public static void AfterAll()
    {
        driver.quit();
    }
    @Test()
    public void TrueDataLoginTest()
    {
        driver = new ChromeDriver();
        driver.get("http://demo.testarena.pl/zaloguj");
                                                                                                            driver.findElement(By.id("email")).sendKeys("administrator@testarena.pl");
        driver.findElement(By.id("password")).sendKeys("sumXQQ72$L");
        driver.findElement(By.id("login")).click();
        
        assertEquals(driver.getTitle(),"Cockpit - TestArena");
        System.out.println("Poprawne logowanie");
    }
}

 

 

2. TestNG

TestNG to biblioteka do automatyzacji testów dla Javy, stworzona przez Cedrica Beusta. Liczne ograniczenia JUnit i NUnit zainspirowały autora do stworzenia narzędzia które je przezwycięży.   

Jego celem jest objęcie wszystkich kategorii automatyzacji testów - testowanie jednostkowe, testy funkcjonalne, testy end-to-end, integracyjne. Eliminując większość ograniczeń szkieletu - JUnit, ta struktura automatyzacji testów w Java daje możliwość pisania bardziej elastycznych i zaawansowanych testów. Chociaż, aby móc korzystać z tej struktury automatyzacji, jak w przypadku JUnit potrzebna jest przynajmniej podstawowa znajomość języka programowania Java.

Biblioteka pojawiła się poraz pierwszy w czasie gdy wszyscy korzystali z JUnit 3. Biblioteka TestNG oferowała nowe funkcje, które nie istniały wówczas w innych projektach, koncentrując się przy tym na rozbudowie możliwości testowych. Niektóre z funkcji, takie jak np. użycie adnotacji było czymś zupełnie nowym, ponieważ w JUnit zostały dodane dopiero w wersji 4, czyli po opublikowaniu TestNG.

Poniżej przedstawiamy podstawowe adnotacje w TestNG:

 

@Test Oznacza klasę lub metodę jako część testu.
@BeforeSuite Metoda zostanie uruchomiona, zanim wszystkie testy w tym pakiecie zostaną uruchomione.
@AfterSuite Metoda zostanie uruchomiona po uruchomieniu wszystkich testów w tym pakiecie.
@BeforeTest Metoda zostanie uruchomiona, zanim zostanie uruchomiona jakakolwiek metoda testowa należąca do klas wewnątrz znacznika <test>.
@AfterTest Metoda zostanie uruchomiona po uruchomieniu wszystkich metod testowych należących do klas wewnątrz znacznika <test>.
@BeforeGroups Lista grup, dla których ta metoda konfiguracji będzie wcześniej uruchamiana. Ta metoda jest uruchamiana na krótko przed wywołaniem pierwszej metody testowej należącej do którejkolwiek z tych grup.
@AfterGroups Lista grup, dla których ta metoda konfiguracji będzie wcześniej uruchamiana. Ta metoda jest uruchamiana na krótko po wywołaniu ostatniej metody testowej należącej do którejkolwiek z tych grup.
@BeforeClass Metoda zostanie uruchomiona przed wywołaniem pierwszej metody testowej w bieżącej klasie.
@AfterClass Metoda zostanie uruchomiona po uruchomieniu wszystkich metod testowych w bieżącej klasie.
@BeforeMethod Metoda zostanie uruchomiona przed uruchomieniem każdej metody testowej w bieżącej klasie.
@AfterMethod Metoda zostanie uruchomiona po każdej metodzie testu.

 

Pełna dokumentacja opisująca również pozostałe adnotacje dostępna jest tutaj >>

 

Przykład podstawowej klasy testowej TestNG:

 

import static org.testng.Assert.assertEquals;

import org.openqa.selenium.By;

import org.openqa.selenium.WebDriver;

import org.openqa.selenium.chrome.ChromeDriver;

import org.testng.annotations.*;

 

public class TestNG

{

    WebDriver driver;

 

    @BeforeSuite()

    public void BeforeSuite() throws InterruptedException

    {

 

    }

    

    @AfterSuite()

    public void AfterSuite() throws InterruptedException

    {

        driver.quit();

    }

    

    @BeforeTest()

    public void BeforeTest() throws InterruptedException

    {

 

    }

    

    @AfterTest()

    public void AfterTest() throws InterruptedException

    {

 

    }

    

    @BeforeGroups()

    public void BeforeGroups() throws InterruptedException

    {

 

    }

    

    @AfterGroups()

    public void AfterGroups() throws InterruptedException

    {

 

    }

    

    @BeforeClass()

    public void BeforeClass() throws InterruptedException

    {

        System.setProperty("webdriver.chrome.driver", "chromedriver.exe");

    }

    

    @AfterClass()

    public void AfterClass() throws InterruptedException

    {

 

    }

    

    @BeforeMethod()

    public void BeforeMethod() throws InterruptedException

    {

 

    }

    

    @AfterMethod()

    public void AfterMethod() throws InterruptedException

    {

 

    }

    

    @Test()

    public void TrueDataLoginTest() throws InterruptedException

    {

        driver = new ChromeDriver();

        driver.get("http://demo.testarena.pl/zaloguj");

        

        driver.findElement(By.id("email")).sendKeys("administrator@testarena.pl");

        driver.findElement(By.id("password")).sendKeys("sumXQQ72$L");

        driver.findElement(By.id("login")).click();

        

        assertEquals(driver.getTitle(),"Cockpit - TestArena");

        System.out.println("Poprawne logowanie");

    }

}

 

3. Porównanie

Pisanie testów

Zarówno TestNG, jak i JUnit opierają się na odpowiednio przygotowanych Asercjach w Java, które zostały dodane w Javie 4. Mogąc więc bezpośrednio użyć asercji, po co zawracać sobie głowę biblioteką testową?

Łatwo jest odnieść takie wrażenie, zaczynając od prostych asercji Java, ale wraz z rozwojem projektu, testowanie staje się trudnym w utrzymaniu procesem i w takiej sytuacji bardzo sensowne jest używanie biblioteki do zarządzania nim. Zarówno JUnit, jak i TestNG są zgodne z konwencjami xUnit , ale mają kilka elementów, o których warto tutaj wspomnieć. Grupy, paralelizm, sparametryzowany test i zależności:

 

Grupy

TestNG oferuje dodatkowe adnotacje do tych dostępnych w JUnit. Prawdopodobnie najbardziej zauważalną różnicą jest możliwość stworzenie grup testowych i uruchomienia kodu przed / po grupach przypadków testowych. Ponadto pojedyncze testy mogą należeć do wielu grup, a następnie działać w różnych kontekstach (np. Wolne lub szybkie testy). Jest to nieformalna warstwa pomiędzy przypadkiem testowym a scenariuszem testowym.

@Test(groups = { "sanity", "insanity" })

Podobna funkcja istnieje w kategoriach JUnit, ale brakuje jej adnotacji @BeforeGroups / @AfterGroups TestNG. W JUnit natomiast dopiero w wersji 5 wprowadzana jest nowa koncepcja, która nazywa się @Tag i ma podobne zastosowanie co grupy w TestNG, przykład:

@Tag("sanity")

@Tag("insanity")

void testSomething()

{

  ...

}

 

Testy równoległe

Jeśli chcesz uruchomić ten sam test równolegle na wielu wątkach, TestNG oferuje prostą w użyciu adnotację, podczas gdy JUnit nie oferuje tak prostego rozwiązania.

Implementacja TestNG wyglądałaby następująco:

@Test(threadPoolSize = 3, invocationCount = 9)

public void testSomething()

{

  ...

}

 

TestNG daje również możliwość uruchomienia całych pakietów testów równolegle, jeśli są one określone w pliku konfiguracyjnym XML. W przypadku JUnit trzeba napisać niestandardową metodę runner i wielokrotnie podawać te same parametry testowe.

 

Testy sparametryzowane / oparte na danych testowych

Jest to problem podawania różnych danych wejściowych do tego samego przypadku testowego, który zarówno TestNG, jak i JUnit rozwiązują, ale używają różnych podejść. Podstawowa idea jest taka sama, tworząc tablicę dwuwymiarową, Object [] [], która zawiera parametry.

Jednak oprócz dostarczania parametrów za pomocą kodu (@DataProvider), TestNG może również obsługiwać XML do podawania danych, plików CSV, a nawet plików tekstowych.

JUnit posiada funkcję różniącą się od tych dostępnych w bibliotece TestNG, umożliwia ona użycie różnych kombinacji kilku argumentów. Daje ona łatwy dostęp do listy parametrów, więcej informacji uzyskacie tutaj: JUNIT Theories.

 

Zależności między grupami / metodami

Ponieważ JUnit został zbudowany do testów jednostkowych, a TestNG miał na uwadze szerszy zakres testów, różnią się one również podejściem do zależności między testami.

TestNG pozwala zadeklarować zależności między testami i pominąć je, jeśli test zależności nie przeszedł:

@Test(dependsOnMethods = { "dependOnSomething" })

public void testSomething()

{

  ...

}

 

Ta funkcja nie istnieje w JUnit, ale może być emulowana za pomocą założeń. Istnieje jednak ryzyko że nieudane założenie może skutkować zignorowaniem i pominięciem testu.

A co za tym idzie TestNG wydaje się zapewniać większą elastyczność już od samego początku i większe możliwości w porównaniu do JUnit.

 

Uruchomienie testów

Podczas tworzenia nowego testu nia ma konieczności uwzględniania metody main w naszym kodzie, ponieważ biblioteki zapewniają własną główną metodę która zarządza wykonaniem poszczególnych testów.

Jeśli potrzebujesz niestandardowego rozwiązania, JUnit udostępnia adnotację @RunWith, która pozwala użyć własnego runnera. Pomijanie domyślnego runnera jest również możliwe z TestNG, ale nie jest tak proste, jak w przypadku JUnit. Jednak warto zauważyć, że TestNG obsługuje konfiguracje XML, która okazuje się przydatna w wielu przypadkach.

Jeśli chodzi o faktycznie przeprowadzane testy, obydwie biblioteki mają wsparcie CLI, działające przez ANT i wtyczki dostępne dla wybranych IDE z całkiem podobną funkcjonalnością. Chociaż na korzyć JUnita jest możliwość skorzystania z JDT (Eclipse Java Development Tools).

Przykładowy wynik testu z wykorzystaniem TestNG.

 

Raportowanie wyników

Raportowanie wyników testów jest ważną częścią całego procesu, w tym temacie obie biblioteki dostarczają nam pewne rozwiązania.

Raporty TestNG są generowane domyślnie w folderze wyjściowym, który zawiera 

dokument w formacie html z tabelami zawierającymi wszystkie dane testowe, informację o testach udanych / nieudanych / pominiętych, czasie trwania testów, informację o wykorzystanych danych wejściowych, oraz dzienniki testów.

Ponadto eksportuje wszystko do pliku XML, który można wykorzystać do stworzenia własnego szablonu raportu.

Sytuacja ma się nieco gorzej w przypadku JUnit, gdzie wszystkie te dane są dostępne tylko za pośrednictwem pliku XML, a raport jest niedostępny. W takim przypadku mamy potrzebę posiłkowania się wtyczkami do wybranego IDE.

 

Przykładowy raport z testów, z wykorzystaniem TestNG.

 

Automatyzacja przebiegu testów

Zarówno biblioteka JUnit jak i TestNG współpracują z takimi narzędziami jak np. Jenkins. Dla obu bibliotek zostały udostępnione wtyczki wspomagające tworzenie raportów m.in.: w formie graficznej. Tak wygenerowane raporty może być bezpośrednio wysłany do odbiorcy wybranym przez Ciebie sposobem. Prostym przykładem może być użycie: TestNG z narzędziem Jenkins i integracja z pocztą elektroniczną oraz serwisem Slack.

Konkluzja: Zarówno JUnit, jak i TestNG mają porównywalne możliwości w tym zakresie.

 

Społeczność

JUnit ma dłuższą historię i znacznie większą bazę użytkowników, można stwierdzić że zdefiniował standard testów jednostkowych dla środowiska JAVA. W związku z tym dość łatwo jest znaleźć odpowiedź na pytanie lub kogoś kto pomoże w przypadku napotkanego problemu.

TestNG również nie pozostaje w tyle, zrzeszając wielu użytkowników. A odpowiedzi na nawet najbardziej nurtujące pytania są dostępne na wielu serwisach oraz grupach internetowych.

Podsumowując: W przypadku kłopotu w użyciu obu bibliotek, nie ma kłopotu ze znalezieniem pomocy.

 

Końcowe wnioski

Testy automatyczne mogą być częścią wsparcia jakości wytwarzanego oprogramowania, jednak musi to być część dobrze przemyślanego planu.

Czego tak naprawdę potrzebujesz? Dokładnych raportów, równoległych testów czy liczysz może na mniejsze ryzyko związane z większym wsparciem społeczności korzystającej z wybranego rozwiązania?

Oba rozwiązania mogą sprawdzić się lepiej lub gorzej w Waszych projektach.

Zarówno JUnit jak i TestNG są profesjonalnymi projektami które wspomagają pracę przy automatyzacji testów.

Mamy nadzieję że powyższy materiał rozwieje częściowo wątpliwości związane z wyborem konkretnej biblioteki, i przyspieszy tworzenie Waszego środowiska automatyzacji.

 

Autor: Krzysztof Kołodziejczyk

 

 

SPRAWDŹ TAKŻE
Akcja - automatyzacja! Część 1: Konfiguracja 

 

 

 

To powinno Cię zainteresować