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

Unit Test - kiedy klasa jest zależna od innej?
somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:2 dni
  • Lokalizacja:Wrocław
1
Krolik napisał(a):
  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.

Możemy. Możemy też używać goto, singletonów, oprzeć całą aplikację na statycznych klasach, a nawet zawrzeć ją w jednej wielkiej klasie. Możemy mieć setki pól przechowujących stan i bezargumentowe metody zwracające void. Tylko co to ma do rzeczy? TDD nie wymusza pisania słabego kodu.

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.

Wychwytywanie błędów w architekturze to jeden z efektów pisania testów jednostkowych, i ich ogromna zaleta.

Innymi słowy - marnujemy czas na przepisywanie testów kilka razy.

No tak, bo w trakcie "normalnego programowania", nigdy niczego nie przepisujemy. Zwłaszcza, gdy jednak jakimś cudem znajdziemy błędy w implementacji. (Tzn. użytkownik na produkcji znajdzie.)

  1. 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.

Powiedzieć o kimś takim, że to słaby programista jest obrażaniem @maszynaz. Dobrzy programiści nie piszą słabego kodu, żeby przeszedł testy. Celem testów jest pomoc w procesie implementacji, a nie samo ich zaliczenie, gościowi się praca pomyliła z politechniką.

  1. 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

Napisanie klasy z pustymi metodami, potem testów, a potem implementacji metod, jest bardzo dalekie od jakiegokolwiek projektowania.

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ć.

Ale czy ktoś zabrania dopisania brakującego testu w trakcie implementacji? Skoro takie zjawisko istnieje, to trzeba je uwzględnić w testach i tyle.

Testy dodatkiem do aplikacji, a nie są metodą jej wytwarzania.

Uważasz, że TDD nie jest metodą/metodologią tworzenia kodu?

Testy to też jest kod, który trzeba wytworzyć i utrzymać. Im mniej, aby osiągnąć cel, tym lepiej.

Dlatego pisząc testy trzeba jest pisać tak, jak każdy inny kod, a nie na zasadzie kopiuj-wklej.

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.

Owszem, tylko to jest narzędzie, które akurat każdy programista może stosować, bo jest dość proste, tanie i efektywne.

Klienta nie obchodzą testy, klienta obchodzi działające oprogramowanie.

Na szczęście coraz więcej klientów rozumie, że testy leżące bliżej kodu pozwalają na redukcję czasu poprawiania błędów w późniejszych fazach przedsięwzięcia, co ma duży wpływ na koszty.
Może za ileś lat programiści też to zrozumieją.

Twój post wygląda tak, jakbyś sam najpierw chciał sprowadzić pisanie testów do bycia głównym elementem wytwarzania oprogramowania, a potem pokazać jakie to złe podejście i testowanie w ogóle obalić. Z kimś, kto obala własne argumenty trudno polemizować, bo ma zawsze rację. :P

To co piszesz wygląda tak, jakbyś uważał, że TDD polega na tym, że najpierw jacyś architekci projektują cała aplikację, później banda programistów siada i pisze miesiącami testy do wszystkich metod całej aplikacji, a na końcu pisze się implementację, żeby przeszła te testy. Tymczasem to w ogóle nie o to chodzi. Jest zadanie do wykonania, jeśli w toku tego zadania programista dochodzi do wniosku, że procesy, które ma zaimplementować warto przetestować, to pisze testy, a potem implementację. Traci co prawda na czasie pisania kodu testów, ale zyskuje znacznie więcej na czasie uruchamiania aplikacji i doprowadzania jej do momentu, w którym korzysta z implementowanego przez niego kodu. To jest jak najbardziej oddolny, a nie odgórny proces.

KR
Moderator
  • Rejestracja:prawie 21 lat
  • Ostatnio:2 dni
  • Postów:2964
0

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.

Wychwytywanie błędów w architekturze to jeden z efektów pisania testów jednostkowych, i ich ogromna zaleta.

Akurat nie przesadzałbym z ich zdolnością do wychwytywania błędów w architekturze... Patrz dalej.

Innymi słowy - marnujemy czas na przepisywanie testów kilka razy.

No tak, bo w trakcie "normalnego programowania", nigdy niczego nie przepisujemy. Zwłaszcza, gdy jednak jakimś cudem znajdziemy błędy w implementacji. (Tzn. użytkownik na produkcji znajdzie.)

Przepisanie samego kodu to mniej pracy niż przepisanie kodu i testów. Podobnie napisanie dobrego testu automatycznego to znacznie więcej pracy niż przeprowadzenie analogicznego testu ręcznie jeden czy dwa razy (np. w REPLu) po napisaniu kodu. Poza tym nie wiem jak Ty, ale ja często w trakcie implementowania nowego kawałka kodu, przesuwam bądź wyrzucam nawet całkowicie pewne fragmenty, które napisałem (np. całe metody, całe klasy). Wtedy musiałbym wyrzucić też testy, które tak pieczołowicie wcześniej napisałem.

W skrajnym przypadku takie podejście może skończyć się tak jak to się przydarzyło Ronowi Jeffries'owi, ekspertowi i promotorowi TDD. Nie dość, że jego rozwiązenie miało mnóstwo testów, to miało też 10x więcej kodu, skopaną architekturę i na dodatek ostatecznie w ogólności... nie działało. Ron zamiast najpierw skupić się na tym, jak rozwiązać problem (lub poszukać jak to zrobić), to skupił się na pisaniu testów. Raczej mu to nie pomogło, a zmarnował tylko kupę czasu i wystawił się na pośmiewisko. Tymczasem Norvig nie napisał ani jednego testu przed, a napisał po prostu ładny, czytelny kod od razu, który jeszcze działał. Na końcu napisał testy wydajnościowe. Ktoś może powiedzieć, że po prostu Ron jest słabym programistą, a Norvig jest o 3 klasy wyżej i pewnie miałby rację. Ale jakoś nie mam zaufania do teorii/filozofii (w tym przypadku TDD) głoszonych przez programistów będących 3 klasy niżej niż Norvig...

http://ravimohan.blogspot.com/2007/04/learning-from-sudoku-solvers.html

  1. 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.

Powiedzieć o kimś takim, że to słaby programista jest obrażaniem @maszynaz. Dobrzy programiści nie piszą słabego kodu, żeby przeszedł testy. Celem testów jest pomoc w procesie implementacji, a nie samo ich zaliczenie, gościowi się praca pomyliła z politechniką.

Ależ przecież właśnie on robił TDD jak przystało:

  1. Napisz testy
  2. Napisz szybko minimalny kod, który je jakoś przechodzi.
  3. Zrefaktoryzuj

Problem w tym, że to działa często gorzej niż:

  1. Napisz poprawy i ładny kod - przerabiaj / przepisuj go do woli dopóki nie jesteś zadowolony.
  2. Daj do code review
  3. Jak code review ok, napisz testy aby mieć pewność, że wszystko ok w ostatecznej wersji (lub daj komuś innemu do testowania)
  4. Ewentualnie popraw błędy.

Zaletą tego drugiego podejścia jest to, że zwykle wcześniej / szybciej mamy działający kod, co w przypadku zbliżającego się deadline może mieć znaczenie.
W obu podejściach jest ryzyko - olanie refactoryzacji w TDD, olania testów w tradycyjnym podejściu. Moim zdaniem olanie testów jest mniej groźne (zwłaszcza jeśli na końcu jest oficjalne QA, które powinno złapać błędy i tak). Natomiast olanie refaktoryzacji w TDD, to droga do projektu z tysiącem testów, gdzie cokolwiek ruszysz, się sypie.

  1. 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

Napisanie klasy z pustymi metodami, potem testów, a potem implementacji metod, jest bardzo dalekie od jakiegokolwiek projektowania.

I tu dochodzimy z powrotem do oryginalnego tematu wątku. Dlaczego mam napisać klasę z pustymi metodami? Dlaczego mam napisać całą klasę? A dlaczego nie np. tylko dwie powiązane metody (np. implementuję stos: zacznę od pop i push; o reszcie na razie nie chcę myśleć)? A może ja chcę tylko jedną metodę, a o innych metodach zdecyduję później? A może ja nie wiem jeszcze w ogóle jak będzie wyglądać hierarchia klas, dopóki nie zacznę pisać kodu? Może przerobię całą hierarchię pięć razy bo w trakcie pisania kodu okaże się, że problem można było zdekomponować w inny, lepszy sposób? Kto powiedział, że API to ma być jedna klasa i że testy mają obejmować jedną klasę? Dlatego piszę testy jak wiem, że mam już stabilne API, rozwiązanie działa, a testy są tylko do ochrony przed regresjami, ewentualnie złapaniem jakiś niuansów. Natomiast testy nie są metodą projektowania kodu, bo są do tego zwyczajnie za słabe.

Uważasz, że TDD nie jest metodą/metodologią tworzenia kodu?

Uważam że jest, ale niekoniecznie skuteczną, a w każdym razie nie zawsze i nie w każdej sytuacji. Innymi słowy - nieco przereklamowaną z ww powodów. Ma zalety i ma wady, jak wszystko inne.

Testy to też jest kod, który trzeba wytworzyć i utrzymać. Im mniej, aby osiągnąć cel, tym lepiej.

Dlatego pisząc testy trzeba jest pisać tak, jak każdy inny kod, a nie na zasadzie kopiuj-wklej.

Zgoda, ale i tak kosztuje wcale nie mało.

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.

Owszem, tylko to jest narzędzie, które akurat każdy programista może stosować, bo jest dość proste, tanie i efektywne.

Czy takie tanie to kwestia sporna. Koszty:

  1. testy trzeba napisać; pisząc testy najpierw odsuwamy w czasie moment zmierzenia się z problemem, który ma rozwiązywać właściwy kod - zwiększamy w efekcie ryzyko
  2. testy trzeba utrzymać - każdy większy refactoring czy zmiana wymagań i testy musimy modyfikować
  3. testy mogą wprowadzać własne błędy; wywalający się test nie oznacza błędu w kodzie testowanym; może oznaczać błąd w teście
  4. testy wydłużają cykl budowania projektu, czasem bardzo znacznie; zbyt dużo testów powoduje, że programiści uruchamiają je coraz rzadziej, zdając się na CI, w efekcie zmniejszając podstawową zaletę unit-testów jaką jest wczesne łapanie błędów

Czy takie efketywne, też kwestia sporna. Unit-testy z moich obserwacji są najmniej efektywne. Programiści rzadko popełniają błędy we własnym kodzie, a tym bardziej rzadko łapią je w swoich własnych testach. Najwięcej błędów, jakie wykrywamy, to błędy w cudzym kodzie, leżące na styku wielu systemów. Takie rzeczy można łapać tylko testami integracyjnymi / funkcjonalnymi; których raczej nie sposób pisać przed implementacją, bo zwykle pisze je niezależny zespół na podstawie dokumentacji końcowej, a dokumentacja powstaje po implementacji. W ten sposób jest pewność, że wszystko działa tak jak w dokumentacji, a nie tak jak "wymyślił sobie programista".

YU
  • Rejestracja:ponad 16 lat
  • Ostatnio:ponad 6 lat
1
  1. testy trzeba napisać; pisząc testy najpierw odsuwamy w czasie moment zmierzenia się z problemem, który ma rozwiązywać właściwy kod - zwiększamy w efekcie ryzyko

Z drugiej strony zaczynając od testów ustalamy na wczesnym etapie interfejs jaki mają wystawić na zewnątrz testowane klasy oraz uzgadniamy zależności między nimi.
Jest szansa że wykryjemy jakieś zgrzyty projektowe.

  1. testy trzeba utrzymać - każdy większy refactoring czy zmiana wymagań i testy musimy modyfikować

Kod produkcyjny również trzeba utrzymywać. Mając testy utrzymanie jest prostsze, z uwagi na to że testy są formą dokumentacji.
Mając testy na nieznany kawałek systemu łatwiej jest mi zrozumieć jak ten kawałek działa i "wgryźć się w niego". Testy mają tę przewagę nad pdf-ami i wordami, że mogę je sobie po prostu uruchomić i w mechaniczny sposób sprawdzić na ile system jest zgodny z taką "wykonywalną dokumentacją".

  1. testy mogą wprowadzać własne błędy; wywalający się test nie oznacza błędu w kodzie testowanym; może oznaczać błąd w teście

Jeśli utrzymuje się rozsądny poziom skomplikowania testu zgodnie z zasadą, że testujemy tylko i wyłącznie jedną rzecz jednym testem to
wprowadzenie błędów w sam test jest mało prawdopodobne. Given/When/Then + umiarkowana ilość mocków powinny zapewnić względną prostotę testu.

  1. testy wydłużają cykl budowania projektu, czasem bardzo znacznie; zbyt dużo testów powoduje, że programiści uruchamiają je coraz rzadziej, zdając się na CI, w efekcie zmniejszając podstawową zaletę unit-testów jaką jest wczesne łapanie błędów

Przy odpowiednim build systemie z podziałem na domeny wystarczy puszczać tylko testy z domeny w której dokonywaliśmy zmian, omijając wszystkie inne. Testy mają tę fajną cechę, że są mocno od siebie niezależne zatem ich wykonanie może zostać zrównoleglone. Dodatkowo czas ulegnie dalszemu skróceniu jeśli przerwiemy na pierwszym failu ;)


edytowany 1x, ostatnio: yurai
Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
2
yurai napisał(a):

Z drugiej strony zaczynając od testów ustalamy na wczesnym etapie interfejs jaki mają wystawić na zewnątrz testowane klasy oraz uzgadniamy zależności między nimi.
Jest szansa że wykryjemy jakieś zgrzyty projektowe.

Pewny jesteś? Bo poza tym sytuacjami kiedy implementujesz dodatkową funkcjonalność w już istniejacej porządnej architekturze, to trudno mi sobie to wyobrazić. Szczególnie w projektach skomplikowanych dziedzinowo. Bo zwyczajnie zanim nie zaczniesz implementować to nie wiesz co konkretnie będzie ci potrzebne i jaki interfejs będzie miał sens a jaki nie. W trakcie implementacji mogą też pojawić się ograniczenia technologiczne albo jakieś inne, które wpłyną na strukturę kodu, a były nie do przewidzenia wcześniej.

Kod produkcyjny również trzeba utrzymywać. Mając testy utrzymanie jest prostsze, z uwagi na to że testy są formą dokumentacji.
Mając testy na nieznany kawałek systemu łatwiej jest mi zrozumieć jak ten kawałek działa i "wgryźć się w niego". Testy mają tę przewagę nad pdf-ami i wordami, że mogę je sobie po prostu uruchomić i w mechaniczny sposób sprawdzić na ile system jest zgodny z taką "wykonywalną dokumentacją".

Z tym się zgadzam, ale tylko jeśli testy są porządne :)

Przy odpowiednim build systemie z podziałem na domeny wystarczy puszczać tylko testy z domeny w której dokonywaliśmy zmian, omijając wszystkie inne. Testy mają tę fajną cechę, że są mocno od siebie niezależne zatem ich wykonanie może zostać zrównoleglone. Dodatkowo czas ulegnie dalszemu skróceniu jeśli przerwiemy na pierwszym failu ;)

To prawda, ale faktem jest że developerzy często próbują omijać testy, szczególnie w dużych projektach. A z tym testowaniem tylko fragmentu który zmienialismy to nie zawsze to takie proste. Bo mogliśmy zmienić coś z czego korzysta wiele innych modułów i niestety trzeba puścić testy integracyjne ;)


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
YU
Co do pierwszego to ok, argument z wykrywaniem problemów design-u był nieco naciągany. Z ostatnim jasna sprawa - jeśli mamy pecha i graf zależności między modułami jest grafem pełnym to i tak trzeba puścić wszystko.
Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:10 minut
1

Pracowałem już w kilku firmach i generalnie z mojego doświadczenia większy nacisk na testy jest równoznaczny z większą niezawodnością i do pewnego stopnia jakością kodu. Ponadto zdecydowana większość czasu jest spędzana nad rozkminianiem jak kod działa, zwłaszcza jeśli trzeba naprawić buga. Ale często trzeba też sobie jakoś wyobrazić co kod robi żeby wiedzieć gdzie dodać nowe funkcjonalności. I tutaj testy dają duże pole do manewru. Testy można tak napisać, że od razu będzie wiadomo jak testowana klasa działa i po co jest. Można pokazać jej działanie na przykładach, czyli podać konkretne wejście i sprawdzić czy otrzymano konkretne wyjście.

Rozwiązywanie Sudoku jest na tyle prostym problemem, że tutaj "dupa-debugging" się idealnie sprawdza (można wyprintować cały stan i zmieści się na ekranie), a odpalenie Sudoku solvera na jakimkolwiek wejściu jest jednocześnie ostatecznym testem akceptacyjnym. Przy odpowiednio wielu różnych wejściach taki ich zestaw staje się w zasadzie wystarczającym zestawem testów akceptacyjnych.

Z drugiej strony samo wprowadzenie TDD czy dużego zbioru testów nie jest lekarstwem na wszystko bo nawet jak wszystkie testy przechodzą to i tak backlog może być spuchnięty do granic możliwości, Technical Debt nie do ogarnięcia, itd bo np testy są zbyt niskopoziomowe, zbyt wyizolowane, etc Pokrycie kodu może być dobre, ale produkt dalej może wywalać się na produkcji. Wszystko trzeba robić z głową :) Ale ogólnie np testy dają większą pewność przy refaktorze, że wszystko działa tak jak powinno. Refaktor wiąże się z koniecznością poznania kodu i podczas refaktoru można wyłapać błędy, które już dawno zaczęły umykać uwadze.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 2x, ostatnio: Wibowit
KR
Moderator
  • Rejestracja:prawie 21 lat
  • Ostatnio:2 dni
  • Postów:2964
0

Dyskusja zbiegła na temat "czy testowanie jest dobre, czy nie". A mnie się wydaje, że to zupełnie nie o to chodziło w oryginalnym poście, ani w mojej polemice z @somekind.
Wszyscy się tutaj raczej zgadzamy, że testowanie jest dobre i przydatne, i posiadanie dobrych testów znacząco podnosi jakość projektu. W naszym projekcie mamy bardzo dużo testów, z pokryciem łącznym bliskim 100% oraz brudnymi tetami, testującymi np. reakcję na sytuacje błędne.

Jednak diabeł tkwi w szczegółach - co to znaczy dobrze testować? Gdzie leży granica między unit-testem a testem integracyjnym? Czy unit-test testujący 10 klas, to unit test, czy test integracyjny? Jestem zdania, że zagadnienie testowania jest na tyle złożone samo w sobie, że nie poddaje się sztywnym regułom. Polemizowałem z "pisz testy zawsze najpierw" czy "unit test testuje zawsze jedną metodę", a nie z "pisz testy" w ogóle. Norvig też napisał testy do swojego kodu, tyle że później.

Np. u nas wszystkie testy, które nie wymagają bazy danych i wykonują się szybko, są testami jednostkowymi, niezależnie od tego, czy testują jedną metodę, czy 5 warstw równocześnie. Testy testujące więcej na raz zwykle wykrywają więcej problemów, bo tak jak mówiłem, błędy najczęsciej zdarzają się na styku komponentów, zwłaszcza pisanych przez różnych ludzi. Podstawowe kryterium - unit-testy mają być łatwe do odpalenia i wykonywać się bardzo szybko. Przetestowanie wszystkiego zajmuje kilka sekund. Stąd mogę je robić co commit. Testy integracyjne zwykle odpalają wbudowany serwer bazy danych, ładują dane testowe i zajmują kilka minut. Jednak nadal można je odpalać na lokalnej maszynie, jednym skryptem, bez wstępnej konfiguracji. Testy funkcjonalne za to wymagają pewnego przygotowania np. klastra na 10 maszyn. Też są automatyzowane, ale potrafią zająć 2 dni.

My to robimy tak i dla nas to działa. Ale każdy system i projekt jest nieco inny. Nie uważamy, że to jest jedyna słuszna droga. Jeżeli testujesz inaczej i w Twoim zespole to działa, to czemu ktoś miałby Ci tego zabraniać?

Myślę też, że ten post dość dobrze oddaje to co chciałem powiedzieć: http://www.writemoretests.com/2011/09/test-driven-development-give-me-break.html

Rozwiązywanie Sudoku jest na tyle prostym problemem, że tutaj "dupa-debugging" się idealnie sprawdza (można wyprintować cały stan i zmieści się na ekranie), a odpalenie Sudoku solvera na jakimkolwiek wejściu jest jednocześnie ostatecznym testem akceptacyjnym.

I na tyle skomplikowanym, że:

  1. Ron sobie z nim nie poradził.
  2. Nie da się go rozwiązać w sposób iteracyjny. Tzn. ta cała dyskusja o sudoku nie jest tyle krytyką TDD, co podejścia iteracyjnego do rozwoju oprogramowania i podejścia "worse-is-better" / YAGNI, na którym TDD bazuje. Strategia "napisz najprościej jak się da, aby przechodziło testy", a później ewentualnie zrefaktoryzuj może oznaczać, że "zrefaktoryzuj" == "wyrzuć co napisałeś i napisz wszystko od nowa używając zupełnie innego algorytmu / innej architektury / innego projektu".
edytowany 2x, ostatnio: Krolik
somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:2 dni
  • Lokalizacja:Wrocław
1
Krolik napisał(a):

Akurat nie przesadzałbym z ich zdolnością do wychwytywania błędów w architekturze... Patrz dalej.

Jeśli kod jest słabo zaprojektowany, to ciężko jest napisać testy. Np. jakiś proces biznesowy wymaga nazwy zalogowanego użytkownika, więc pobiera go z jakiejś klasy frameworka webowego udostępniającej informacje dotyczące uwierzytelnienia, ale klasa ta wymaga do działania uruchomionego serwera WWW. Nie przetestujesz tego jednostkowo, bo architektura jest do bani.

Podobnie napisanie dobrego testu automatycznego to znacznie więcej pracy niż przeprowadzenie analogicznego testu ręcznie jeden czy dwa razy (np. w REPLu) po napisaniu kodu.

O ile masz REPLa, nie piszesz aplikacji z GUI, które wymagają "doklikania" się do testowanego miejsca w kodzie, a testowany kod ma mało zależności, to faktycznie możesz. Tylko nie podepniesz już chyba tego do CI, a ktoś, kto będzie ten soft utrzymywał, będzie musiał sobie to testować ręcznie. Na dłuższą metę to słabe rozwiązanie.

Poza tym nie wiem jak Ty, ale ja często w trakcie implementowania nowego kawałka kodu, przesuwam bądź wyrzucam nawet całkowicie pewne fragmenty, które napisałem (np. całe metody, całe klasy). Wtedy musiałbym wyrzucić też testy, które tak pieczołowicie wcześniej napisałem.

Ja komituję jakieś 10-20% napisanego przez siebie kodu, ale to jest kod aplikacji. Też kasuję metody czy klasy, wiele rzeczy przenoszę, refaktoryzuję, rozbudowuję albo upraszam hierarchię, zmieniam sygnatury metod, ale to nie ma większego wpływu na testy.
Nie umiem sobie wyobrazić takiego nieogarnięcia, żeby wyrzucać całe otestowane klasy, przecież to świadczy o tym, że napisało się kompletnie bezużyteczny kod.

Ron zamiast najpierw skupić się na tym, jak rozwiązać problem (lub poszukać jak to zrobić), to skupił się na pisaniu testów.

Dla mnie oczywiste jest, że testy pisze się do czegoś, co rozumiemy i mamy z grubsza pomysł na implementację. Jeśli nie, to TDD prawdopodobnie zawiedzie.

Ktoś może powiedzieć, że po prostu Ron jest słabym programistą, a Norvig jest o 3 klasy wyżej i pewnie miałby rację. Ale jakoś nie mam zaufania do teorii/filozofii (w tym przypadku TDD) głoszonych przez programistów będących 3 klasy niżej niż Norvig...

Ja w ogóle niespecjalnie staram się podążać za głosicielami filozofii, stąd nazwiska, którymi tu rzucasz są mi kompletnie nieznane. Ja jestem pragmatyczny - jeśli widzę, że coś działa i się sprawdza, to tego używam, nieważne kto to wymyślił, ani kto to popiera.

Ależ przecież właśnie on robił TDD jak przystało:

  1. Napisz testy
  2. Napisz szybko minimalny kod, który je jakoś przechodzi.
  3. Zrefaktoryzuj

Minimalny nie znaczy c****.
Ale współczuję wszystkim facetom, dla których te dwa przymiotniki są synonimami. :P
A tak na serio - bardzo słabe tłumaczenie dla napisania słabego kodu.

I tu dochodzimy z powrotem do oryginalnego tematu wątku. Dlaczego mam napisać klasę z pustymi metodami? Dlaczego mam napisać całą klasę? A dlaczego nie np. tylko dwie powiązane metody (np. implementuję stos: zacznę od pop i push; o reszcie na razie nie chcę myśleć)? A może ja chcę tylko jedną metodę, a o innych metodach zdecyduję później?

To tak zrób, przecież nie trzeba od razu pisać testów do wszystkich metod danej klasy. Sam zazwyczaj testuję po jednej.
Ja nie pisałem o tym, że trzeba mieć od razu zaprojektowaną całą klasę, tylko o tym, że napisanie pustej klasy to nie jest projektowanie.

A może ja nie wiem jeszcze w ogóle jak będzie wyglądać hierarchia klas, dopóki nie zacznę pisać kodu? Może przerobię całą hierarchię pięć razy bo w trakcie pisania kodu okaże się, że problem można było zdekomponować w inny, lepszy sposób? Kto powiedział, że API to ma być jedna klasa i że testy mają obejmować jedną klasę?

Napisałem w tym wątku, że unit to jedna publiczna metoda. Uważam tak, bo:

  1. prywatnych metod się nie testuje bezpośrednio;
  2. jednoczesny test kilku publicznych metod danej klasy to w ogóle jakiś absurd dla mnie.
    Mi nie chodzi o to, że każda publiczna metoda ma mieć testy, ani tym bardziej tego że każda klasa ma mieć swoje własne testy i być testowana w oderwaniu od klas, które jej używają, ani tym bardziej tego, że test ma testować tylko kod zawarty w tej jednej metodzie.
    Publiczna metoda to coś, co pozwala wykonać jakiś istotny w ramach dziedziny systemu proces. Wysoce prawdopodobne jest, że w tym celu korzysta z całej hierarchii klas pomocniczych. Być może te klasy mają swoje własne testy, a być może nie, ale to wszystko zależy od konkretnego przypadku.

Uważam że jest, ale niekoniecznie skuteczną, a w każdym razie nie zawsze i nie w każdej sytuacji. Innymi słowy - nieco przereklamowaną z ww powodów. Ma zalety i ma wady, jak wszystko inne.

Myślę, że jest bardziej niezrozumiałe niż przereklamowane, zarówno wśród programistów jak i kierowników.

  1. testy wydłużają cykl budowania projektu, czasem bardzo znacznie; zbyt dużo testów powoduje, że programiści uruchamiają je coraz rzadziej, zdając się na CI, w efekcie zmniejszając podstawową zaletę unit-testów jaką jest wczesne łapanie błędów

No, ale CI jest właśnie po to, żeby pomóc wcześnie wyłapać błędy. Failujący z powodu nieprzechodzących testów build, to raczej szybka informacja.

Czy takie efketywne, też kwestia sporna. Unit-testy z moich obserwacji są najmniej efektywne. Programiści rzadko popełniają błędy we własnym kodzie, a tym bardziej rzadko łapią je w swoich własnych testach. Najwięcej błędów, jakie wykrywamy, to błędy w cudzym kodzie, leżące na styku wielu systemów. Takie rzeczy można łapać tylko testami integracyjnymi / funkcjonalnymi; których raczej nie sposób pisać przed implementacją, bo zwykle pisze je niezależny zespół na podstawie dokumentacji końcowej, a dokumentacja powstaje po implementacji. W ten sposób jest pewność, że wszystko działa tak jak w dokumentacji, a nie tak jak "wymyślił sobie programista".

To już zależy od konkretnego przedsięwzięcia, i od ludzi, którzy biorą w nim udział. Z mojego doświadczenia wynika jednak, że ja szybciej dostarczam działający kod stosując TDD niż koledzy nie piszący testów, np.: ja 2 dni piszę testy, a pół dnia właściwą implementację, zaś koledzy mają implementację po 1 dniu, a potem 3 dni debugują. (A potem jeszcze poprawiają błędy zgłoszone przez testerów, których u mnie jest wyraźnie mniej.)

Krolik napisał(a):

Np. u nas wszystkie testy, które nie wymagają bazy danych i wykonują się szybko, są testami jednostkowymi, niezależnie od tego, czy testują jedną metodę, czy 5 warstw równocześnie. Testy testujące więcej na raz zwykle wykrywają więcej problemów, bo tak jak mówiłem, błędy najczęsciej zdarzają się na styku komponentów, zwłaszcza pisanych przez różnych ludzi. Podstawowe kryterium - unit-testy mają być łatwe do odpalenia i wykonywać się bardzo szybko. Przetestowanie wszystkiego zajmuje kilka sekund. Stąd mogę je robić co commit. Testy integracyjne zwykle odpalają wbudowany serwer bazy danych, ładują dane testowe i zajmują kilka minut. Jednak nadal można je odpalać na lokalnej maszynie, jednym skryptem, bez wstępnej konfiguracji. Testy funkcjonalne za to wymagają pewnego przygotowania np. klastra na 10 maszyn. Też są automatyzowane, ale potrafią zająć 2 dni.

Zgadzam się, to są wszystko oczywistości dla mnie. Z tymże, o ile dobrze rozumiem, to Ty pisząc o "testowaniu jednej metody" masz na myśli tylko jej kod, a ja wszystko pod spodem, czyli pewno Twoje "5 warstw".

KR
Moderator
  • Rejestracja:prawie 21 lat
  • Ostatnio:2 dni
  • Postów:2964
1
  1. jednoczesny test kilku publicznych metod danej klasy to w ogóle jakiś absurd dla mnie.

Jestem ciekaw jak przetestujesz działanie stosu bez posiadania testu, który najpierw coś na stos wkłada (publiczna metoda) a później to zdejmuje (również publiczna metoda), lub ewentualnie podgląda (tak samo publiczna metoda).

Z mojego doświadczenia wynika jednak, że ja szybciej dostarczam działający kod stosując TDD niż koledzy nie piszący testów

Powinieneś porównać do kolegów piszących testy zaraz po implementacji, a nie do kolegów niepiszących testów. Inna sprawa, że to może być też kwestią umiejętności ludzi w zespole. Jeśli ktoś umie pisać dobry kod, to TDD mu ani nie przeszkodzi ani nie pomoże. Z kolei jak ktoś nie umie napisać porządnie (w sensie projektu) kodu, to TDD mu raczej też nie pomoże. Źle napisany kod bywa trudno testowalny, ale nie oznacza to, że łatwo testowalny kod to dobry kod. Chodzi mi o to, że to się nie ekstrapoluje na inne aspekty jakości kodu.

No, ale CI jest właśnie po to, żeby pomóc wcześnie wyłapać błędy.

CI to już jest druga linia obrony. Miałem na myśli sam proces kodowania implementacji. Jeśli testy wykonują się długo, to pisanie implementacji będzie przez testy skutecznie spowolnione.

edytowany 1x, ostatnio: Krolik
somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:2 dni
  • Lokalizacja:Wrocław
0
Krolik napisał(a):

Jestem ciekaw jak przetestujesz działanie stosu bez posiadania testu, który najpierw coś na stos wkłada (publiczna metoda) a później to zdejmuje (również publiczna metoda), lub ewentualnie podgląda (tak samo publiczna metoda).

W takim przypadku nadal testuję jedną metodę, wkładanie na stos to etap przygotowania testu. Wywołam dwie publiczne metody, ale będę testował jedną.

Inna sprawa, że to może być też kwestią umiejętności ludzi w zespole. Jeśli ktoś umie pisać dobry kod, to TDD mu ani nie przeszkodzi ani nie pomoże.

Czyli jeśli komuś TDD pomaga, to znaczy, że nie pisze dobrego kodu?

Źle napisany kod bywa trudno testowalny, ale nie oznacza to, że łatwo testowalny kod to dobry kod. Chodzi mi o to, że to się nie ekstrapoluje na inne aspekty jakości kodu.

Owszem, to nie jest implikacja. Ale z moich doświadczeń wynika, że to się wszystko jakoś zazębia i ze sobą wiąże. Jeśli tylko nie jestem w stanie łatwo napisać szybkiego testu, i coś z tego powodu poprawiam w projekcie (najczęściej delegując pewne zachowanie do innej klasy), to kod staje się bardziej SOLIDny.

1
somekind napisał(a):

Czyli jeśli komuś TDD pomaga, to znaczy, że nie pisze dobrego kodu?

Krolik napisał(a):

Z kolei jak ktoś nie umie napisać porządnie (w sensie projektu) kodu, to TDD mu raczej też nie pomoże.

Czyli nie, TDD nikomu nie pomaga.

somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:2 dni
  • Lokalizacja:Wrocław
0
the real mućka napisał(a):

Czyli nie, TDD nikomu nie pomaga.

A jednak pomaga, dlatego dopytuję.

KR
Moderator
  • Rejestracja:prawie 21 lat
  • Ostatnio:2 dni
  • Postów:2964
1

Zdania na temat czy pomaga, czy nie, są mocno podzielone w literaturze fachowej. Najczęściej badacze dochodzą do wniosku, że TDD pomaga, jeśli porównują TDD do braku testów w ogóle. Wtedy owszem pomaga, ale sam fakt pisania testów, a nie że pisze się je najpierw.

Tu masz przykład takiego badania, z którego łatwo wyciągnąć mylne wnioski:
http://research.microsoft.com/en-us/groups/ese/nagappan_tdd.pdf

Jak się skoncentrujesz na porównaniu test-first do test-last, to się okazuje, że różnice są bardzo niewielkie:
http://madeyski.e-informatyka.pl/download/Madeyski10c.pdf

Czasem programiści myślą, że TDD jest cool, ale w rzeczywistości piszą gorszy kod i nie zdają sobie z tego sprawy:
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.304.1621&rep=rep1&type=pdf

A czasem TDD troszkę pomaga, ale daleko do tak daleko idących efektów, jakie reklamują zwolennicy TDD:
http://dl.acm.org/citation.cfm?id=1345901

Podsumowując - TDD jest modne i na topie, tak jak kiedyś modne było XP i pair-programming. Mody mają to do siebie, że przemijają.

W takim przypadku nadal testuję jedną metodę, wkładanie na stos to etap przygotowania testu. Wywołam dwie publiczne metody, ale będę testował jedną.

Otóż nie, Twoją intencją jest testowanie jednej metody, ale faktycznie testujesz dwie. Etap przygotowania testu jest częścią testu. Test się wyrypie, niezależnie od tego, czy błąd będzie w pierwszej metodzie, czy w drugiej.

Czyli jeśli komuś TDD pomaga, to znaczy, że nie pisze dobrego kodu

Coś w tym jest. Najgorsi programiści z jakimi miałem do czynienia zawsze byli pod wpływem jakiejś ideologii. Raz było to ślepe stosowanie wzorców projektowych wszędzie gdzie się da po przeczytaniu pewnej książki (i teksty w stylu "na każdy problem jest wzorzec, który go rozwiązuje"), innym razem szał dependency-injection, a niedawno spotykam coraz więcej nawiedzonych programistów z sekty TDD.

edytowany 2x, ostatnio: Krolik
somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:2 dni
  • Lokalizacja:Wrocław
1
Krolik napisał(a):

Podsumowując - TDD jest modne i na topie, tak jak kiedyś modne było XP i pair-programming. Mody mają to do siebie, że przemijają.

Te teksty, które podlinkowałeś też są chyba już nieco "przeminięte". Za to dobrze, że ktoś machnął o tym jakiś papier, lepsze to niż gdyby miał siedzieć i się po prostu nudzić.

Otóż nie, Twoją intencją jest testowanie jednej metody, ale faktycznie testujesz dwie. Etap przygotowania testu jest częścią testu. Test się wyrypie, niezależnie od tego, czy błąd będzie w pierwszej metodzie, czy w drugiej.

Jeśli błąd będzie w pierwszej metodzie, to wysypie także przez faktyczne testy pierwszej metody. Prosta dedukcja pozwoli stwierdzić, która metoda failuje.

Coś w tym jest. Najgorsi programiści z jakimi miałem do czynienia zawsze byli pod wpływem jakiejś ideologii.

Z kolei najgorsi programiści z jakimi współpracowałem ja byli pod wpływem ideologi: "skończyłem dobre liceum i elitarną politechnikę, więc mogę pchać całą logikę do jednej klasy i testować nie muszę".

Raz było to ślepe stosowanie wzorców projektowych wszędzie gdzie się da po przeczytaniu pewnej książki (i teksty w stylu "na każdy problem jest wzorzec, który go rozwiązuje"), innym razem szał dependency-injection, a niedawno spotykam coraz więcej nawiedzonych programistów z sekty TDD.

Ja cały czas piszę przemyślanym stosowaniu TDD w sensownych przypadkach, a nie o robieniu tego na siłę, "bo jest modne". Jeśli ktoś uważa, że użycie TDD samo z siebie sprawia, że wytwarzane oprogramowanie jest bezbłędne i wspaniałe, to jest bardzo naiwny i nie ma pojęcia o tworzeniu aplikacji. To, że ktoś używa TDD źle, że przy okazji zapomina o jakości kodu, to, że ktoś nie rozumie tego podejścia nie zmienia faktu, że w rękach odpowiedniej osoby jest to efektywna metodyka. Pomocny jest sam fakt przemyślenia i spisania tego, co testowany kod ma zrobić i jak zachować się w warunkach brzegowych. Coś o czym ludzie piszący kod najpierw zazwyczaj w ogóle nie myślą. No, ale myślenie zapewne świadczy o byciu słabym programistą. Przecież dobry programista nakurwia kolejne linijki kodu, myślenie jest zbędne.

KR
Moderator
  • Rejestracja:prawie 21 lat
  • Ostatnio:2 dni
  • Postów:2964
0

Jeśli błąd będzie w pierwszej metodzie, to wysypie także przez faktyczne testy pierwszej metody. Prosta dedukcja pozwoli stwierdzić, która metoda failuje.

Nadal nie widzę jak przetestujesz dodawanie do stosu bez testowania metody do podglądania stosu / zdejmowania, albo metodę do podglądania zawartości stosu / zdejmowania elementu bez wywołania wpierw metody do dodawania do stosu? :D Żadnej z tych metod nie da się testować w izolacji.

Poza tym 90% sukcesu to wykrycie błędu. Jeśli test się wywala, to znalezienie dokładnej przyczyny, nawet jeśli ów test wywołuje 5 metod po kolei a nie jedną, nie jest zwykle dużym problemem. Chodzi mi o to, żeby nie traktować tego dogmatycznie. Dla mnie unit-test to testowanie jakiejś własności użytkowej modułu, niekoniecznie wywołanie ściśle jednej metody albo ściśle jednej klasy. Coś, co da się przedstawić łatwo zrozumiałym opisem tekstowym. Czyli dla stosu miełbym test "jak coś włożę to da się zdjąć" albo "jak włożę w kolejności ABCD, to ma się zdejmować w kolejności DCBA". Ten drugi będzie miał kilka wywołań metod.

Z kolei najgorsi programiści z jakimi współpracowałem ja byli pod wpływem ideologi: "skończyłem dobre liceum i elitarną politechnikę, więc mogę pchać całą logikę do jednej klasy i testować nie muszę".

Ależ bardzo często to są akurat ci sami ludzie. Często po etapie pchania wszystkiego do jednej klasy i spektakularnej porażce przychodzi przegięcie w drugą stronę.

edytowany 1x, ostatnio: Krolik
somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:2 dni
  • Lokalizacja:Wrocław
0
Krolik napisał(a):

Nadal nie widzę jak przetestujesz dodawanie do stosu bez testowania metody do podglądania stosu / zdejmowania, albo metodę do podglądania zawartości stosu / zdejmowania elementu bez wywołania wpierw metody do dodawania do stosu? :D Żadnej z tych metod nie da się testować w izolacji.

No jeśli stos będzie miał w sobie jakieś repozytorium, które odpowiednio zamockujemy na potrzeby testu, to będziemy weryfikować mocka repozytorium i z takim testem będzie problemu. :P

A tak na serio - tu mnie masz, w jakichś specyficznych przypadkach faktycznie trzeba testować kilka metod jednocześnie. Tylko jak często typowy programista implementuje stosy? :)

Chodzi mi o to, żeby nie traktować tego dogmatycznie. Dla mnie unit-test to testowanie jakiejś własności użytkowej modułu, niekoniecznie wywołanie ściśle jednej metody albo ściśle jednej klasy.

Zobacz, wcześniej w tym wątku napisałem:

Publiczna metoda to coś, co pozwala wykonać jakiś istotny w ramach dziedziny systemu proces. Wysoce prawdopodobne jest, że w tym celu korzysta z całej hierarchii klas pomocniczych.

Jak dla mnie, to obaj piszemy o tym samym, tylko ja zakładam że dobranie się do tej "własności użytkowej" odbywa się przez publiczną metodę, bo w praktyce zazwyczaj tak jest.

KR
Moderator
  • Rejestracja:prawie 21 lat
  • Ostatnio:2 dni
  • Postów:2964
0

Tylko jak często typowy programista implementuje stosy?

Stosy może nie, ale ostatnio implementowałem własny cache-friendly PriorityHashMap (gdzie priorytety są w wartościach i można je dowolnie zmieniać w czasie O(log n)). I trochę nie bardzo widzę jak to przetestować po jednej metodzie po kawałku. Paradoksalnie dzisiaj kumpel do mnie napisał, że właśnie w tej klasie znalazł buga (tzn. wywala mu się test), na co odruchowo odparłem "niemożliwe"... No i okazało się, że bug był... w teście. :D

Jak dla mnie, to obaj piszemy o tym samym, tylko ja zakładam że dobranie się do tej "własności użytkowej" odbywa się przez publiczną metodę, bo w praktyce zazwyczaj tak jest.

Zgoda, ale wtedy już testujesz warstwę wyżej. Stos nie będzie mieć pojedynczej metody "włóż 2 elementy i zdejmij 2 elementy", natomiast jakiś fragment logiki biznesowej korzystający ze stosu taką operację może wykonać.

Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
0

@somekind @Krolik @Wibowit @yurai @quetzakubica
Piszę sobie właśnie w pracy testy do kodu który popełniłem i zastanowiło mnie jak napisalibyście do tego testy. A może w ogóle inaczej byście to zaimplementowali? Pytam szczególnie dlatego, że moje testy wymagają mockowania instancjonowania obiektów i częściowych mocków, a niektórzy uważają, że jeśli trzeba takiej artylerii to kod jest napisany źle. Ciekawi mnie wasza opinia.

Kod jest generalnie bardzo prosty. Jest sobie pewna klasa serwisowa która udostępnia kilka metod. Każda metoda robi dokładnie to samo -> trawersuje kolekcję, sprawdza dla każdego elementu pewien warunek i jeśli jest spełniony to wykonuje dla tego elementu pewną operację. W związku z tym postanowiłem, że zrobię sobie jedną metodę runOperationOnDataset która przyjmuje jako argumenty listę z danymi, obiekt do ewaluacji warunku oraz obiekt do ewaluacji operacji. W efekcie mam sobie metodę mniej więcej taką:

Kopiuj
    public void runOperationOnDataset(List<Data> Data, Condition condition, Operation operation) {
        for (Data element : data) {
            if (condition.isTrue(data)) {
                operation.execute(data);
            }
        }
    }

Oprócz tej metody jest jeszcze kilka które po prostu wywołują tą metodę z odpowiednimi parametrami. Załóżmy że na przykład takie dwie:

Kopiuj
public void addPadding(List<Data> data, int prePadding, int postPadding){
    runOperationOnDataset(data, new SomeCondition(), new AddPadding(prePadding, postPadding));
}

public void convertHexToDec(List<Data> data, int prePadding, int postPadding){
    runOperationOnDataset(data, new IsHexValueCondition(), new ConvertHexToDec());
}

Jak nie trudno się domyślić same klasy implementujące Condition i Operation są trywialnie proste i mają swoje własne testy.
Ciekawie zaczyna się przy testach dla tej klasy serwisowej. Test dla runOperationOnDataset jest oczywiście dość prosty, ot mockuje sobie warunek oraz operacje i sprawdzam czy operacja została wywołana tyle razy ile razy warunek był prawdziwy i czy jedno i drugie dostawało poprawne parametry.
Znak zapytania pojawia się przy testach metod addPadding oraz convertHexToDec. Oczywiście mógłbym je puścić na normalnych danych i zweryfkować wyniki, ale wymagałoby to przygotowania odpowiednich danych wejściowych. Poza tym test sprawdzałby nie tylko czy sama metoda np. addPadding zachowuje się poprawnie, ale także czy jej warunek oraz operacja działają poprawnie i czy runOperationOnDataset działa poprawnie. A ja jednak chciałbym zweryfikować działanie samego addPadding a nie wszystkich jego zależności.
W efekcie "niezależny" test polega na:

  • stworzeniu częściowego mocka klasy serwisowej, mockującego runOperationOnDataset
  • ustawienia oczekiwania na utworzenie odpowiedniej instancji implementującej Condition oraz Operation
  • ustawienia oczekiwania na wywołanie runOperationOnDataset
  • wywołania metody
  • weryfikacji czy utworzone zostały odpowiednie instancje Conditon oraz Operation i czy runOperationOnDataset zostało wywołane z odpowiednimi parametrami

Pytanie brzmi: czy takie rozdrobnienie ma sens? Jeśli nie, to w jaki sposób byście to przetestowali / w jaki sposób byście to zaimplementowali, jeśli błąd wg. was leży w kodzie a nie w teście :)


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
Zobacz pozostałe 2 komentarze
Shalom
@furious programming ty z tych co nie testują? :P
flowCRANE
Testuję i to do bólu, aż wszystko będzie działać idealnie; Ale po swojemu, bo ten sposób rzadko zawodzi :)
ShookTea
Jestem zadumiony i dumny, będąc wymienionym przez @Shalom tuż obok @Koziołek, @fasadin, @Azarien i @furious programming. Choć równie dobrze to mogło być ironicznie.
Shalom
@ShookTea nie bardzo rozumiem czemu ironiczne. Po prostu chciałem znać opinię kilku użytkowników którzy mogą mieć ciekawego do napisania na ten temat. Oczywiście nie wymieniłem wszystkich ;)
ShookTea
@Shalom w takim razie wyrażę swoją opinię na temat testowania: robię tylko wtedy, gdy nie jestem pewien, jak zadziała funkcja :)
Koziołek
Moderator
  • Rejestracja:prawie 18 lat
  • Ostatnio:około 2 miesiące
  • Lokalizacja:Stacktrace
  • Postów:6821
0

eee.... nie... Dlatego jeżeli nie musisz mieć 100% pokrycia to olej. Szkoda czasu na sprawdzanie czy new rzeczywiście stworzyło obiekt (bo do tego sprowadza się test). Może nie jest to koszerne podejście, ale IMO ekonomiczne.


Sięgam tam, gdzie wzrok nie sięga… a tam NullPointerException
Shalom
Nie do końca chodzi tu o new. Zauważ że test sprawdza też czy w danej metodzie utworzono poprawne funktory, bo generalnie to jest jedyna "logika" zawarta w tej metodzie.
Koziołek
Trochę sztuka dla sztuki takie testowanie. Ja bym to robił na samym końcu jak by mi brakowało pokrycia. To i tak będzie przetestowane na poziomie integracyjnym.
Endrju
  • Rejestracja:około 22 lata
  • Ostatnio:ponad rok
0

Skoro pytasz... ;-)

Jeżeli te funktory Condition i Operation są przetestowane i robią to co powinny, jedyne co pozostało do przetestowania to to, co napisałeś - czy zostały utworzone instancje obiektów i czy metoda została poprawnie wywołana. Zgadzam się z Tobą i gdybm miał to robić (patrz niżej) to pewnie tak właśnie bym to próbował przetestować.

Można też uznać, że testowanie tak prostej metody nie ma wiele sensu. :-P U nas metod poniżej pewnego CCM się nie testuje. Wysiłek potrzebny do napisana tego testu jest niewspółmierny do korzyści z niego wynikających. (To nie zawsze jest dobre podejście)


"(...) otherwise, the behavior is undefined".
edytowany 1x, ostatnio: Endrju
KR
Moderator
  • Rejestracja:prawie 21 lat
  • Ostatnio:2 dni
  • Postów:2964
0

Dla mnie to jest przykład kodu, którego testowanie niewiele wnosi. Masz się upewnić, czy zostały utworzone prawidłowe funktory - ok, to pokaż kod dwóm recenzentom, niech sprawdzą.

somekind
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:2 dni
  • Lokalizacja:Wrocław
1
Shalom napisał(a):

Test dla runOperationOnDataset jest oczywiście dość prosty, ot mockuje sobie warunek oraz operacje i sprawdzam czy operacja została wywołana tyle razy ile razy warunek był prawdziwy i czy jedno i drugie dostawało poprawne parametry.

Czyli testujesz, czy if działa w pętli?
W sumie to Java, więc pewno może nie działać. :P

Znak zapytania pojawia się przy testach metod addPadding oraz convertHexToDec. Oczywiście mógłbym je puścić na normalnych danych i zweryfkować wyniki, ale wymagałoby to przygotowania odpowiednich danych wejściowych. Poza tym test sprawdzałby nie tylko czy sama metoda np. addPadding zachowuje się poprawnie, ale także czy jej warunek oraz operacja działają poprawnie i czy runOperationOnDataset działa poprawnie.

Ja bym raczej napisał takie testy zamiast testów runOperationOnDataset, a być może nawet testów dla poszczególnych Condition czy Operation.

Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:10 minut
0

@Shalom:
Zakładając, że stosowałbyś się do TDD to najpierw napisałbyś testy do addPadding, potem implementację dla niej, potem analogicznie dla convertHexToDec, a dopiero potem zrobiłbyś refaktor, czyli wyodrębniłbyś wspólny kod. Ale na etapie wyodrębniania miałbyś już całkowicie pokryty kod i to by się nie zmieniło, więc i robienie osobnego testu dla wydzielonej metody mogłoby byś spokojnie pominięte.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)