Testowanie jednostkowe elementów wewnętrznych

14

W jakim stopniu jednostka testuje wewnętrzne / prywatne komponenty klasy / modułu / pakietu / etc? Czy w ogóle je testujesz, czy tylko testujesz interfejs do świata zewnętrznego? Przykładem tych wewnętrznych metod są metody prywatne.

Jako przykład wyobraźmy sobie parser rekurencyjnego zejścia , który ma kilka wewnętrznych procedur (funkcji / metod) wywoływanych z jednej centralnej procedury. Jedynym interfejsem do świata zewnętrznego jest centralna procedura, która pobiera ciąg znaków i zwraca przeanalizowane informacje. Pozostałe procedury analizują różne części łańcucha i są wywoływane albo z procedury centralnej, albo z innych procedur.

Oczywiście powinieneś przetestować zewnętrzny interfejs, wywołując go z przykładowymi ciągami znaków i porównując go z ręcznie analizowanym wyjściem. Ale co z innymi procedurami? Czy przetestowałbyś je indywidualnie, aby sprawdzić, czy poprawnie analizują podciągi?

Mogę wymyślić kilka argumentów:

Plusy :

  1. Więcej testów jest zawsze lepsze, a to może pomóc zwiększyć zasięg kodu
  2. Niektóre komponenty wewnętrzne mogą być trudne do podania określonych danych wejściowych (na przykład przypadków krawędzi) poprzez podanie danych wejściowych do interfejsu zewnętrznego
  3. Bardziej przejrzyste testy. Jeśli wewnętrzny komponent ma (naprawiony) błąd, przypadek testowy tego komponentu wyraźnie pokazuje, że błąd dotyczył tego konkretnego komponentu

Wady :

  1. Refaktoryzacja staje się zbyt bolesna i czasochłonna. Aby zmienić cokolwiek, musisz przepisać testy jednostkowe, nawet jeśli nie dotyczy to użytkowników interfejsu zewnętrznego
  2. Niektóre języki i ramy testowania na to nie pozwalają

Jakie są twoje opinie

imgx64
źródło
możliwe powtórzenie lub znaczące pokrywanie się z: programmers.stackexchange.com/questions/10832/…
azheglov

Odpowiedzi:

8

Przypadek: „moduł” (w szerokim znaczeniu, tj. Coś, co ma publiczny interfejs i ewentualnie także pewne prywatne części wewnętrzne) ma w sobie skomplikowaną / zaangażowaną logikę. Testowanie samego interfejsu modułu będzie swego rodzaju testem integracyjnym w odniesieniu do wewnętrznej struktury modułu, a zatem w przypadku znalezienia błędu, takie testowanie nie zlokalizuje dokładnej wewnętrznej części / komponentu, która jest odpowiedzialna za awarię.

Rozwiązanie: zamień skomplikowane części wewnętrzne w same moduły, przetestuj je (i powtórz te czynności, jeśli są zbyt skomplikowane) i zaimportuj do oryginalnego modułu. Teraz masz tylko zestaw modułów, które są wystarczająco proste do testowania w trybie uniit (oba sprawdzają, czy zachowanie jest poprawne i naprawia błędy) i to wszystko.

Uwaga:

  • nie będzie potrzeby zmiany czegokolwiek w testach „podmodułów” modułu przy zmianie kontraktu modułu, chyba że „podmoduł” nie będzie już oferował usług wystarczających do wypełnienia nowej / zmienionej umowy.

  • nic nie zostanie niepotrzebnie upublicznione, tzn. umowa modułu zostanie zachowana, a enkapsulacja utrzymana.

[Aktualizacja]

Aby przetestować pewną inteligentną wewnętrzną logikę w przypadkach, gdy trudno jest ustawić wewnętrzne części obiektu (tzn. Członków, a nie prywatnie importowanych modułów / pakietów) w odpowiedni stan, po prostu wprowadzając dane wejściowe przez publiczny interfejs obiektu:

  • wystarczy mieć kod testowy z przyjacielem (w języku C ++) lub dostęp do pakietu (Java) do wewnętrznych ustawień, które faktycznie ustawiają stan od wewnątrz i testują zachowanie, jak chcesz.

    • nie spowoduje to ponownego przerwania enkapsulacji, zapewniając jednocześnie łatwy bezpośredni dostęp do elementów wewnętrznych w celach testowych - wystarczy uruchomić testy jako „czarną skrzynkę” i skompilować je w kompilacjach wersji.
mlvljr
źródło
i układ listy wydaje się być nieco zepsuty; (
mlvljr,
1
Dobra odpowiedź. W .NET możesz użyć tego [assembly: InternalsVisibleTo("MyUnitTestAssembly")]atrybutu AssemblyInfo.csdo przetestowania wewnętrznych. Czuję się jak oszustwo.
Nikt
@rmx Gdy coś spełni wszystkie niezbędne kryteria, nie będzie oszukiwania, nawet jeśli ma coś wspólnego z rzeczywistymi kodami. Ale temat dostępu między modułami / wewnątrz modułu jest naprawdę nieco wymyślony we współczesnych językach głównego nurtu.
mlvljr
2

Podejście do kodu opartego na FSM jest nieco inne niż stosowane tradycyjnie. Jest bardzo podobny do tego, co opisano tutaj do testowania sprzętu (który zwykle jest również FSM).

Krótko mówiąc, tworzysz testową sekwencję wejściową (lub zestaw testowych sekwencji wejściowych), która powinna nie tylko generować określone dane wyjściowe, ale także podczas tworzenia określonego „złego” wyjścia pozwala na identyfikację uszkodzonego komponentu na podstawie rodzaju awarii. Podejście to jest dość skalowalne, im więcej czasu poświęcisz na projekt testu, tym lepszy będzie test.

Ten rodzaj testowania jest bliższy tak zwanym „testom funkcjonalnym”, ale eliminuje potrzebę zmiany testów za każdym razem, gdy lekko dotkniesz implementacji.

Bobah
źródło
2

Cóż, to zależy :-). Jeśli stosujesz podejście BDD (Behaviour Driven Development) lub ATDD (Acceptance Test Driven Development), testowanie interfejsu publicznego jest w porządku (o ile testujesz go wyczerpująco przy różnych danych wejściowych. Podstawowa implementacja, np. Metody prywatne, nie jest naprawdę ważne.

Powiedzmy jednak, że chcesz, aby część tego algorytmu została wykonana w określonym przedziale czasowym lub wzdłuż określonej krzywej bigO (np. Nlogn), a następnie tak, testowanie poszczególnych części ma znaczenie. Niektórzy nazwaliby to bardziej tradycyjnym podejściem TDD / testu jednostkowego.

Jak ze wszystkim, YMMV

Martijn Verburg
źródło
1

Podzielić go na kilka części o znaczeniu funkcjonalnym, na przykład ParseQuotedString(), ParseExpression(), ParseStatement(), ParseFile()i uczynić z nich wszystkich publicznych. Jak prawdopodobne jest to, że twoja składnia zmienia się tak bardzo, że stają się nieistotne?

DomQ
źródło
1
takie podejście łatwo prowadzi do słabszej enkapsulacji oraz większych i trudniejszych w użyciu / zrozumieniu interfejsów.
sara,