CodeGym /Blog Java /Random-PL /Testy jednostkowe w Javie z JUnit
Autor
Edward Izraitel
Software Engineer at Champions Oncology

Testy jednostkowe w Javie z JUnit

Opublikowano w grupie Random-PL

Co to są testy jednostkowe w Javie?

Zanim przejdziemy do nauki języka JUnit w Javie, omówmy pokrótce, czym są testy jednostkowe i dlaczego są tak popularne (jeśli znasz już te zagadnienia, przejdź do „Jak napisać test JUnit w Javie?”). Testy jednostkowe w Javie sprawiają, że tworzenie oprogramowania na dużą skalę jest znacznie wydajniejsze i łatwiejsze. Może pomóc zarówno poszczególnym osobom, jak i zespołom skrócić niezliczone godziny debugowania i ogromnie usprawnić proces współpracy. Testy jednostkowe w Javie z JUnit - 1

https://junit.org/junit4/

Podstawowa idea testów jednostkowych jest następująca: pisz testy atomowe poszczególnych funkcji (zwane testami jednostkowymi) i powoli dodawaj kolejne funkcje po przetestowaniu i upewnieniu się, że poprzednie działają. To niezwykle prosty, ale potężny pomysł. Jako przykład tego, jak może wyglądać ten proces, wyobraź sobie, że budujesz wirtualny kalkulator naukowy. Oprócz oczywistych operatorów arytmetycznych ( +, -, x, %), ten kalkulator miałby zaawansowane funkcje, które wymagają innych funkcji podrzędnych do działania w nim. Aby obliczyć wykładniki, kalkulator musi poprawnie mnożyć. Tak więc podejście do testowania jednostek do budowania i testowania tego kalkulatora byłoby następujące:
  • Napisz funkcję dodawania. Przetestuj to dokładnie, zmień, powtarzaj, aż zadziała.
  • Zrób to samo dla funkcji odejmowania, mnożenia i dzielenia.
  • Użyj tych operatorów podstawowych, aby napisać bardziej zaawansowane funkcje operatorskie, takie jak wykładniki, a następnie przetestuj również te funkcje.
Dzięki temu funkcje, które składają się z innych mniejszych funkcji podrzędnych, nie tylko działają prawidłowo same w sobie, ale nie zawierają wadliwych funkcji podrzędnych. Na przykład, jeśli testuję funkcję wykładniczą i coś idzie nie tak, wiem, że błąd prawdopodobnie nie występuje w podfunkcji mnożenia, ponieważ funkcja mnożenia została już dokładnie przetestowana. To znacznie eliminuje całkowitą ilość kodu, który muszę prześledzić wstecz i sprawdzić, aby znaleźć błąd. Mamy nadzieję, że ten trywialny przykład wyjaśnia, w jaki sposób zorganizowany jest proces myślowy wokół testów jednostkowych. Ale w jaki sposób testy jednostkowe wchodzą w interakcję z resztą procesu tworzenia oprogramowania? A co jeśli masz jeszcze bardziej złożone funkcje, które muszą współpracować i komunikować się ze sobą? Testy jednostkowe są niewystarczające, aby upewnić się, że tak złożone funkcje mogą ze sobą prawidłowo współpracować. W rzeczywistości jest to tylko pierwszy krok Czterech Poziomów Testowania Oprogramowania (używam wielkich liter, ponieważ odnoszę się do standardu branżowego lub najpowszechniejszego podejścia do testowania oprogramowania). Ostatnie trzy kroki toTesty integracyjne , Testy systemowe i Testy akceptacyjne. Wszystkie one prawdopodobnie oznaczają dokładnie to, co myślisz, że robią, ale pozwól, że wyjaśnię: Testy integracyjne są tym, co zrobilibyśmy, aby upewnić się, że te, jak wspomniano powyżej, „złożone funkcje”, prawidłowo współdziałają ze sobą. (np. upewnienie się, że kalkulator poradzi sobie z „3 + 7 * 4 - 2”) Testowanie systemu to testowanie ogólnego projektu konkretnego systemu; często istnieje wiele systemów złożonych funkcji współpracujących ze sobą w produkcie, więc grupujesz je w systemy i testujesz indywidualnie. (np. gdybyś budował kalkulator graficzny, najpierw zbudowałbyś „system” arytmetyczny do obsługi liczb, testując, aż zadziała zgodnie z przeznaczeniem, a następnie zbudowałbyś i przetestował „system” graficzny do obsługi wypłat, jak opierałby się na systemie arytmetycznym). Testy akceptacyjne to testy na poziomie użytkownika; sprawdza, czy wszystkie systemy mogą działać w synchronizacji, aby stworzyć gotowy produkt gotowy do zaakceptowania przez użytkowników (np. użytkowników testujących kalkulator). Twórcy oprogramowania mogą czasami zignorować ten ostatni krok procesu, ponieważ firmy często zlecają innym pracownikom oddzielne wdrażanie testów użytkowników (beta).

Jak napisać test JUnit w Javie?

Teraz, gdy masz już jaśniejsze wyobrażenie o korzyściach i ograniczeniach testów jednostkowych, spójrzmy na kod! Będziemy używać popularnego środowiska testowego Java o nazwie JUnit (innym popularnym jest TestNG, którego możesz również użyć, jeśli chcesz. Są one bardzo podobne pod względem składni; TestNG jest inspirowany JUnit). Możesz pobrać i zainstalować JUnit tutaj . W przypadku tego przykładowego kodu będziemy kontynuować od przykładu „kalkulatora naukowego”, o którym wspomniałem wcześniej; dość łatwo jest owinąć głowę, a kod testowy jest bardzo łatwy. Konwencjonalną praktyką jest pisanie oddzielnych klas testowych dla każdej z twoich klas, więc to właśnie zrobimy. Załóżmy, że w tym momencie mamy Math.javaplik ze wszystkimi funkcjami matematycznymi (w tym Math.add) i piszemyMathTests.javaplik w tym samym pakiecie. Teraz skonfigurujmy instrukcje importu i treść klasy: (MOŻLIWE PYTANIE DO WYWIAD JUnit: Możesz zostać zapytany, gdzie umieścić test JUnit i czy musisz importować pliki źródłowe. Jeśli piszesz swoje klasy testowe w tym samym pakiecie co swoje główne klasy, to nie potrzebujesz żadnych instrukcji importu dla plików źródłowych w klasie testowej. W przeciwnym razie upewnij się, że importujesz pliki źródłowe!)

import org.junit.jupiter.Test;    //gives us the @Test header
import static org.junit.jupiter.api.Assertions.assertEquals; //less typing :) 

public class MathTests {
	//...
}
Pierwsza instrukcja importu daje nam @Testnagłówek. Piszemy „ @Test” bezpośrednio nad każdą definicją funkcji testowej, aby JUnit wiedział, że jest to pojedynczy test jednostkowy, który można uruchomić osobno. Później pokażę ci, jak możesz uruchomić określone testy jednostkowe za pomocą tego nagłówka. Druga instrukcja importu oszczędza nam trochę pisania. Podstawową funkcją JUnit, której używamy do testowania naszych funkcji, jest to Assert.assertEquals(), która przyjmuje dwa parametry (wartość rzeczywistą i wartość oczekiwaną) i upewnia się, że są one równe. Posiadanie tej drugiej instrukcji importu pozwala nam po prostu wpisać „ assertEquals(...” zamiast konieczności każdorazowego określania, którego pakietu jest częścią. Teraz napiszmy bardzo prosty przypadek testowy, aby sprawdzić, czy 2 + 2 to rzeczywiście 4!

import org.junit.jupiter.Test; // gives us the @Test header
import static org.junit.jupiter.api.Assertions.assertEquals; // less typing :) 


public class MathTests {
	@Test
	public void add_twoPlusTwo_returnsFour(){
	final int expected = 4;
	final int actual = Math.add(2, 2);
	assertEquals(“2+2 is 4”, actual, expected);
	}
}
Przeanalizujmy każdą z pięciu linii funkcji testowej i ich działanie: Linia 5: Ten @Testnagłówek określa, że ​​poniższa definicja funkcji add_twoPlusTwo_returnsFour()jest rzeczywiście funkcją testową, którą JUnit może uruchomić oddzielnie. Linia 6: To jest sygnatura funkcji dla naszego przypadku testowego. Przypadki testowe są zawsze bardzo pojedyncze; testują tylko jeden konkretny przykład, na przykład 2+2=4. Zwyczajem jest nazywanie przypadków testowych w postaci „ [function]_[params]_returns[expected]()”, gdzie [function]to nazwa testowanej funkcji, [params]konkretne przykładowe parametry, które testujesz, oraz [expected]oczekiwana wartość zwracana przez funkcję. Funkcje testowe prawie zawsze zwracają typ „ void”, ponieważ głównym celem całej funkcji jest wykonanieassertEquals, który wyświetli na konsoli, czy twój test przeszedł pomyślnie; nie potrzebujesz żadnych innych danych, aby zostać zwrócone gdziekolwiek. Linia 7: Deklarujemy finalzmienną „ ” zwracanego typu Math.add (int)i zgodnie z konwencją nazywamy ją „oczekiwana”. Jego wartością jest oczekiwana przez nas odpowiedź (4). Linia 8: Deklarujemy finalzmienną „ ” zwracanego typu Math.add (int)i zgodnie z konwencją nazywamy ją „rzeczywistą”. Jego wartość jest wynikiem Math.add(2, 2). Linia 9: Złota linia. To jest linia, która porównuje rzeczywiste i oczekiwane i mówi nam, że zdaliśmy test tylko wtedy, gdy są one równe. Pierwszy przekazany parametr „2+2 to 4” to opis funkcji testowej.

Co jeśli moja funkcja powinna zgłosić wyjątek?

Jeśli twój konkretny przykład testowy powinien rzucić wyjątek zamiast twierdzić, że rzeczywista i oczekiwana wartość są równe, JUnit ma sposób na wyjaśnienie tego w nagłówku @Test. Spójrzmy na przykład poniżej. Zakładając, że mamy funkcję o Math.javanazwie Math.divide, chcemy się upewnić, że dane wejściowe nie mogą być dzielone przez 0. Zamiast tego próba wywołania Math.divide(a, 0)dowolnej wartości „a” powinna zgłosić wyjątek ( ArithmeticException.class). Określamy więc w nagłówku jako takie:

import org.junit.jupiter.Test; // gives us the @Test header
import static org.junit.jupiter.api.Assertions.assertEquals; // less typing :) 


public class MathTests {
	@Test (expectedExceptions = ArithmeticException.class)
	public void divide_byZero_throwsException() throws ArithmeticException{
	Math.divide(1, 0);
	}
}
Możesz mieć więcej niż jeden wyjątek dla expectedExceptions, po prostu upewnij się, że używasz nawiasów i przecinków, aby wymienić swoje klasy wyjątków, jako takie:

expectedException = {FirstException.class, SecondException.class, … }

Jak uruchomić testy JUnit w Javie?

Jak dodać JUnit do IntelliJ: https://stackoverflow.com/questions/19330832/setting-up-junit-with-intellij-idea Możesz uruchomić swój projekt tak, jak normalnie uruchamiałbyś testy. Uruchomienie wszystkich testów w klasie testowej spowoduje ich uruchomienie w kolejności alfabetycznej. W JUnit 5 możesz dodać priorytet do testów, dodając znacznik @Order. Przykład:

@TestMethodOrder(OrderAnnotation.class)
public class Tests {
…
@Test
@Order(2)
public void a_test() { … }

@Test
@Order (1)
public void b_test() { … }
…
}
Mimo że a_test()występuje przed b_test()alfabetycznie i w kodzie, b_test()będzie działać wcześniej a_test()tutaj, ponieważ 1 występuje przed 2 w kolejności. To tyle jeśli chodzi o podstawy JUnit. Teraz zajmijmy się kilkoma typowymi pytaniami do wywiadu JUnit i dowiedzmy się więcej o JUnit po drodze!

Pytania do wywiadu JUnit (dodatkowe informacje)

Tutaj zebrałem najpopularniejsze pytania do rozmowy kwalifikacyjnej JUnit. Jeśli masz coś do dodania — zrób to w komentarzach poniżej. P: Jaką metodę możesz wywołać w swojej metodzie testowej, aby automatycznie nie zdać testu? A: fail("opis błędu tutaj!"); P: Testujesz klasę Psa; aby przetestować obiekt Dog, musisz utworzyć jego instancję, zanim będzie można na nim przeprowadzić testy. Więc piszesz funkcję setUp(), aby utworzyć instancję psa. Chcesz uruchomić tę funkcję tylko raz podczas wszystkich testów. Co należy umieścić bezpośrednio nad sygnaturą funkcji setUp(), aby JUnit wiedział, że musi uruchomić setUp() przed uruchomieniem testów? O: @BeforeClass (@BeforeAll w JUnit 5) P:Jaka musi być sygnatura funkcji opisanej powyżej funkcji setUp()? A: publiczna pustka statyczna. Każda funkcja z @BeforeClass (@BeforeAll w JUnit 5) lub @AfterClass (@AfterAll w JUnit 5) musi być statyczna. P: Zakończyłeś testowanie klasy Pies. Piszesz funkcję void tearDown() , która po każdym teście czyści dane i drukuje informacje do konsoli. Chcesz, aby ta funkcja była uruchamiana po każdym teście. Co należy umieścić bezpośrednio nad sygnaturą funkcji tearDown() , aby JUnit wiedział, że ma uruchomić tearDown() po uruchomieniu każdego testu? A: @Po (@AfterEach w JUnit 5)
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION