Jak działa testowanie jednostkowe?


23

Staram się, aby mój kod był bardziej niezawodny i czytałem o testowaniu jednostkowym, ale bardzo trudno mi znaleźć rzeczywiste użyteczne zastosowanie. Na przykład przykład z Wikipedii :

public class TestAdder {
    public void testSum() {
        Adder adder = new AdderImpl();
        assert(adder.add(1, 1) == 2);
        assert(adder.add(1, 2) == 3);
        assert(adder.add(2, 2) == 4);
        assert(adder.add(0, 0) == 0);
        assert(adder.add(-1, -2) == -3);
        assert(adder.add(-1, 1) == 0);
        assert(adder.add(1234, 988) == 2222);
    }
}

Uważam, że ten test jest całkowicie bezużyteczny, ponieważ musisz ręcznie obliczyć pożądany wynik i przetestować go, wydaje mi się, że lepszy byłby tutaj test jednostkowy

assert(adder.add(a, b) == (a+b));

ale to tylko kodowanie samej funkcji w teście. Czy ktoś może podać mi przykład, w którym testowanie jednostkowe jest rzeczywiście przydatne? Do twojej wiadomości Obecnie koduję głównie funkcje „proceduralne”, które pobierają ~ 10 logicznych i kilka liczb całkowitych i dają mi wynik int oparty na tym, wydaje mi się, że jedynym testem jednostkowym, który mógłbym zrobić, byłoby po prostu ponowne kodowanie algorytmu w test. edytuj: Powinienem był również sprecyzować, że dzieje się to podczas przenoszenia (prawdopodobnie źle zaprojektowanego) kodu ruby ​​(którego nie stworzyłem)


14
How does unit testing work?Nikt tak naprawdę nie wie :)
yannis

30
„musisz ręcznie obliczyć pożądany wynik”. Jak to jest „całkowicie bezużyteczne”? Jak inaczej możesz być pewien, że odpowiedź jest prawidłowa?
S.Lott,

9
@ S.Lott: Nazywa się postępem, w czasach starożytnych ludzie używali komputerów do łamania liczb i oszczędzania czasu, we współczesnych czasach ludzie spędzają czas, aby upewnić się, że komputery mogą zgniatać liczby: D
Koder

2
@ Koder: celem testów jednostkowych nie jest „chrupanie liczb i oszczędzanie czasu”;)
Andres F.,

7
@lezebulon: przykład z Wikipedii nie jest zbyt dobry, ale jest to problem z tym konkretnym przypadkiem testowym, a nie z testowaniem jednostkowym w ogóle. Około połowa danych testowych z tego przykładu nie dodaje nic nowego, co czyni je zbędnymi (boję się pomyśleć, co autor tego testu zrobiłby z bardziej złożonymi scenariuszami). Bardziej sensowny test podzieliłby dane testowe co najmniej w następujących scenariuszach: „czy można dodać liczby ujemne?”, „Czy zero jest neutralne?”, „Czy można dodać liczbę ujemną i dodatnią?”.
Andres F.,

Odpowiedzi:


26

Testy jednostkowe, jeśli testujesz wystarczająco małe jednostki, zawsze zapewniają oślepiająco oczywiste.

Powodem, add(x, y)dla którego pojawia się nawet wzmianka o teście jednostkowym, jest to, że jakiś czas później ktoś wejdzie addi umieści specjalny kod obsługi logiki podatkowej, nie wiedząc, że dodatek jest wszędzie używany.

Testy jednostkowe w dużej mierze dotyczą zasady asocjacji: jeśli A robi B, a B robi C, to A robi C. „A robi C” jest testem wyższego poziomu. Rozważmy na przykład następujący, całkowicie uzasadniony kod biznesowy:

public void LoginUser (string username, string password) {
    var user = db.FetchUser (username);

    if (user.Password != password)
        throw new Exception ("invalid password");

    var roles = db.FetchRoles (user);

    if (! roles.Contains ("member"))
        throw new Exception ("not a member");

    Session["user"] = user;
}

Na pierwszy rzut oka wygląda to na niesamowitą metodę testów jednostkowych, ponieważ ma bardzo wyraźny cel. Jednak robi około 5 różnych rzeczy. Każda rzecz ma prawidłowy i nieprawidłowy przypadek i spowoduje ogromną permutację testów jednostkowych. Idealnie jest to podzielone dalej:

public void LoginUser (string username, string password) {

    var user = _userRepo.FetchValidUser (username, password);

    _rolesRepo.CheckUserForRole (user, "member");

    _localStorage.StoreValue ("user", user);
}

Teraz sprowadzamy się do jednostek. Jeden test jednostkowy nie przejmuje się tym, co _userRepouważa za prawidłowe zachowanie FetchValidUser, tylko że się nazywa. Możesz użyć innego testu, aby upewnić się, co dokładnie stanowi prawidłowy użytkownik. Podobnie dla CheckUserForRole... oddzieliłeś swój test od wiedzy, jak wygląda struktura roli. Oddzieliłeś także cały program od ścisłego przywiązania Session. Wyobrażam sobie, że wszystkie brakujące elementy tutaj wyglądałyby tak:

class UserRepository : IUserRepository
{
    public User FetchValidUser (string username, string password)
    {
        var user = db.FetchUser (username);

        if (user.Password != password)
            throw new Exception ("invalid password");

        return user;
    }
}

class RoleRepository : IRoleRepository
{
    public void CheckUserForRole (User user, string role)
    {
        var roles = db.FetchRoles (user);

        if (! roles.Contains (role))
            throw new Exception ("not a member");
    }
}

class SessionStorage : ILocalStorage
{
    public void StoreValue (string key, object value)
    {
        Session[key] = value;
    }
}

Dokonując refaktoryzacji, osiągnąłeś kilka rzeczy na raz. Program jest znacznie bardziej pomocny w odrywaniu podstawowych struktur (możesz porzucić warstwę bazy danych dla NoSQL) lub płynnym dodawaniu blokowania, gdy uznasz, że Sessionnie jest bezpieczne dla wątków. Dałeś też teraz bardzo proste testy dla tych trzech zależności.

Mam nadzieję że to pomoże :)


13

Obecnie koduję głównie funkcje „proceduralne”, które pobierają ~ 10 logów i kilka liczb całkowitych i dają mi wynik int oparty na tym, wydaje mi się, że jedynym testem jednostkowym, który mógłbym zrobić, byłoby po prostu ponowne kodowanie algorytmu w teście

Jestem prawie pewien, że każda z twoich funkcji proceduralnych jest deterministyczna, więc zwraca określony wynik int dla każdego zestawu wartości wejściowych. Idealnie byłoby, gdybyś miał funkcjonalną specyfikację, z której możesz dowiedzieć się, jaki wynik powinieneś otrzymać dla niektórych zestawów wartości wejściowych. W przeciwnym razie można uruchomić kod ruby ​​(który, jak się zakłada, działa poprawnie) dla niektórych zestawów wartości wejściowych i zapisać wyniki. Następnie musisz ZWIĘKSZYĆ KOD wyniki w teście. Test ma być dowodem na to, że Twój kod rzeczywiście generuje wyniki, o których wiadomo, że są poprawne .


+1 za uruchomienie istniejącego kodu i zapisanie wyników. W tej sytuacji jest to prawdopodobnie pragmatyczne podejście.
MarkJ

12

Ponieważ wydaje się, że nikt inny nie podał faktycznego przykładu:

    public void testRoman() {
        RomanNumeral numeral = new RomanNumeral();
        assert( numeral.toRoman(1) == "I" )
        assert( numeral.toRoman(4) == "IV" )
        assert( numeral.toRoman(5) == "V" )
        assert( numeral.toRoman(9) == "IX" )
        assert( numeral.toRoman(10) == "X" )
    }
    public void testSqrt() {
        assert( sqrt(4) == 2 )
        assert( sqrt(9) == 3 )
    }

Mówisz:

Uważam, że ten test jest całkowicie bezużyteczny, ponieważ musisz ręcznie obliczyć pożądany wynik i przetestować go

Ale chodzi o to, że znacznie rzadziej popełniasz błąd (lub przynajmniej bardziej prawdopodobne, że zauważysz swoje błędy) podczas wykonywania obliczeń ręcznych niż podczas kodowania.

Jak prawdopodobne jest, że popełnisz błąd w kodzie konwersji dziesiętnej na rzymską? Całkiem prawdopodobne. Jak prawdopodobne jest, że popełnisz błąd przy ręcznej konwersji cyfr dziesiętnych na rzymskie? Niezbyt prawdopodobne. Dlatego testujemy na podstawie ręcznych obliczeń.

Jak prawdopodobne jest, że pomylisz się, gdy zastosujesz funkcję pierwiastka kwadratowego? Całkiem prawdopodobne. Jak prawdopodobne jest, że popełnisz błąd przy ręcznym obliczaniu pierwiastka kwadratowego? Prawdopodobnie bardziej prawdopodobne. Ale dzięki sqrt możesz użyć kalkulatora, aby uzyskać odpowiedzi.

Do Twojej wiadomości Obecnie koduję głównie funkcje „proceduralne”, które pobierają ~ 10 logicznych i kilka liczb całkowitych i dają mi wynik int oparty na tym, wydaje mi się, że jedynym testem jednostkowym, który mógłbym zrobić, byłoby po prostu ponowne kodowanie algorytmu w test

Spekuluję więc o tym, co się tutaj dzieje. Twoje funkcje są dość skomplikowane, więc trudno jest ustalić na podstawie danych wejściowych, jakie powinny być dane wyjściowe. Aby to zrobić, musisz ręcznie uruchomić (w swojej głowie) funkcję, aby dowiedzieć się, jaki jest wynik. Zrozumiałe, że wydaje się to trochę bezużyteczne i podatne na błędy.

Kluczem jest to, że chcesz znaleźć prawidłowe dane wyjściowe. Ale musisz przetestować te wyniki pod kątem czegoś, o czym wiadomo, że jest poprawne. Nie warto pisać własnego algorytmu, aby to obliczyć, ponieważ może to być bardzo niepoprawne. W takim przypadku zbyt trudno jest ręcznie obliczyć wartości.

Wrócę do kodu ruby ​​i wykonam te oryginalne funkcje z różnymi parametrami. Wziąłbym wyniki kodu ruby ​​i umieściłem je w teście jednostkowym. W ten sposób nie musisz wykonywać ręcznych obliczeń. Ale testujesz na podstawie oryginalnego kodu. To powinno pomóc utrzymać te same wyniki, ale jeśli w oryginale są błędy, to ci nie pomoże. Zasadniczo możesz traktować oryginalny kod jak kalkulator w przykładzie sqrt.

Jeśli pokazałeś faktyczny kod, który przenosisz, możemy udzielić bardziej szczegółowych informacji zwrotnych na temat sposobu rozwiązania problemu.


A jeśli kod Ruby zawiera błąd, o którym nie wiesz, którego nie ma w twoim nowym kodzie, a Twój kod nie przejdzie testu jednostkowego na podstawie danych wyjściowych Rubiego, wówczas dochodzenie w sprawie przyczyny niepowodzenia zakończy się potwierdzeniem i spowoduje znaleziono ukryty błąd Ruby. To całkiem fajne.
Adam Wuerl,

11

Wydaje mi się, że jedynym testem jednostkowym, jaki mógłbym wykonać, byłoby po prostu ponowne zakodowanie algorytmu w teście

Masz prawie rację dla takiej prostej klasy.

Wypróbuj bardziej skomplikowany kalkulator. Jak kalkulator kręgli.

Wartość testów jednostkowych jest łatwiej dostrzegalna, gdy masz bardziej złożone reguły „biznesowe” z różnymi scenariuszami do przetestowania.

Nie twierdzę, że nie powinieneś testować przebiegu kalkulatora młyna (czy twoje konto kalkulatora ma problemy z wartościami takimi jak 1/3, których nie można przedstawić? Co to robi z dzieleniem przez zero?), Ale zobaczysz zwiększaj wartość, jeśli testujesz coś z większą liczbą oddziałów, aby uzyskać zasięg.


4
+1 za zauważenie, że staje się bardziej użyteczny w przypadku skomplikowanych funkcji. Co jeśli zdecydujesz się rozszerzyć adder.add () na wartości zmiennoprzecinkowe? Matryce? Leger wartości kont?
joshin4colours

6

Pomimo religijnej gorliwości około 100% pokrycia kodu, powiem, że nie każda metoda powinna być testowana jednostkowo. Tylko funkcjonalność zawierająca sensowną logikę biznesową. Funkcja, która po prostu dodaje liczbę, nie ma sensu testować.

Obecnie koduję głównie funkcje „proceduralne”, które pobierają ~ 10 logów i kilka liczb całkowitych i dają wynik int na podstawie tego

Tam jest twój prawdziwy problem. Jeśli testowanie jednostkowe wydaje się nienaturalnie trudne lub bezcelowe, oznacza to prawdopodobnie wadę projektową. Gdyby był bardziej zorientowany obiektowo, twoje sygnatury metod nie byłyby tak masywne i byłoby mniej możliwych danych wejściowych do przetestowania.

Nie muszę wchodzić do mojego OO, jest lepszy od programowania proceduralnego ...


w tym przypadku „podpis” metody nie jest ogromny, po prostu czytam ze std :: vector <bool>, który jest członkiem klasy. Powinienem był również wiedzieć, że przesyłam (prawdopodobnie źle zaprojektowany) kod ruby ​​(którego nie stworzyłem)
lezebulon

2
@lezebulon Niezależnie od tego, czy istnieje tyle możliwych danych wejściowych dla jednej metody do zaakceptowania, ta metoda robi zbyt wiele .
wałek klonowy

3

Z mojego punktu widzenia testy jednostkowe są nawet przydatne w twojej małej klasie adderów: nie myśl o „przekodowaniu” algorytmu i myśl o nim jak o czarnej skrzynce z jedyną wiedzą, jaką posiadasz, o funkcjonalnym zachowaniu (jeśli znasz dzięki szybkiemu mnożeniu znasz kilka szybszych, ale bardziej złożonych prób niż użycie „a * b”) i publicznego interfejsu. Następnie powinieneś zadać sobie pytanie „Co do diabła może pójść nie tak?” ...

W większości przypadków dzieje się to na granicy (widzę, że testujesz już dodawanie tych wzorów ++, -, + -, 00 - czas na ich uzupełnienie przez - +, 0+, 0-, +0, -0). Pomyśl o tym, co dzieje się w MAX_INT i MIN_INT podczas dodawania lub odejmowania (dodawania negatywów;)). Lub postaraj się, aby twoje testy wyglądały dokładnie tak, jak dzieje się w okolicach zera.

Podsumowując, sekret jest bardzo prosty (być może także dla bardziej złożonych;)) dla prostych klas: pomyśl o umowach swojej klasy (zobacz projekt po umowie), a następnie przetestuj je. Im lepiej poznasz swoje inv, pre i post jako „uzupełniające” twoje testy.

Wskazówka dla klas testowych: spróbuj zapisać tylko jedną aser w metodzie. Nadaj metodom dobre nazwy (np. „TestAddingToMaxInt”, „testAddingTwoNegatives”), aby uzyskać najlepszą informację zwrotną w przypadku niepowodzenia testu po zmianie kodu.


2

Zamiast testować ręcznie obliczoną wartość zwracaną lub powielając logikę w teście w celu obliczenia oczekiwanej wartości zwracanej, przetestuj wartość zwracaną dla oczekiwanej właściwości.

Na przykład, jeśli chcesz przetestować metodę odwracającą macierz, nie chcesz ręcznie odwracać wartości wejściowej, powinieneś pomnożyć wartość zwracaną przez dane wejściowe i sprawdzić, czy otrzymujesz macierz tożsamości.

Aby zastosować to podejście do metody, należy wziąć pod uwagę jej cel i semantykę, aby określić, jakie właściwości będzie mieć wartość zwracana w stosunku do danych wejściowych.


2

Testy jednostkowe są narzędziem produktywności. Otrzymujesz żądanie zmiany, zaimplementuj je, a następnie uruchom kod za pomocą gambit testu jednostkowego. To automatyczne testowanie oszczędza czas.

I feel that this test is totally useless, because you are required to manually compute the wanted result and test it, I feel like a better unit test here would be

Punkt sporny. Test w tym przykładzie pokazuje, jak utworzyć instancję klasy i przeprowadzić ją przez serię testów. Koncentrując się na szczegółach pojedynczego wdrożenia, brakuje lasu dla drzew.

Can someone provide me with an example where unit testing is actually useful?

Masz podmiot pracownika. Podmiot zawiera nazwę i adres. Klient decyduje się na dodanie pola ReportsTo.

void TestBusinessLayer()
{
   int employeeID = 1234
   Employee employee = Employee.GetEmployee(employeeID)
   BusinessLayer bl = new BusinessLayer()
   Assert.isTrue(bl.Add(employee))//assume Add returns true on pass
}

To podstawowy test BL do pracy z pracownikiem. Kod przejdzie / zakończy się właśnie wprowadzoną zmianą schematu. Pamiętaj, że twierdzenia nie są jedyną rzeczą, którą robi test. Uruchomienie kodu zapewnia również, że nie zostaną wprowadzone żadne wyjątki.

Z czasem posiadanie testów ułatwia wprowadzanie zmian w ogóle. Kod jest automatycznie testowany pod kątem wyjątków i podjętych przez ciebie asercji. Pozwala to uniknąć dużych kosztów ogólnych spowodowanych ręcznym testowaniem przez grupę kontroli jakości. Chociaż interfejs użytkownika jest nadal dość trudny do zautomatyzowania, pozostałe warstwy są na ogół bardzo łatwe, zakładając, że prawidłowo używasz modyfikatorów dostępu.

I feel like the only unit testing I could do would be to simply re-code the algorithm in the test.

Nawet logika proceduralna jest łatwo zamknięta w funkcji. Obuduj, utwórz instancję i przekaż int / primitive do przetestowania (lub próbnego obiektu). Nie kopiuj wklej kodu do testu jednostkowego. To pokonuje SUCHO. Powoduje to także całkowite pokonanie testu, ponieważ nie testujesz kodu, ale jego kopia. Jeśli kod, który powinien zostać przetestowany, zmieni się, test nadal się powiedzie!


<pedantry> „gamut”, a nie „gambit”. </
pedantry

@chao lol uczyć się czegoś nowego każdego dnia.
P.Brian.Mackey

2

Biorąc twój przykład (z odrobiną refaktoryzacji),

assert(a + b, math.add(a, b));

nie pomaga:

  • zrozumieć, jak się math.addzachowuje wewnętrznie,
  • wiedzieć, co się stanie z przypadkowymi przypadkami.

To prawie tak, jak powiedzenie:

  • Jeśli chcesz wiedzieć, co robi metoda, idź i zobacz setki wierszy kodu źródłowego (ponieważ tak, math.add może zawierać setki LOC; patrz poniżej).
  • Nie przejmuję się, czy metoda działa poprawnie. Jest ok, jeśli zarówno oczekiwane, jak i rzeczywiste wartości różnią się od tego, czego naprawdę oczekiwałem .

Oznacza to również, że nie musisz dodawać testów takich jak:

assert(3, math.add(1, 2));
assert(4, math.add(2, 2));

Nie pomagają ani, przynajmniej, gdy raz uczynisz pierwsze twierdzenie, drugie nie przyniesie nic pożytecznego.

Zamiast tego:

const numeric Pi = 3.1415926535897932384626433832795;
const numeric Expected = 4.1415926535897932384626433832795;
assert(Expected, math.add(Pi, 1),
    "Adding an integer to a long numeric doesn't give a long numeric result.");
assert(Expected, math.add(1, Pi),
    "Adding a long numeric to an integer doesn't give a long numeric result.");

Jest to zrozumiałe i cholernie pomocne zarówno dla ciebie, jak i dla osoby, która zachowa kod źródłowy później. Wyobraź sobie, że ta osoba wprowadza niewielką modyfikację, aby math.adduprościć kod i zoptymalizować wydajność, i widzi wynik testu w następujący sposób:

Test TestNumeric() failed on assertion 2, line 5: Adding a long numeric to an
integer doesn't give a long numeric result.

Expected value: 4.1415926535897932384626433832795
Actual value: 4

osoba ta natychmiast zrozumie, że nowo zmodyfikowana metoda zależy od kolejności argumentów: jeśli pierwszy argument jest liczbą całkowitą, a drugi długą liczbą, wynik będzie liczbą całkowitą, podczas gdy oczekiwano długiej liczby.

W ten sam sposób uzyskanie rzeczywistej wartości 4.141592pierwszego stwierdzenia jest oczywiste: wiesz, że oczekuje się, że metoda poradzi sobie z dużą precyzją , ale tak naprawdę się nie powiedzie.

Z tego samego powodu w niektórych językach mogą mieć sens dwa następujące stwierdzenia:

// We don't expect a concatenation. `math` library is not intended for this.
assert(0, math.add("Hello", "World"));

// We expect the method to convert every string as if it was a decimal.
assert(5, math.add("0x2F", 5));

A co z:

assert(numeric.Infinity, math.add(numeric.Infinity, 1));

Nie trzeba tłumaczyć: chcesz, aby twoja metoda była w stanie poprawnie radzić sobie z nieskończonością. Wyjście poza nieskończoność lub zgłoszenie wyjątku nie jest oczekiwanym zachowaniem.

A może, w zależności od języka, będzie to miało większy sens?

/**
 * Ensures that when adding numbers which exceed the maximum value, the method
 * fails with OverflowException, instead of restarting at numeric.Minimum + 1.
 */
TestOverflow()
{
    UnitTest.ExpectException(ofType(OverflowException));

    numeric result = math.add(numeric.Maximum, 1));

    UnitTest.Fail("The tested code succeeded, while an OverflowException was
        expected.");
}

1

W przypadku bardzo prostej funkcji, takiej jak dodawanie, testowanie można uznać za niepotrzebne, ale ponieważ funkcje stają się coraz bardziej złożone, staje się coraz bardziej oczywiste, dlaczego testowanie jest konieczne.

Pomyśl o tym, co robisz podczas programowania (bez testów jednostkowych). Zwykle piszesz kod, uruchamiasz go, widzisz, że działa i przechodzisz do następnej rzeczy, prawda? Gdy piszesz więcej kodu, szczególnie w bardzo dużym systemie / GUI / witrynie internetowej, okazuje się, że musisz robić coraz więcej „uruchamiania i sprawdzania, czy to działa”. Musisz tego spróbować i spróbować. Następnie dokonujesz kilku zmian i musisz spróbować tych samych rzeczy od nowa. Staje się bardzo oczywiste, że możesz zaoszczędzić czas, pisząc testy jednostkowe, które zautomatyzują całą część „uruchamiania i sprawdzania, czy to działa”.

Gdy twoje projekty stają się coraz większe, liczba rzeczy, które musisz „uruchomić i zobaczyć, czy to działa”, staje się nierealna. Tak więc kończysz tylko uruchamianie i wypróbowywanie kilku głównych komponentów GUI / projektu, a następnie masz nadzieję, że wszystko inne jest w porządku. To jest przepis na katastrofę. Oczywiście, jako człowiek, nie możesz wielokrotnie testować każdej możliwej sytuacji, z której mogą skorzystać Twoi klienci, jeśli GUI jest używany przez dosłownie setki osób. Jeśli posiadasz testy jednostkowe, możesz po prostu uruchomić test przed wysłaniem stabilnej wersji lub nawet przed przekazaniem do centralnego repozytorium (jeśli twoje miejsce pracy korzysta z niego). A jeśli później pojawią się jakieś błędy, możesz po prostu dodać test jednostkowy, aby sprawdzić go w przyszłości.


1

Jedną z zalet pisania testów jednostkowych jest to, że pomaga pisać bardziej niezawodny kod, zmuszając do myślenia o przypadkowych przypadkach. Co powiesz na testowanie niektórych przypadków krawędzi, takich jak przepełnienie liczb całkowitych, obcięcie dziesiętne lub obsługa wartości zerowych dla parametrów?


0

Być może zakładasz, że add () został zaimplementowany z instrukcją ADD. Jeśli jakiś młodszy programista lub inżynier sprzętu ponownie zaimplementował funkcję add () za pomocą ANDS / ORS / XORS, bitów odwraca i przesuwa, możesz chcieć przetestować to urządzenie pod kątem instrukcji ADD.

Zasadniczo, jeśli zastąpisz wnętrzności add () lub testowanej jednostki losową generacją liczb lub generatora wyjściowego, skąd wiesz, że coś się zepsuło? Zakoduj tę wiedzę w testach jednostkowych. Jeśli nikt nie jest w stanie stwierdzić, czy jest zepsuty, po prostu wpisz kod rand () i idź do domu, praca jest skończona.


0

Mógłbym przeoczyć ją wśród wszystkich odpowiedzi, ale dla mnie głównym motorem testów jednostkowych jest nie tyle udowodnienie dzisiaj poprawności metody, ale to, że udowadnia ona ciągłą poprawność tej metody, gdy [kiedykolwiek] ją zmienisz .

Weź prostą funkcję, np. Zwracanie liczby elementów w niektórych kolekcjach. Dzisiaj, gdy twoja lista oparta jest na jednej wewnętrznej strukturze danych, którą dobrze znasz, możesz pomyśleć, że ta metoda jest tak boleśnie oczywista, że ​​nie potrzebujesz do niej testu. Następnie, za kilka miesięcy lub lat, ty (lub ktoś inny ) zdecydujesz się zastąpić wewnętrzną strukturę listy. Nadal musisz wiedzieć, że getCount () zwraca poprawną wartość.

Właśnie tam twoje testy jednostkowe naprawdę się sprawdzają.

Możesz zmienić wewnętrzną implementację swojego kodu, ale dla wszystkich odbiorców tego kodu wynik pozostaje taki sam.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.