Unit Test - kiedy klasa jest zależna od innej?

0

Cześć,

Mam takie pytanie związane z testami jednostkowymi. Testy jednostkowe zakładają żeby klasy testować wyizolowane od innych a w przypadku zależności do innych klas należy utworzyć prosta implementacje tych klas. Ale nie rozumiem kliku przykładów więc zwracam się do was o objaśnienia gdyż mój profesor od programowania nie był w stanie mi tego wyjaśnić.

Przykład pierwszy:

public class SimpleClass {
    private OtherClass obj;

    public SimpleClass() {
        this.obj = new OtherClass();
    }

    public String doSomething() {
        obj.do();

         //reszta kodu
    }

    // gettery i settery
}

I pytanie, czy taka klasa jest zależna od innej? Od profesora się dowiedziałem, że nie jest gdyż jesteśmy w stanie ją przetestować bez tworzenia innych obiektów ("co było by testem integracyjnym"). Ale mi się wydaje, że test jednostkowy, który ma testować klasę, funkcję nie zależną od innych, będzie tutaj nie na miejscu bo przecież w klasie OtherObject w metodzie do() może być coś nie tak i w tedy test w SimpleKlas się nie powiedzie...

Już nie rozumiem tego...

0

To zależy co to za OtherClass. Jeśli to jest inna "twoja" klasa to oczywiście test jednostkowy wymagałby mockowania tej klasy, bo jest ona zależnością. Ale jeśli ta klasa pochodzi z biblioteki albo to standardowa klasa javy to już niekoniecznie. Wtedy mockowałbyś tylko jeśli to by ci ułatwiło życie, albo żeby testować specjalne przypadki.

0

Tak zakładam, że to moja własna klasa... sory zapomniałem wcześniej o tym wspomnieć.

No ok w tym przykładzie, który podałem to jeszcze się da to zamokować, ale weźmy taki przykład.

public class SimpleClass {
    private OtherClass  obj;

    public void doSomething() {
        obj = OtherClass();
        obj.do();

        //reszta kodu
    }
}

W takim przypadku nie jestem już w stanie przetestować tej metody w izolacji od innych...

0
  1. To jest teraz kompozycja i pytanie brzmi czy w takim razie ma sens w ogóle testowanie tych dwóch klas osobno, skoro są tak mocno związane?
  2. A czemu dokładnie nie możesz? Porządne biblioteki do mockowania mają expectNew / whenNew i spokojnie mogą mockować także tworzenie obiektów.
0

ad. 1 - Aha czyli takie klasy celowo napisane nie podlegają testom jednostkowym? Więc pisząc jakiś kod trzeba to robić tak aby dało się go później jak najlepiej przetestować?

ad. 2 - Problem widzę w tym: nawet jak zamockuję klase OtherObject i ustawię ją w SimpleObjects to po wywołaniu metody doSomething() i tak zostanie utworzony nowy realny obiekt.
Nie wiem do czego służą te metody, ja korzystam z frameworka Spock.

0
  1. Między innymi warto mieć to na uwadze. Tak samo jak i to, że czasem implementacja może sie zmienić i fajnie byłoby móc ją podmienic bez zmian w 100 miejscach w kodzie bo masz wszędzie new. Po to mamy kontenery IoC, singletony, service locatory i generalnie wstrzykiwanie zależności. Jeśli dana klasa tworzy swoją zależność to znaczy że jest z nią ściśle związana.
  2. Na to już ci nic nie poradzę :P
3

W ogóle cała idea testowania jednej klasy jest głupia i nie wiem, po co tak kurczowo się tego trzymać. Kto powiedział, że w unit-testing unit == class?

0

Generalnie są dwa nurty: klasyczny unit testing (restrykcyjny) w którym unit = klasa i nowy nurt, gdzie unit nie jest ściśle określony. Większość programistów używa tego drugiego, bo jest po prostu bardziej praktyczny. Nie widzę szczerze mówiąc zalet pierwszego podejścia do unit testów. Więcej możesz przeczytać o tym w artykule Martina Fowlera o testach jednostkowyhh na jego stronie.

0

To ja chyba jakiś dziwny jestem, bo dla mnie unit to zawsze była metoda.

0

@somekind im raczej chodzi o to co mockować i uważać za "zewnętrzną" zależność. Ja zwykle też zakładam że unit=metoda, ale to wymaga czasem zaawansowanych narzędzi takich jak partial mock, no bo co zrobisz jak testowana metoda woła inną metodę tej klasy? Albo zostawisz to wywołanie, ale wtedy unit to juz nie jest jedna metoda, albo zrobisz częściowego mocka.

0

@Shalom, uściślając - dla mnie unit to jedna publiczna metoda.

0

No dobra, ale jeśli ta publiczna metoda woła inną publiczną metodę to co? Partial mock czy jednak unit=klasa? ;)

0

Po pierwsze, to nie widzę potrzeby w wywoływaniu metod publicznych z innych publicznych w tej samej klasie. To jakaś lekka aberracja.
Po drugie - co za różnica, czy testowana metoda woła metody publiczne czy prywatne? Nadal testuję tę jedną metodę.

1

@somekind no bez przesady. Prosty przykład: masz klasę Stack która ma metodę pop() i metodę empty(). Implementacja pop() sprawdza czy lista jest pusta i jeśli nie jest to zdejmuje element (a jeśli jest to rzuca wyjątek). I teraz jak chcesz testować tą metodę? Chcesz testować ją z wywołaniem prawdziwego empty()? Wtedy test może sie wyłożyć mimo że pop() jest poprawny, bo błąd jest w empty().
To czy wołasz metodę prywatną czy publiczną ma tylko takie znaczenie że większość ludzi nie testuje prywatnych metod (co uważam za głupie) i zakłada że testując publiczną metodę X testujesz jednocześnie jej wszystkie prywatne zależności.

1
Shalom napisał(a):

@somekind no bez przesady. Prosty przykład: masz klasę Stack która ma metodę pop() i metodę empty(). Implementacja pop() sprawdza czy lista jest pusta i jeśli nie jest to zdejmuje element (a jeśli jest to rzuca wyjątek). I teraz jak chcesz testować tą metodę? Chcesz testować ją z wywołaniem prawdziwego empty()? Wtedy test może sie wyłożyć mimo że pop() jest poprawny, bo błąd jest w empty().

Wtedy test od empty też się wywali.

To czy wołasz metodę prywatną czy publiczną ma tylko takie znaczenie że większość ludzi nie testuje prywatnych metod (co uważam za głupie) i zakłada że testując publiczną metodę X testujesz jednocześnie jej wszystkie prywatne zależności.

To akurat oczywiste i jak najbardziej prawidłowe założenie. Jeśli metoda prywatna nie jest testowana podczas testów metod publicznych, to jest niepotrzebna, zaś jeśli jest tak skomplikowana, że potrzebuje własnych testów, to znaczy, że powinna być metodą publiczną w innej klasie.

0

Założenie że tylko skomplikowane metody potrzebują testów jest bardzo złudne. Zresztą załóżmy że piszemy ten Stack ale uznaliśmy że empty() nie będzie w interfejsie tej klasy. Niemniej samo empty jednak mamy bo używamy go w kilku innych miejscach więc wydzielenie tej logiki do osobnej metody mialo sens (dodatkowo zwiększyło czytelność kodu). Ale sama logika tej metody jest prosta. I co teraz? Wydzielenie do osobnej klasy to jakiś overkill (chociaż faktycznie może mieć sens) a testowanie jej razem z metodami które jej używają niepotrzebnie komplikuje tamte testy i sprawia że są mniej czytelne.

Zresztą ja osobiście uważam że unit-testy zwykle niewiele testują a ich główną wartością jest to, że do słabego kodu testy pisze się po prostu trudno. Jak masz długie metody, albo z zagnieżdżonymi pętlami albo z N ifami to pisanie testów to masakra, bo samo konfigurowanie zachowania / konstruowanie danych wejściowych wymaga bardzo skomplikowanego i długiego kodu.

0
Shalom napisał(a):

Założenie że tylko skomplikowane metody potrzebują testów jest bardzo złudne.

A kto tak zakłada?
Ja napisałem, że jeśli metoda nie ma testów, to jest niepotrzebna, z czego wynika, że wszystkie metody potrzebują testów.

Wydzielenie do osobnej klasy to jakiś overkill (chociaż faktycznie może mieć sens) a testowanie jej razem z metodami które jej używają niepotrzebnie komplikuje tamte testy i sprawia że są mniej czytelne.

Skoro metoda jest prosta, to tak bardzo znowu tych testów nie skomplikuje. :)

Zresztą ja osobiście uważam że unit-testy zwykle niewiele testują

Mówisz teraz o swoich? :P

a ich główną wartością jest to, że do słabego kodu testy pisze się po prostu trudno. Jak masz długie metody, albo z zagnieżdżonymi pętlami albo z N ifami to pisanie testów to masakra, bo samo konfigurowanie zachowania / konstruowanie danych wejściowych wymaga bardzo skomplikowanego i długiego kodu.

Tak, to zaleta, ale bynajmniej nie główna.

0

Mówisz teraz o swoich?

Bardzo możliwe że po prostu moje testy są słabe ;) Niemniej wielokrotnie zdarzało mi się poprawiać defekty mimo pełnego pokrycia kodu testami. Po prostu testy zwykle testują "czy kod działa dla danego przypadku testowego". Testy służą szukaniu błędów a nie dowodzeniu poprawności kodu. Jasne że w takiej sytuacji zawsze dopisujesz kolejny przypadek testowy, który zabezpiecza przed regresją dla znalezionego błędu, ale mimo to nadal nie ma pewności że jeszcze kiedyś się coś tam nie sypnie ;) Dlatego ja raczej z rezerwą podchodzę do unit-testów. Jak przechodzą to istnieje szansa że coś zadziała ;]

1

Dla mnie jednostka to zachowanie dostępne publicznie. Jeżeli mam metodę do obliczania miejsc zerowych funkcji kwadratowej, to piszę testy tylko dla tej funkcji.
nie mockuje/stubuje operacji mnożenia, dodawania itd (wyolbrzymiając), bo:

  • jeżeli są to funkcje publiczne, to powinny być otestowane, ale nie w ramach testów mojej funkcji kwadratowej, tylko tam gdzie zostały zdefiniowane,
  • jeżeli są to funkcje napisane tylko na potrzeby mojej funkcji (np. obliczanie delty funkcji kwadratowej), czyli są prywatne dla jednostki (nie mylić z private w kodzie), to wtedy nie muszę jej testować, bo błędy i tak wyjdą w teście mojej funkcji. Pisał o tym też Evans (albo Vernon, już nie pamiętam) w DDD, że jednostką (unit of work) jest agregat a nie encja, chociaż, że encje to też instancje klas z publicznymi metodami. Jednak te encje i ich metody są prywatne dla agregatu, tj. tylko agregat może z nich korzystać. Dlatego otestowanie zachowań agregatu jest wystarczające.
0
Shalom napisał(a):

Niemniej wielokrotnie zdarzało mi się poprawiać defekty mimo pełnego pokrycia kodu testami.

To zdanie stoi w sprzeczności samo ze sobą. Skoro są przypadki, których testy nie pokryły, to znaczy, że pokrycie nie było pełne.

Po prostu testy zwykle testują "czy kod działa dla danego przypadku testowego". Testy służą szukaniu błędów a nie dowodzeniu poprawności kodu.

Jeśli uwzględniają kilka przypadków typowych oraz wszystkie brzegowe, to dowodzą w wystarczającym stopniu. Znasz skuteczniejszą metodę dowiedzenia, że kod działa?

Dlatego ja raczej z rezerwą podchodzę do unit-testów. Jak przechodzą to istnieje szansa że coś zadziała ;]

To znaczy, że działają w testowanych przypadkach. Jeśli testowane są wszystkie przypadki, to soft będzie działać.

Cały czas piszę o sensownych testach, pisanych przed implementacją, a nie takich klepanych w ostatnim tygodniu trwania projektu, żeby dobić do 75% pokrycia testami, które PM obiecał klientowi.

0

@somekind

  1. Pełne pokrycie oznacza tylko ze każda linia kodu jest wywołana przez jakiś test. Wcale nie oznacza to że wszystkie możliwe przypadki są sprawdzone. Nie ma takiej metryki bo i nie byłoby jak jej liczyć.
  2. Testy NIE dowodzą poprawności algorytmu. Są formalne metody dowodzenia poprawności, ale zwykle nie są zbyt praktyczne i się ich nie stosuje. Testy dowodzą że kod działa dla przypadków testowych. Jasne, że staramy się uwzględnić wszystkie klasy równoważności w teście, ale w praktyce nie zawsze da się to zrobić albo nie zawsze zrobimy to poprawnie. O testowaniu jakichś współbieżnych operacji to nawet nie wspominam ;]

Ja tez pisze o sensownych testach ;) Znam takich geniuszy co piszą jeden test który woła metodę na szczycie hierarchii (np. jakąś metodę kontrolera) i ona potem powoduje kaskadę wywołań przez pół projektu (bo np. ciągnie coś z bazy, woła serwisu do operacji na modelu etc) i potem się cieszą że napisali 5 linijek a miernik pokrycia pokazuje 60%. Nie mówie o takich sytuacjach ;)

0
Shalom napisał(a):
  1. Pełne pokrycie oznacza tylko ze każda linia kodu jest wywołana przez jakiś test.

No, ale to nie jest pełne pokrycie kodu testami, tylko 100% linii pokrytych testami. Taka metryka dla PMów i innych humanistów.

Nie ma takiej metryki bo i nie byłoby jak jej liczyć.

Nie ma i nigdy nie będzie, bo nie da się żadnym automatem stwierdzić, czy kod jest sensownie napisany. To wszystko jest subiektywne i wymaga wiedzy eksperckiej.

  1. Testy NIE dowodzą poprawności algorytmu. Są formalne metody dowodzenia poprawności, ale zwykle nie są zbyt praktyczne i się ich nie stosuje.

Więc jedyną drogą to weryfikacji jest użycie testów.

Testy dowodzą że kod działa dla przypadków testowych. Jasne, że staramy się uwzględnić wszystkie klasy równoważności w teście, ale w praktyce nie zawsze da się to zrobić albo nie zawsze zrobimy to poprawnie.

I jeśli nie rozpatrzymy wszystkich przypadków, albo zrobimy to źle, to właśnie mamy niepełne testy, więc nie mamy też dowodu na poprawność naszego kodu. Wszystko się zgadza.

0

Dobrym przykładem jest kod który symuluje, że Słońce krąży wokół Ziemi. Można to pokryć testami w 100% i wszystkie będą przechodzić a kod i tak będzie błędny, bo programista nie poznał dokładnie domeny problemu.

1

Problem w tym, że bardzo często liczba wszystkich przypadków jest nieskończona i co wtedy? Nie da się przetestować wszystkich. Dlatego wolę testować zachowania i scenariusze a nie pojedyncze wywołania. Czasem unit to metoda, czasem klasa, a czasem kilka powiązanych klas, a czasem cały moduł składający się z kilkuset klas.

0

Parę słów w temacie: http://www.pzielinski.com/?p=2432 i nawet się z tym zgadzam.

1

@somekind no on tam nic wielce odkrywczego nie napisał ;) Tylko że moim zdaniem to się nie sprawdzi jeśli logika jest mocno rozbudowana, bo taki "scenariusz testowy" może wymagać współdziałania połowy modułów systemu i wysypanie się takiego testu niewiele nam powie. Poza tym to co on proponuje to typowy blackbox test, którym trudno jest objąć dużo przypadków. To bardziej taki sanity-test, który sprawdza czy jakieś znane standardowe scenariusze śmigają, ale nic więcej.
Z drugiej strony oczywiście nie należy przesadzać w drugą stronę i bawić się w pisanie testu, który sprawdza czy ciało danej metody jest takie jakie było kiedy pisano test. Widziałem takie "testy" -> sprawdzały kolejność, porządek i ilość wywołań wszystkiego w danej metodzie, w efekcie dowolna zmiana wymagała zmiany testu.

4

Są formalne metody dowodzenia poprawności, ale zwykle nie są zbyt praktyczne i się ich nie stosuje.

Każdy język statycznie typowany je stosuje, bo statyczny system typów nie jest niczym innym jak formalnym systemem dowodzenia poprawności kodu. Oczywiście systemy typów różnią się siłą i klasami poprawności, które są w stanie udowadniać. Np. Rust jest silniejszy niż C++, a Scala/Haskell silniejsze niż Java/C#. Natomiast każdy praktyczny formalny system posiada pewne ograniczenia, tzn. klasy błędów, których nie jest w stanie wykryć (lub udowodnić, że ich brak). I tam się zaczyne pole do popisu dla testowania.

Wracając do tematu - bardzo nie lubię dogmatycznego podejścia do testowania. Dogmaty z jakimi się spotkałem, które wyrządzają raczej więcej szkody niż pożytku:

  1. unit test musi zawsze testować jedną metodę / klasę / coś
  2. wszystkie zależności muszą być mockowane / stubowane
  3. łatwość pisania testów jest ważniejsza niż czytelność i prostota kodu (czyli wpychamy wszędzie DI tylko aby zadowolić pisaczy testów)
  4. testy koniecznie trzeba pisać zanim się napisze kod; jak się napisze odwrotnie, to robi się to źle
  5. kod o 100% pokryciu testami (wg linii) to dobrze przetestowany kod
  6. trzeba mieć minimum XX% pokrycia testami
  7. przechodzące testy dowodzą poprawności kodu (nie - jedynie dowodzą, że kod wykonuje prawidłowo przypadki testowe, i tylko te)
  8. statyczny system typów można zastąpić testami (szczególnie często wyznawane przez programistów języków dynamicznych)

Uważam, że jak do każdego innego tematu, do testowania trzeba podchodzić zdroworozsądkowo. Testowanie jest narzędziem a nie ideologią. Jeżeli testujesz pojedyncze metody i dzięki temu nabierasz pewności, że robią to co mają robić, i faktycznie wychwytujesz w nich ewentualne błędy to znaczy że robisz to dobrze. Jeżeli testujesz całe klasy albo moduły z wielu klas i dzięki temu łapiesz błędy przy refactoringu, to wszystko jest ok. Jeśli Twoje testy nie wyłapują błędów, ale pomagają młodym programistom zrozumieć, jak używa się twojego kodu - to też ma sens, i skoro to dla Ciebie działa, to czemu miałbyś tego tak nie robić? Jeżeli napisałeś 10000 testów w jakiś określony sposób, bo jakiś guru w Internetach napisał, że tylko tak jest poprawnie; ale te testy nie złapały ani jednego błędu, ani właściwie nikomu nie są potrzebne (no może tylko szefowi, żeby miał poczucie jaki to nowoczesny zespół ma działający wg najnowszych metodyk) to coś jest nie tak i lepiej się zastanowić.

0
Krwawy Młot napisał(a):

Cześć,

Mam takie pytanie związane z testami jednostkowymi. Testy jednostkowe zakładają żeby klasy testować wyizolowane od innych a w przypadku zależności do innych klas należy utworzyć prosta implementacje tych klas. Ale nie rozumiem kliku przykładów więc zwracam się do was o objaśnienia gdyż mój profesor od programowania nie był w stanie mi tego wyjaśnić.

Przykład pierwszy:

public class SimpleClass {
    private OtherClass obj;

    public SimpleClass() {
        this.obj = new OtherClass();
    }

    public String doSomething() {
        obj.do();

         //reszta kodu
    }

    // gettery i settery
}

I pytanie, czy taka klasa jest zależna od innej? Od profesora się dowiedziałem, że nie jest gdyż jesteśmy w stanie ją przetestować bez tworzenia innych obiektów ("co było by testem integracyjnym"). Ale mi się wydaje, że test jednostkowy, który ma testować klasę, funkcję nie zależną od innych, będzie tutaj nie na miejscu bo przecież w klasie OtherObject w metodzie do() może być coś nie tak i w tedy test w SimpleKlas się nie powiedzie...

Już nie rozumiem tego...

Tutaj jest zależność na stałe od OtherClass, która jest konkretną produkcyjną implementacją. Żeby to obejść można np stworzyć dodatkowy konstruktor, który przyjmuje OtherClass jako zależność i wtedy można ją mockować/ podstawiać (np testową implementacją która jest współdzielona przez wiele testów)/ cokolwiek.

Sorry jeśli już to zostało napisane :)

1

@Krolik, z większością się zgadzam, ale:

  1. testy koniecznie trzeba pisać zanim się napisze kod; jak się napisze odwrotnie, to robi się to źle

To jest prawda w pewnym sensie.

  1. Pisanie testów przed implementacją to zysk na czasie debugowania właściwej implementacji. Pisząc testy "po" nie ma tego zysku, więc mają one mniejszy sens.
  2. Pisząc testy przed łatwiej je napisać skrupulatnie, zastanawiając się, co metoda ma przyjąć, i co zwrócić. Takie testy testują sensowne przypadki testowe, błędne dane i warunki brzegowe. Pisanie testów "po" zazwyczaj kończy się przetestowaniem jedynie kilku optymistycznych ścieżek. Takie testy są nic nie warte.
  3. Pisanie czegoś, w co się wierzy, że jest przydatne sprawia, że programista przynajmniej stara się zrobić to dobrze.. Jeśli pisze się testy "po", często kończy się to klasami testowymi będącymi ogromnymi spaghetti godobjectami, bo są one tylko "dodatkiem" do aplikacji, a nie metodą jej wytwarzania.
  1. kod o 100% pokryciu testami (wg linii) to dobrze przetestowany kod
  2. trzeba mieć minimum XX% pokrycia testami

Tu jestem skonfundowany. W poście piszesz takie rzeczy, a w komentarzach się ze mną kłócisz, gdy piszę, że ta miara jest bez sensu.

  1. przechodzące testy dowodzą poprawności kodu (nie - jedynie dowodzą, że kod wykonuje prawidłowo przypadki testowe, i tylko te)

Nie dowodzą w sensie matematycznym, bo i nie ma sensu takiego dowodzenia w praktyczniej dziedzinie. Tworzenie oprogramowania to jakby nie patrzeć inżynieria, teoria jest tylko jednym z jej filarów, równie ważne są doświadczenie, intuicja i praktyczny zmysł nie robienia rzeczy bezsensownych tylko dlatego, że teoretycy mówią, że tak należy.

Rozumiem, że ktoś się nie zgadza ze mną, gdy twierdzę, że kod działa, czego dowodem są testy zwracające oczekiwane rezultaty dla sensownych zestawów danych wejściowych. Nie ma nic łatwiejszego niż obalić to twierdzenie podając dane, dla których kod nie zadziała.

Jeżeli napisałeś 10000 testów w jakiś określony sposób, bo jakiś guru w Internetach napisał, że tylko tak jest poprawnie; ale te testy nie złapały ani jednego błędu

No i to jest właśnie powód do pisania testów przed implementacją.

2

Co do pisania testów przed implementacją, to ja mogę podać kilka argumentów przeciw:

  1. Tylko w idealnym świecie oraz książkach interfejs jest całkowicie niezależny od implementacji. W praktyce jednak implementacja ma wpływ na interfejs, a interfejs na implementację. Myśląc tylko o samym interfejsie, możemy zaprojektować taki interfejs, że implementacja będzie 3x bardziej skomplikowana niż mogłaby być. Możemy też spowodować problemy z wydajnością - np. konieczność przekształcania formatu danych do takiego jaki zażyczył sobie projektant interfejsu, albo problemy na styku sync/async. W efekcie pisząc testy pod interfejs zaprojektowany w oderwaniu od implementacji może się okazać, że w trakcie pisania implementacji będziemy musieli zmienić interfejs, a w efekcie przepisać testy. Innymi słowy - marnujemy czas na przepisywanie testów kilka razy.

  2. Pisanie implementacji pod gotowy zestaw testów powoduje u niektórych programistów chęć "zaliczenia" wszytkich testów byle jak, byle tylko na końcu było zielone. W efekcie czytelność i prostota implementacji pozostawia wiele do życzenia. Np. widziałem kod ogólnie całkiego dobrego programisty, który folgując TDD, napisał implementację odzwierciedlającą po prostu po kolei każdy przypadek testowy jakimś wielkim switchem czy drabinką ifów, zamiast uogólnić wszystkie przypadki. A ponieważ w testach brakowało dwóch szczególnych przypadków, a jeden był na dodatek źle, to i kod miał analogiczne błędy.

  3. Testy przed implementacją wymuszają programowanie/projektowanie stylem top-down. W efekcie można spędzić 5 dni na pisanie kodu i dalej nie mieć nic działającego, prócz kilku pięknych warstw abstrakcji. Ja tu widzę pewien paradoks, bo top-down jest bardzo, bardzo "nie-agile". Tymczasem wielu programistów, których podziwiam, i którzy piszą bardzo dobrze zaprojektowany kod, preferują bottom-up (np. Paul Graham, Peter Norvig, Ken Thompson, Linus Torvalds).
    http://www.paulgraham.com/progbot.html

Pisząc testy przed łatwiej je napisać skrupulatnie, zastanawiając się, co metoda ma przyjąć, i co zwrócić. Takie testy testują sensowne przypadki testowe, błędne dane i warunki brzegowe

Ten argument raczej działa na korzyść pisania testów po, a nie przed. Nie znając implementacji, nie jesteś w stanie określić prawidłowo wszystkich przypadków brzegowych, bo mogą być przypadki brzegowe ukryte, wynikające z implementacji. Przypadkiem brzegowym mogą być np. dane wejściowe o wielkości dokładnie 65536 B, bo implementacja używa bufora o takiej dokładnie wielkości i pechowo tylko dla tej wartości jest off-by-one error... Pisząc testy przed implementacją nie masz szans takich rzeczy sprawdzić.

Pisanie testów "po" zazwyczaj kończy się przetestowaniem jedynie kilku optymistycznych ścieżek. Takie testy są nic nie warte.

Test testujący kilka optymistycznych ścieżek może nie jest kompletny, ale też ma pewną wartość. Zwłaszcza jeśli te optymistyczne ścieżki to akurat te, które są wykorzystywane przez klientów. Taki test nadal chroni przed regresjami.

  1. trzeba mieć minimum XX% pokrycia testami
    Tu jestem skonfundowany. W poście piszesz takie rzeczy, a w komentarzach się ze mną kłócisz, gdy piszę, że ta miara jest bez sensu.

Sama miara nie jest bez sensu. Natomiast bez sensu jest dogmatyczne używanie jej jako jedynego wyznacznika jakości testów. Pokrycie linijkowe jest użyteczne w celu pokazania, które fragmenty kodu wymagają jeszcze testów. Ale nie jest użyteczne określenie, że ma być pokrycie 80% i ani pół procenta mniej.

Nie dowodzą w sensie matematycznym, bo i nie ma sensu takiego dowodzenia w praktyczniej dziedzinie.

Statyczny system typów dowodzi braku błędów (pewnej klasy) dla wszystkich możliwych wejść, nawet jeśli liczba ta jest nieskończona, w sensie matematycznym. Dysponując silnym systemem typów mogę pisać tak kod, aby zminimalizować zapotrzebowanie na testy. Stąd testy powinno się robić po, aby pokryć tylko te przypdadki, których nie dało się uwzględnić w systemie typów / analizą statyczną. Na przykład, jeśli system typów potrafi udowodnić, że dana metoda nigdy nie zwraca null, to nie ma sensu pisać testów, które to testują - będzie to marnowanie czasu. Oczywiście w praktyce system typów nie potrafi udowodnić wszystkiego, więc testy są nadal użyteczne.

Jeśli pisze się testy "po", często kończy się to klasami testowymi będącymi ogromnymi spaghetti godobjectami, bo są one tylko "dodatkiem" do aplikacji, a nie metodą jej wytwarzania.

Testy dodatkiem do aplikacji, a nie są metodą jej wytwarzania. Testy to też jest kod, który trzeba wytworzyć i utrzymać. Im mniej, aby osiągnąć cel, tym lepiej. Testy są użytecznym narzędziem, ale są tylko jednym z wielu narzędzi zapewnienia odpowiedniej jakości projektu. Robienie z nich centralnego elementu wytwarzania oprogramowania to jakieś nieporozumienie. Klienta nie obchodzą testy, klienta obchodzi działające oprogramowanie.

1 użytkowników online, w tym zalogowanych: 0, gości: 1