Co to jest asercja?

Asercja to instrukcja, której celem jest potwierdzenie, że predykat jest prawdziwy w danym miejscu w kodzie.

Znalezione obrazy dla zapytania que gif

Weźmy klasyczny scenariusz logowania, np. na Testelce. Załóżmy, że nie mamy weryfikacji wieloetapowej – po prostu podajemy poprawny adres email i pasujące do niego hasło. Po wprowadzeniu tych informacji i kliknięciu na przycisk logowania powinniśmy być poprawnie zalogowani. Co jest dowodem na to, że się zalogowaliśmy? Na Testelce takim dowodem będzie pojawienie się tekstu „Cześć, Ola!” wraz z odpowiednim imieniem w prawym górnym rogu zamiast „Zaloguj się”. Przerabiając to na Selenium możemy powiedzieć, że predykatem będzie: odpowiedni element na stronie zawiera tekst „Cześć, odpowiednie_imię!”. Asercja z kolei, to taka instrukcja, która weźmie ten predykat i potwierdzi jego prawdziwość, a w przypadku, gdy predykat nie będzie prawdziwy, zatrzyma test i zwróci informację o asercji zakończonej niepowodzeniem (failed assertion).

Asercja jest sercem każdego testu automatycznego – to właśnie ona ostatecznie nam potwierdza, czy zestaw kroków, jakie test wykonał przed asercją, skutkuje jakimś oczekiwanym stanem.

Jak budować asercje?

Za pomocą dostępnych bibliotek. W każdym języku programowania z jakim miałam chociaż przelotny kontakt, dostępny jest jakiś framework do testów jednostkowych (z reguły więcej niż jeden). Pozwalają one na budowanie asercji w sposób czytelny i intuicyjny i do tego pozwalają także na wprowadzenie komunikatu, który się wyświetli w przypadku, gdy asercja zakończy się niepowodzeniem (czyli mówiąc potocznie i zupełnie nie po polsku: sfailuje).

I tak na przykład asercja w naszych testach logowania w poszczególnych frameworkach w Javie i C# wyglądała by jak poniżej:

//JUnit 5.2
Assertions.assertTrue(myAccountText == expectedWelcomeUserText, "My account link text is not what is expected. " +
                "Expected: " + expectedWelcomeUserText + " Actual: " + myAccountText);
//TestNG 6.14.3
Assert.assertTrue(myAccountText == expectedWelcomeUserText, "My account link text is not what is expected. " +
                "Expected: " + expectedWelcomeUserText + " Actual: " + myAccountText);
//NUnit 3.11
Assert.True(myAccountText == expectedWelcomeUserText, "My account link text is not what is expected. " +
                "Expected: " + expectedWelcomeUserText + " Actual: " + myAccountText);
//MSTest 1.3.2
Assert.IsTrue(myAccountText == expectedWelcomeUserText, "My account link text is not what is expected. " +
                "Expected: " + expectedWelcomeUserText + " Actual: " + myAccountText);

Różnice nie wydają się duże, prawda? Tutaj podałam tylko jedną z dostępnych asercji, czyli asercję sprawdzającą czy wyrażenie, które podamy w parametrze jest prawdziwe. Każdy z tych czterech frameworków zawiera także inny rodzaj asercji, który sprawdza np. czy dwie zmienne są sobie równe. Powyższy przykład moglibyśmy też zatem zapisać jak poniżej:

//JUnit 5.2
Assertions.assertEquals(expectedWelcomeUserText, myAccountText, "My account link text is not what is expected. " +
                "Expected: " + expectedWelcomeUserText + " Actual: " + myAccountText);
//TestNG 6.14.3
Assert.assertEquals(myAccountText, expectedWelcomeUserText, "My account link text is not what is expected. " +
                "Expected: " + expectedWelcomeUserText + " Actual: " + myAccountText);
//NUnit 3.11
Assert.AreEqual(expectedWelcomeUserText, myAccountText, "My account link text is not what is expected. " +
                "Expected: " + expectedWelcomeUserText + " Actual: " + myAccountText);
//MSTest 1.3.2
Assert.AreEqual(expectedWelcomeUserText, myAccountText, "My account link text is not what is expected. " +
                "Expected: " + expectedWelcomeUserText + " Actual: " + myAccountText);

W tym przypadku jedyną wyraźną różnicę widzimy w przypadku TestNG – wartość oczekiwaną (czyli expectedWelcomeUserText) podajemy jako drugą, a faktyczną, czyli to co rzeczywiście widzimy na stronie po zalogowaniu, jako pierwszą, podczas gdy w pozostałych frameworkach podaje się odwrotnie.

Mała dygresja przy tej okazji – moglibyście pomyśleć, że co to za różnica w jakiej kolejności podamy wartość oczekiwana i faktyczną, skoro i tak je między sobą porównujemy. A no otóż ma to znaczenie i przychodzi mi co najmniej jedna taka sytuacja do głowy. Te wszystkie frameworki wyrzucają w konsolce informacje o asercji zakończonej niepowodzeniem i wtedy podają informację co było oczekiwane, a co faktycznie się znalazło na stronie. Gdy pomieszamy kolejność, to ten komunikat wprowadzi nas w błąd twierdząc, że to faktyczna wartość była oczekiwaną. Oczywiście są jeszcze komunikaty, które sami wprowadzamy do asercji (o ile rzeczywiście je podamy) ale skąd wtedy mamy wiedzieć, czy to nasz komunikat jest niepoprawny, czy może ten domyślny komunikat z asercji nas wprowadza w błąd? Zwłaszcza jeżeli test był skomplikowany i napisany rok temu przez kogoś, kto już w firmie nie pracuje, a developer który pisał testowaną funkcjonalność rzucił to wszystko i wyjechał w Bieszczady warzyć domowe piwo.

Najpopularniejsze asercje

To wcale nie musi być tak, że każdy z tych frameworków ma identyczny zestaw asercji. Są jednak takie asercje, które są na tyle powszechne w użyciu, że znajdziemy je zarówno w NUnit, MSTest, JUnit czy TestNG. Pokażę Ci tutaj kilka z tych, których samej zdarzyło mi się użyć co najmniej raz albo takich, które w mojej opinii mogą być przydatne np. w testach Selenium.

assertTrue (JUnit, TestNG), True (NUnit), IsTrue (MSTest oraz NUnit)

Sprawdza, czy podany warunek jest prawdziwy.

//JUnit 5.2
Assertions.assertTrue(a>b, "a nie jest większe od b");
//TestNG 6.14.3
Assert.assertTrue(a>b, "a nie jest większe od b");
//NUnit 3.11
//Dostępne dwie metody
Assert.True(a>b, "a nie jest większe od b");
Assert.IsTrue(a>b, "a nie jest większe od b");
//MSTest 1.3.2
Assert.IsTrue(a>b, "a nie jest większe od b");

assertFalse (JUnit, TestNG), False (NUnit), IsFalse (MSTest oraz NUnit)

Sprawdza, czy podany warunek jest nieprawdziwy.

//JUnit 5.2
Assertions.assertFalse(a==0, "a jest równe 0");        
//TestNG 6.14.3
Assert.assertFalse(a==0, "a jest równe 0");
//NUnit 3.11
//Dostępne dwie metody
Assert.IsFalse(a==0, "a jest równe 0");
Assert.False(a==0, "a jest równe 0");
//MSTest 1.3.2
Assert.IsFalse(a==0, "a jest równe 0");

assertEquals (JUnit, TestNG), AreEqual (MSTest, NUnit)

Sprawdza, czy to, co jest oczekiwane i to co faktycznie test otrzyma jest równe (czyli czy zgadza się typ i wartość; w odróżnieniu od assertSame, który sprawdza czy to co podamy w obu parametrach odwołuje się do tego samego obiektu).

//JUnit 5.2
Assertions.assertEquals(expectedUsername, actualUsername, "Nazwa użytkownika jest niepoprawna.");      
//TestNG 6.14.3
Assert.assertEquals(actualUsername, expectedUsername, "Nazwa użytkownika jest niepoprawna.");
//NUnit 3.11
Assert.AreEqual(expectedUsername, actualUsername, "Nazwa użytkownika jest niepoprawna.");
//MSTest 1.3.2
Assert.AreEqual(expectedUsername, actualUsername, "Nazwa użytkownika jest niepoprawna.");

assertNotEquals (JUnit, TestNG), AreNotEqual (MSTest, NUnit)

Sprawdza, czy to, co podamy w parametrach jest różne.

//JUnit 5.2
Assertions.assertNotEquals(logInLinkText, username, "Link do logowania nadal jest widoczny na stronie.");      
//TestNG 6.14.3
Assert.assertNotEquals(logInLinkText, username, "Link do logowania nadal jest widoczny na stronie.");
//NUnit 3.11
Assert.AreNotEqual(logInLinkText, username, "Link do logowania nadal jest widoczny na stronie.");
//MSTest 1.3.2
Assert.AreNotEqual(logInLinkText, username, "Link do logowania nadal jest widoczny na stronie.");

assertNull (JUnit, TestNG), Null (NUnit), IsNull (MSTest oraz NUnit)

Sprawdza, czy to, co podamy w parametrze jest nullem.

//JUnit 5.2
Assertions.assertNull(a, "a nie jest nullem.");
//TestNG 6.14.3
Assert.assertNull(a, "a nie jest nullem.");
//NUnit 3.11
//Dostępne dwie metody
Assert.IsNull(a, "a nie jest nullem.");
Assert.Null(a, "a nie jest nullem.");
//MSTest 1.3.2
Assert.IsNull(a, "a nie jest nullem.");

assertNotNull (JUnit, TestNG), NotNull (NUnit), IsNotNull (MSTest oraz NUnit)

Sprawdza, czy to, co podamy w parametrze nie jest nullem.

//JUnit 5.2
Assertions.assertNotNull(a, "a jest nullem.");
//TestNG 6.14.3
Assert.assertNotNull(a, "a jest nullem.");
//NUnit 3.11
//Dostępne dwie metody
Assert.IsNotNull(a, "a jest nullem.");
Assert.NotNull(a, "a jest nullem.");
//MSTest 1.3.2
Assert.IsNotNull(a, "a jest nullem.");

assertThrows (JUnit, TestNG), Throws (NUnit), ThrowsException (MSTest)

Sprawdza, czy kawałek kodu, który podamy w parametrze, rzuca wyjątek określonego typu w trakcie jego wykonania. Przykład z wyjątkiem rzucanym, gdy element nie zostanie znaleziony na stronie.

//JUnit 5.2
Assertions.assertThrows(NoSuchElementException.class, () -> driver.findElement(By.cssSelector("#test")), "Podany wyjątek nie został rzucony.");
//TestNG 6.14.3
//brak możliwości dodania swojego komunikatu błędu
Assert.expectThrows(NoSuchElementException.class, () -> driver.findElement(By.cssSelector("#test")));
//NUnit 3.11
Assert.Throws<NoSuchElementException>(() => Driver.FindElement(By.CssSelector("#test")), "Podany wyjątek nie został rzucony.");
//MSTest 1.3.2
Assert.ThrowsException<NoSuchElementException>(() => Driver.FindElement(By.CssSelector("#test")), "Podany wyjątek nie został rzucony.");

assertDoesNotThrow (JUnit), DoesNotThrow (NUnit)

Sprawdza, czy kawałek kodu, który podamy w parametrze, nie rzuca żadnego wyjątku w trakcie jego wykonania.

//JUnit 5.2
Assertions.assertDoesNotThrow(()->driver.findElement(By.cssSelector("#test")), "Wyjątek został rzucony.");
//NUnit 3.11
Assert.DoesNotThrow(() => Driver.FindElement(By.CssSelector("#test")), "Wyjątek został rzucony.");

Miękka asercja

Wspomniałam już wcześniej, że gdy asercja nie zakończy się sukcesem, to test się zatrzyma, czyli nic, co znajduje się w metodzie testowej po asercji się nie wykona. A co jeżeli chcemy wykonać w jednym teście kilka asercji?

Co to jest miękka asercja?

Zanim odpowiem na to pytanie chcę zwrócić uwagę na jedną rzecz. Dobrą praktyką jest sprawdzanie jednej rzeczy w jednym teście, a więc używanie tylko jednej asercji w metodzie testowej na koniec testu. Na przykład poniższy test, jest kiepskim pomysłem:

  1. Przejdź na stronę.
  2. Wprowadź login.
  3. Wprowadź hasło.
  4. Kliknij przycisk „Zaloguj”.
  5. ASERCJA: sprawdź, czy wyświetla się powitanie.
  6. Przejdź do sekcji „Moje konto”.
  7. ASERCJA: sprawdź, czy wyświetlają się poprawne dane adresowe.

Kiepski to pomysł, bo gdy zawiedzie pierwsza asercja to druga asercja się nie wykona i nie będziemy mieli informacji na temat tego, czy wyświetlanie danych adresowych działa poprawnie. Oznacza to, że podczas gdy będziemy czekali, aż developerzy naprawią ten błąd, nie będziemy wiedzieli o potencjalnym ukrytym błędzie i wykryjemy go dopiero, gdy wyświetlanie powitania zostanie naprawione albo gdy przeklikamy aplikację i sprawdzimy poprawność wyświetlania adresu.

No dobrze, ale możecie też chcieć zrobić coś takiego:

  1. Przejdź na stronę.
  2. Wprowadź login.
  3. Wprowadź hasło.
  4. Kliknij przycisk „Zaloguj”.
  5. ASERCJA: sprawdź, czy wyświetla się powitanie.
  6. ASERCJA: sprawdź, czy wyświetla się nazwa użytkownika.
  7. ASERCJA: sprawdź, czy nie ma linku do logowania.

Tu sytuacja wygląda trochę inaczej. Nie musimy wykonywać żadnej dodatkowej akcji na stronie w stylu kliknąć w link „Moje konto”. Wszystko, czego potrzebujemy już tam mamy, chcemy tylko sprawdzić czy jakieś elementy są na stronie i ewentualnie pobrać z nich tekst i sprawdzić czy jest taki, jakiego oczekujemy. Ponadto, wykonywanie tych samych kroków żeby sprawdzić trzy różne rzeczy wydłuży nam testy. O ile jeden test nie musi nam robić większej różnicy, to jeżeli zdecydujemy się na taką praktykę we wszystkich naszych testach, to czas ich wykonania może się naprawdę poważnie wydłużyć.

W tym celu możemy użyć miękkiej asercji, czyli rozwiązania, które pozwoli nam sprawdzić wszystkie asercje w teście bez jego zatrzymywania w przypadku błędu w którejś z nich i zwrócić zbiorczą informację o wyniku wszystkich asercji. Uważam, że w sytuacji, w której wszystko do naszych asercji mamy niejako podane na tacy, możemy użyć tego rozwiązania i sama go czasem używam. Chcę tylko zaznaczyć jedną rzecz – żeby test miał ręce i nogi, to niech testuje tylko jeden obszar. Powyższy test jest testem logowania, wszystkie trzy asercje w zasadzie sprawdzają, czy jesteśmy zalogowani poprawnie. Wrzucanie do takiego testu asercji, która będzie np. sprawdzała wyświetlanie reklam dla zalogowanych użytkowników powinno się znaleźć w osobnym teście. Wrzucanie wszystkiego do jednego worka zmniejszy czytelność testów i utrudni szybką ocenę, co właściwie nie działa, bez grzebania po dokładnych wynikach testu.

Z miękkimi asercjami należy się obchodzić ostrożnie i dobrze się zastanowić czy potencjalne korzyści (np. czas wykonania testu) przeważają nad tym co tracimy używając takiego rozwiązania (tracimy w pewnym stopniu czytelność wyników testu – widzimy, że wysypał się cały test podczas gdy tak naprawdę wysypała się np. tylko jedna asercja).

Przykłady

Nie każdy framework do testów jednostkowych posiada rozwiązanie pozwalające na użycie miękkiej asercji. Znam co najmniej jeden, który go nie ma – MSTest. Pokażę natomiast jak można użyć miękkiej asercji w JUnit, TestNG oraz NUnit.

W JUnit w tym celu używamy po prostu metody assertAll, w której jako argumentów możemy użyć właściwych asercji, jak w przykładzie poniżej:

//JUnit 5.2
Assertions.assertAll(
        ()->Assertions.assertTrue(warunek1),
        ()->Assertions.assertEquals(expected, actual),
        ()->Assertions.assertTrue(warunek2)
);

Podobnie do wygląda w NUnicie, gdzie do dyspozycji mamy metodę Multiple. Ta metoda w odróżnieniu od metody assertAll w JUnit, przyjmuje jeden parametr typu TestDelegate (o tym czym są delegaty w C# przeczytasz więcej tutaj).

//NUnit 3.11
Assert.Multiple(() => {
    Assert.True(warunek1);
    Assert.AreEqual(expected, actual);
    Assert.True(warunek2);
    }                
);

W TestNG zastosowano już inne rozwiązanie. Należy stworzyć obiekt klasy SoftAssert, następnie użyć odpowiednich metod na tym obiekcie, a na koniec użyć metody assertAll.

//TestNG 6.14.3
SoftAssert softassert = new SoftAssert();
softassert.assertTrue(warunek1);
softassert.assertTrue(warunek2);
softassert.assertTrue(warunek3);
softassert.assertAll();

Podsumowanko

Po tym artykule powinnaś albo powinieneś wiedzieć już więcej niż mniej o asercjach. To, co chcę przekazać na koniec tego wpisu, to podobieństwo pomiędzy wszystkimi frameworkami do testów jednostkowych, które dzisiaj pokazałam. Zależy mi na tym, bo od jakiegoś czasu staram się pokazywać osobom, które zaczynają w testy automatyczne, że wybór technologii na początku nie jest tak ważny. Czas poświęcony np. na język programowania, którym się ostatecznie nie zajmiecie zawodowo nie jest na pewno czasem straconym. W najgorszym wypadku wzbogacisz się o wiedzę na temat tego, czy lubisz ten język i chcesz się w nim dalej rozwijać, czy nie. A w najlepszym zrozumiesz w praktyce np. paradygmaty programowania obiektowego.

Sporo rozwiązań się też zwyczajnie nie różni między sobą na poziomie, który wymagał by od Ciebie ogromnego wysiłku żeby przeskoczyć. Chodzi mi tutaj na przykład o TestNG i JUnit. Jeżeli znajdziesz sobie jakiś fajny kurs, z zakresem materiału, który Ci odpowiada i prowadzony przez kogoś, kto tłumaczy w sposób, który Ci pasuje, to szkoda odpuścić tylko dlatego, że jest użyty nie ten framework do testów jednostkowych, w którym chcesz pracować. Poza tym, to w czym będziesz pracować zmieni się wiele razy w trakcie Twojej kariery testera automatyzującego.

Jeżeli chcesz sprawdzić czy mój kurs Selenium jest dla Ciebie, skorzystaj z pierwszych pięciu darmowych lekcji Selenium w Javie tutaj lub Selenium w C# tutaj.

Źródła:
JUnit 5 User Guide
Dokumentacja do JUnit
TestNG: klasa Assert
Dokumentacja do NUnit
Dokumentacja do MSTest

Dodaj komentarz:

 

  1. Wszytsko pieknie Elu, ale jak mozna uzyc tych konstrukcji w naszym kodzie.

    Jak je zainportować ? Bo nie bede inportował całego TestNg , chciałbym odpowiednie asersje

    czyli jak polecasz u gory import co nalezy dodać

    dzieki! super kursy

    Odpowiedz
    • Nie wiem, czy na pewno rozumiem. Żeby użyć asercji z TestNg to musisz np. w Mavenie dodać sobie odpowiednią zależność (np. https://mvnrepository.com/artifact/org.testng/testng/6.14.3). Co rozumiesz przez import u góry?

      Odpowiedz