Testów jednostkowych - Unit testing

W programowaniu komputerowym , testowanie jednostkowe to testowania oprogramowania metodę, dzięki której poszczególne jednostki kodu źródłowego -Służy się z jednego lub większej liczby programów komputerowych modułów wraz z powiązanymi danymi sterującymi, wykorzystanie procedur i procedur operacyjnych, są testowane w celu ustalenia, czy są one zdatne do użytku .

Opis

Testy jednostkowe są zazwyczaj automatycznymi testami pisanymi i uruchamianymi przez programistów, aby upewnić się, że sekcja aplikacji (znana jako „jednostka”) spełnia projekt i zachowuje się zgodnie z przeznaczeniem. W programowaniu proceduralnym jednostka może być całym modułem, ale częściej jest to pojedyncza funkcja lub procedura. W programowaniu obiektowym jednostka jest często całym interfejsem, takim jak klasa lub pojedyncza metoda. Pisząc testy najpierw dla najmniejszych testowalnych jednostek, a następnie złożone zachowania między nimi, można zbudować kompleksowe testy dla złożonych aplikacji.

Aby wyizolować problemy, które mogą się pojawić, każdy przypadek testowy należy przetestować niezależnie. Substytuty, takie jak skróty metod , próbne obiekty , podróbki i wiązki testowe mogą być używane do wspomagania testowania modułu w izolacji.

Podczas opracowywania programista może zakodować kryteria lub wyniki, o których wiadomo, że są dobre, w teście, aby zweryfikować poprawność jednostki. Podczas wykonywania przypadków testowych frameworki rejestrują testy, które nie spełniają kryteriów, i zgłaszają je w podsumowaniu. W tym celu najczęściej stosowanym podejściem jest test - funkcja - wartość oczekiwana.

Pisanie i utrzymywanie testów jednostkowych można przyspieszyć za pomocą testów sparametryzowanych . Umożliwiają one wielokrotne wykonanie jednego testu z różnymi zestawami danych wejściowych, zmniejszając w ten sposób powielanie kodu testowego. W przeciwieństwie do tradycyjnych testów jednostkowych, które zwykle są metodami zamkniętymi i testowymi warunkami niezmiennymi, testy parametryczne przyjmują dowolny zestaw parametrów. Testy parametryczne są obsługiwane przez TestNG , JUnit i jego odpowiednik .Net, XUnit . Odpowiednie parametry dla testów jednostkowych mogą być dostarczane ręcznie lub w niektórych przypadkach są automatycznie generowane przez framework testowy. W ostatnich latach dodano obsługę pisania potężniejszych (jednostkowych) testów, wykorzystujących koncepcję teorii, przypadków testowych, które wykonują te same kroki, ale przy użyciu danych testowych generowanych w czasie wykonywania, w przeciwieństwie do zwykłych testów parametrycznych, które wykorzystują te same kroki wykonania z zestawami wejściowymi które są wstępnie zdefiniowane.

Zalety

Celem testów jednostkowych jest wyizolowanie każdej części programu i wykazanie, że poszczególne części są poprawne. Test jednostkowy zapewnia ścisłą, pisemną umowę, którą musi spełniać fragment kodu. W rezultacie daje kilka korzyści.

Testy jednostkowe znajdują problemy na wczesnym etapie cyklu rozwojowego . Obejmuje to zarówno błędy w implementacji programisty, jak i wady lub brakujące części specyfikacji urządzenia. Proces pisania dokładnego zestawu testów zmusza autora do przemyślenia danych wejściowych, wyjściowych i warunków błędów, a tym samym dokładniejszego zdefiniowania pożądanego zachowania jednostki. Koszt znalezienia błędu przed rozpoczęciem kodowania lub przy pierwszym napisaniu kodu jest znacznie niższy niż koszt późniejszego wykrycia, zidentyfikowania i poprawienia błędu. Błędy w wydanym kodzie mogą również powodować kosztowne problemy dla użytkowników końcowych oprogramowania. Kod może być niemożliwy lub trudny do testowania jednostkowego, jeśli jest źle napisany, dlatego testowanie jednostkowe może zmusić programistów do lepszego ustrukturyzowania funkcji i obiektów.

W programowaniu opartym na testach (TDD), które jest często używane zarówno w programowaniu ekstremalnym, jak i scrum , testy jednostkowe są tworzone przed napisaniem samego kodu. Gdy testy zakończą się pomyślnie, ten kod jest uważany za kompletny. Te same testy jednostkowe są często przeprowadzane w odniesieniu do tej funkcji, ponieważ większa baza kodu jest opracowywana w miarę zmiany kodu lub poprzez zautomatyzowany proces z kompilacją. Jeśli testy jednostkowe nie powiodą się, jest to uważane za błąd w zmienionym kodzie lub samych testach. Testy jednostkowe umożliwiają następnie łatwe prześledzenie lokalizacji usterki lub awarii. Ponieważ testy jednostkowe ostrzegają zespół programistów o problemie przed przekazaniem kodu testerom lub klientom, potencjalne problemy są wykrywane na wczesnym etapie procesu tworzenia.

Testy jednostkowe pozwalają programiście na refaktoryzację kodu lub aktualizację bibliotek systemowych w późniejszym terminie i upewnienie się, że moduł nadal działa poprawnie (np. w testach regresyjnych ). Procedura polega na napisaniu przypadków testowych dla wszystkich funkcji i metod, tak aby za każdym razem, gdy zmiana powoduje błąd, można go szybko zidentyfikować. Testy jednostkowe wykrywają zmiany, które mogą zerwać umowę projektową .

Testów jednostkowych może zmniejszyć niepewność w jednostkach siebie i mogą być wykorzystane w oddolnego podejścia testowania stylu. Testując najpierw części programu, a następnie sumę jego części, testowanie integracyjne staje się znacznie łatwiejsze.

Testy jednostkowe zapewniają rodzaj żywej dokumentacji systemu. Deweloperzy, którzy chcą dowiedzieć się, jakie funkcje zapewnia jednostka i jak z niej korzystać, mogą przyjrzeć się testom jednostkowym, aby uzyskać podstawową wiedzę na temat interfejsu jednostki ( API ).

Przypadki testów jednostkowych zawierają cechy, które są krytyczne dla powodzenia jednostki. Te cechy mogą wskazywać na właściwe/niewłaściwe użycie jednostki, a także negatywne zachowania, które mają zostać uwięzione przez jednostkę. Przypadek testu jednostkowego sam w sobie dokumentuje te krytyczne cechy, chociaż wiele środowisk programistycznych nie polega wyłącznie na kodzie dokumentującym produkt w fazie rozwoju.

Gdy oprogramowanie jest tworzone przy użyciu podejścia opartego na testach, połączenie napisania testu jednostkowego w celu określenia interfejsu oraz czynności refaktoryzacji wykonywanych po pomyślnym zakończeniu testu może zastąpić formalny projekt. Każdy test jednostkowy może być postrzegany jako element projektu określający klasy, metody i obserwowalne zachowanie.

Ograniczenia i wady

Testowanie nie wykryje każdego błędu w programie, ponieważ nie może ocenić każdej ścieżki wykonania w żadnym innym programie niż najbardziej trywialne. Ten problem jest nadzbiorem problemu zatrzymania , który jest nierozstrzygnięty . To samo dotyczy testów jednostkowych. Ponadto testowanie jednostkowe z definicji testuje tylko funkcjonalność samych jednostek. W związku z tym nie wykryje błędów integracji ani szerszych błędów na poziomie systemu (takich jak funkcje wykonywane w wielu jednostkach lub niefunkcjonalne obszary testowe, takie jak wydajność ). Testy jednostkowe powinny być wykonywane w połączeniu z innymi działaniami związanymi z testowaniem oprogramowania , ponieważ mogą one jedynie wykazać obecność lub brak określonych błędów; nie mogą udowodnić całkowitego braku błędów. Aby zagwarantować prawidłowe zachowanie dla każdej ścieżki wykonania i każdego możliwego wejścia oraz zapewnić brak błędów, wymagane są inne techniki, a mianowicie zastosowanie metod formalnych do udowodnienia, że ​​komponent oprogramowania nie zachowuje się nieoczekiwanie.

Skomplikowana hierarchia testów jednostkowych nie oznacza testów integracyjnych. Integracja z jednostkami peryferyjnymi powinna być uwzględniana w testach integracyjnych, ale nie w testach jednostkowych. Testowanie integracyjne zazwyczaj nadal w dużym stopniu opiera się na ręcznym testowaniu przez ludzi ; Testowanie wysokiego poziomu lub testowanie o zakresie globalnym może być trudne do zautomatyzowania, tak że testowanie ręczne często wydaje się szybsze i tańsze.

Testowanie oprogramowania to problem kombinatoryczny. Na przykład, każda instrukcja decyzji logicznej wymaga co najmniej dwóch testów: jednego z wynikiem „prawda”, a drugiego z wynikiem „fałsz”. W rezultacie na każdy napisany wiersz kodu programiści często potrzebują od 3 do 5 wierszy kodu testowego. To oczywiście wymaga czasu, a jego inwestycja może nie być warta wysiłku. Istnieją problemy, których w ogóle nie da się łatwo przetestować – na przykład te, które są niedeterministyczne lub obejmują wiele wątków . Ponadto kod testu jednostkowego może być co najmniej tak samo błędny, jak kod, który testuje. Fred Brooks w The Mythical Man-Month cytuje: „Nigdy nie płyń w morze z dwoma chronometrami; weź jeden lub trzy”. To znaczy, jeśli dwa chronometry są sprzeczne, skąd wiesz, który z nich jest poprawny?

Kolejnym wyzwaniem związanym z pisaniem testów jednostkowych jest trudność tworzenia realistycznych i użytecznych testów. Konieczne jest stworzenie odpowiednich warunków początkowych, aby testowana część aplikacji zachowywała się jak część kompletnego systemu. Jeśli te warunki początkowe nie zostaną ustawione poprawnie, test nie będzie wykonywał kodu w realistycznym kontekście, co umniejsza wartość i dokładność wyników testów jednostkowych.

Aby uzyskać zamierzone korzyści z testów jednostkowych, w całym procesie tworzenia oprogramowania potrzebna jest rygorystyczna dyscyplina. Niezbędne jest prowadzenie dokładnych zapisów nie tylko przeprowadzonych testów, ale także wszystkich zmian dokonanych w kodzie źródłowym tej lub jakiejkolwiek innej jednostki w oprogramowaniu. Stosowanie kontroli wersji systemu jest niezbędna. Jeśli nowsza wersja urządzenia nie przejdzie pomyślnie określonego testu, który wcześniej przeszła, oprogramowanie do kontroli wersji może dostarczyć listę zmian w kodzie źródłowym (jeśli zaszły), które zostały zastosowane w urządzeniu od tego czasu.

Niezbędne jest również wdrożenie zrównoważonego procesu zapewniającego, że awarie przypadków testowych są regularnie przeglądane i natychmiast usuwane. Jeśli taki proces nie zostanie zaimplementowany i zakorzeniony w przepływie pracy zespołu, aplikacja przestanie być zsynchronizowana z zestawem testów jednostkowych, zwiększając liczbę fałszywych alarmów i zmniejszając skuteczność zestawu testów.

Testowanie jednostkowe oprogramowania wbudowanego systemu stanowi wyjątkowe wyzwanie: ponieważ oprogramowanie jest opracowywane na innej platformie niż ta, na której będzie ostatecznie działać, nie można łatwo uruchomić programu testowego w rzeczywistym środowisku wdrażania, jak to jest możliwe w przypadku programów komputerowych.

Testy jednostkowe są najłatwiejsze, gdy metoda ma parametry wejściowe i pewne dane wyjściowe. Tworzenie testów jednostkowych nie jest tak łatwe, gdy główną funkcją metody jest interakcja z czymś zewnętrznym w stosunku do aplikacji. Na przykład metoda, która będzie działać z bazą danych, może wymagać utworzenia makiety interakcji z bazą danych, która prawdopodobnie nie będzie tak obszerna, jak rzeczywiste interakcje z bazą danych.

Przykład

Oto zestaw przypadków testowych w Javie, które określają szereg elementów implementacji. Po pierwsze, musi istnieć interfejs o nazwie Adder i klasa implementująca z konstruktorem z zerowymi argumentami o nazwie AdderImpl. Dalej stwierdza się, że interfejs Adder powinien mieć metodę o nazwie add, z dwoma parametrami całkowitymi, która zwraca inną liczbę całkowitą. Określa również zachowanie tej metody dla małego zakresu wartości w wielu metodach testowych.

import static org.junit.Assert.assertEquals;

import org.junit.Test;

public class TestAdder {

    @Test
    public void testSumPositiveNumbersOneAndOne() {
        Adder adder = new AdderImpl();
        assertEquals(2, adder.add(1, 1));
    }

    // can it add the positive numbers 1 and 2?
    @Test
    public void testSumPositiveNumbersOneAndTwo() {
        Adder adder = new AdderImpl();
        assertEquals(3, adder.add(1, 2));
    }

    // can it add the positive numbers 2 and 2?
    @Test
    public void testSumPositiveNumbersTwoAndTwo() {
        Adder adder = new AdderImpl();
        assertEquals(4, adder.add(2, 2));
    }

    // is zero neutral?
    @Test
    public void testSumZeroNeutral() {
        Adder adder = new AdderImpl();
        assertEquals(0, adder.add(0, 0));
    }

    // can it add the negative numbers -1 and -2?
    @Test
    public void testSumNegativeNumbers() {
        Adder adder = new AdderImpl();
        assertEquals(-3, adder.add(-1, -2));
    }

    // can it add a positive and a negative?
    @Test
    public void testSumPositiveAndNegative() {
        Adder adder = new AdderImpl();
        assertEquals(0, adder.add(-1, 1));
    }

    // how about larger numbers?
    @Test
    public void testSumLargeNumbers() {
        Adder adder = new AdderImpl();
        assertEquals(2222, adder.add(1234, 988));
    }
}

W tym przypadku testy jednostkowe, które zostały napisane jako pierwsze, pełnią rolę dokumentu projektowego, określającego formę i zachowanie pożądanego rozwiązania, a nie szczegóły implementacyjne, które pozostawia się programiście. Postępując zgodnie z praktyką „zrób najprostszą rzecz, która może zadziałać”, poniżej pokazano najprostsze rozwiązanie, które sprawi, że test przejdzie pomyślnie.

interface Adder {
    int add(int a, int b);
}
class AdderImpl implements Adder {
    public int add(int a, int b) {
        return a + b;
    }
}

Jako specyfikacje wykonywalne

Używanie testów jednostkowych jako specyfikacji projektu ma jedną istotną przewagę nad innymi metodami projektowania: sam dokument projektowy (same testy jednostkowe) może być użyty do weryfikacji implementacji. Testy nigdy nie przejdą, chyba że deweloper zaimplementuje rozwiązanie zgodnie z projektem.

Testom jednostkowym brakuje części dostępności specyfikacji diagramatycznej, takiej jak diagram UML , ale mogą one być generowane na podstawie testów jednostkowych za pomocą zautomatyzowanych narzędzi. Większość nowoczesnych języków ma darmowe narzędzia (zazwyczaj dostępne jako rozszerzenia IDE ). Darmowe narzędzia, takie jak te oparte na frameworku xUnit , zlecają innemu systemowi graficzne renderowanie widoku do spożycia przez ludzi.

Aplikacje

Ekstremalne programowanie

Testy jednostkowe to podstawa programowania ekstremalnego , które opiera się na zautomatyzowanym frameworku testów jednostkowych . Ta zautomatyzowana struktura testów jednostkowych może być albo stroną trzecią, np. xUnit , albo stworzoną w ramach grupy programistycznej.

Programowanie ekstremalne wykorzystuje tworzenie testów jednostkowych do programowania sterowanego testami . Deweloper pisze test jednostkowy, który ujawnia wymagania dotyczące oprogramowania lub defekt. Ten test zakończy się niepowodzeniem, ponieważ wymaganie nie zostało jeszcze zaimplementowane lub celowo ujawnia defekt w istniejącym kodzie. Następnie programista pisze najprostszy kod, dzięki któremu test wraz z innymi testami przejdzie pomyślnie.

Większość kodu w systemie jest testowana jednostkowo, ale niekoniecznie wszystkie ścieżki w kodzie. Programowanie ekstremalne wymaga strategii „przetestuj wszystko, co może zepsuć”, zamiast tradycyjnej metody „przetestuj każdą ścieżkę wykonania”. Prowadzi to programistów do opracowania mniejszej liczby testów niż metody klasyczne, ale nie jest to tak naprawdę problem, a raczej ponowne stwierdzenie faktu, ponieważ metody klasyczne rzadko były przestrzegane wystarczająco metodycznie, aby wszystkie ścieżki wykonania zostały dokładnie przetestowane. Programowanie ekstremalne po prostu uznaje, że testowanie rzadko jest wyczerpujące (ponieważ często jest zbyt kosztowne i czasochłonne, aby było ekonomicznie opłacalne) i dostarcza wskazówek, jak skutecznie skoncentrować ograniczone zasoby.

Co najważniejsze, kod testowy jest uważany za artefakt projektowy pierwszej klasy, ponieważ jest utrzymywany w tej samej jakości co kod implementacji, z usuniętymi wszystkimi duplikatami. Deweloperzy udostępniają kod testów jednostkowych do repozytorium kodu w połączeniu z kodem, który testuje. Dokładne testowanie jednostkowe w Extreme Programming pozwala na korzyści wymienione powyżej, takie jak prostsze i pewniejsze tworzenie i refaktoryzacja kodu, uproszczona integracja kodu, dokładna dokumentacja i bardziej modułowe projekty. Te testy jednostkowe są również stale uruchamiane jako forma testu regresji .

Testy jednostkowe mają również kluczowe znaczenie dla koncepcji Emergent Design . Ponieważ powstający projekt jest silnie uzależniony od refaktoryzacji, testy jednostkowe są integralnym elementem.

Ramy testów jednostkowych

Struktury testów jednostkowych to najczęściej produkty innych firm, które nie są dystrybuowane jako część pakietu kompilatora. Pomagają uprościć proces testowania jednostkowego, ponieważ zostały opracowane dla wielu różnych języków .

Zasadniczo możliwe jest wykonywanie testów jednostkowych bez obsługi określonej struktury, pisząc kod klienta, który ćwiczy testowane jednostki i używa asercji , obsługi wyjątków lub innych mechanizmów przepływu sterowania do sygnalizowania niepowodzenia. Testowanie jednostkowe bez ram jest cenne, ponieważ istnieje bariera wejścia do przyjęcia testów jednostkowych; posiadanie skąpych testów jednostkowych jest niewiele lepsze niż ich brak, podczas gdy gdy framework jest już na miejscu, dodawanie testów jednostkowych staje się stosunkowo łatwe. W niektórych frameworkach brakuje wielu zaawansowanych funkcji testów jednostkowych lub muszą one zostać napisane ręcznie.

Obsługa testów jednostkowych na poziomie języka

Niektóre języki programowania bezpośrednio obsługują testowanie jednostkowe. Ich gramatyka pozwala na bezpośrednie deklarowanie testów jednostkowych bez importowania biblioteki (zarówno innej firmy, jak i standardowej). Ponadto warunki logiczne testów jednostkowych mogą być wyrażone w tej samej składni, co wyrażenia logiczne używane w kodzie testów niejednostkowych, takie jak to, co jest używane ifi whileinstrukcje.

Języki z wbudowaną obsługą testów jednostkowych obejmują:

Niektóre języki bez wbudowanej obsługi testów jednostkowych mają bardzo dobre biblioteki/frameworki do testów jednostkowych. Języki te obejmują:

Zobacz też

Bibliografia

Linki zewnętrzne