TDD - Test Driven Development

0

Witam, dziś przeczytałem tę ksiązkę :
"The Clean Coder" - Roberta C. Martin . http://www.amazon.com/Clean-Coder-Conduct-Professional-Programmers/dp/0137081073/ref=asap_bc?ie=UTF8
Jeden z rozdziałów jest poświęcony TDD . Zaintrygowało mnie to i trochę googlowałem, ale na 4programmers znalazłem tylko to : http://4programmers.net/Forum/Java/238415-testy_tddjunit_-_pomoc
I tu moje pytanie : czy w pracy kodujecie w ten sposób ? Tzn. czy kładziecie tak mocny nacisk na testowanie, i jak wygląda refaktoring kodu w taki sposób.

Dzięki za odpowiedzi.
Dobrej nocy.

0

U mnie moj manager lubi TDD i zaczyna od pisania testow ktore ustalaja wymagania danej klasy/produktu. Jak masz testy ktore okreslaja wymagania to potem mozna sie bawic w testowanie szczegolow implementacji. Jesli wymagania trzeba przepisac to i testy trzeba przepisac.

W zasadzie zanim wiedzialem co to znaczy TDD to z tego korzystalem nieswiadomie (np. wstawiajac 50 printf ktore sprawdzaja czy to co juz mam dziala tak jak mialo dzialac) bo tak jest po prostu latwiej. Piszesz klase i oczekujesz ze zrobi X, Y i Z. Jak przetestujesz dana klase w stopniu zadowalajacym to mozesz pisac kolejna klase ktora uzywa tamtej klasy i wiesz ze jesli gdzies jest blad to prawie na pewno jest w tym co wlasnie piszesz.

Prawie zawsze na wczesniejszym testowaniu oszczedzisz wiecej czasu niz stracisz.

0

Póki co robiłem raczej w ten sam sposób - wyrzucam na konsole wynik (o ile myślę, że w domowych projektach to nic złego, o tyle doświadczenia zawodowego nie mam). Coś tam o testach szukałem, coś napisałem dla ćwiczeń, ale teraz tak naprawdę się tym zainteresowałem.

2

"Put the thests first" czasem jest nie do zrobienia. Łatwo też zabetonować implementację niepotrzebnymi testami. TDD tak, ale z głową.

2
krwq napisał(a)

W zasadzie zanim wiedzialem co to znaczy TDD to z tego korzystalem nieswiadomie (np. wstawiajac 50 printf ktore sprawdzaja czy to co juz mam dziala tak jak mialo dzialac) bo tak jest po prostu latwiej.

No nie. TDD polega na tym, że testy pisze się najpierw, i to testy decydują o tym co piszesz (bo "test driven"). TDD != testy jednostkowe. Można robić testy jednostkowe nie stosując wcale TDD.

A co do tych printów to pewnie każdy je czasem pisze dla debugowania, ale zwykle problem jest z ich czytaniem. Będziesz 50 printów czytał? A jak będzie ich 500? Automatyczne testy jednostkowe pozwalają na zdefiniowanie warunków poprawnego wykonania programu, a potem automatyczne wyrzucenie Errora jeśli którykolwiek z warunków się nie spełni. Testy są po to, żeby programistę odciążyć, żeby mógł się skupić na pisaniu programu a nie na debugowaniu.

Co do TDD samego to ostatnio piszę własny projekt open source starając się korzystać z TDD, choć nie zawsze mi się to udaje. No i czasem jest to po prostu niewygodne. Czasem wygodniej mi coś napisać, a potem napisać do tego testy. Lata przyzwyczajeń pewnie. Chociaż TDD ogólnie wydaje mi się atrakcyjną formą pisania, bo jeśli pisać testy, to czemu ich nie pisać najpierw, na świeżo? Testy pisane później mogą nie być już tak dobre...

Inna rzecz, że TDD zakłada, że wiesz co chcesz robić, a potem to piszesz. A jeśli specyfikacje się ciągle zmieniają? Co prawda tutaj TDD z jednej strony ma zaletę - otestowany kod łatwiej się refaktoryzuje, bo wiesz, że nic nie spieprzysz przez swoje zmiany (bo jakbyś spieprzył to test by sfailował), ale z drugiej strony jeśli kod bardzo się zmienia, to co chwila muszą się zmieniać również... testy. No bo założenia się zmieniają. Więc nie byłbym jakimś radykałem. Testy automatyczne się przydają, ale niekoniecznie w każdym projekcie TDD.

Kolejna rzecz jest taka, że wiele rzeczy ciężko otestować, szczególnie takie rzeczy jak GUI, wygląd, regresje wizualne etc. Sam Robert Martin zresztą podchodził do tego dość liberalnie właśnie w tej książce, o której wspomina OP.

0
LukeJL napisał(a):

Co do TDD samego to ostatnio piszę własny projekt open source starając się korzystać z TDD, choć nie zawsze mi się to udaje. No i czasem jest to po prostu niewygodne. Czasem wygodniej mi coś napisać, a potem napisać do tego testy. Lata przyzwyczajeń pewnie. Chociaż TDD ogólnie wydaje mi się atrakcyjną formą pisania, bo jeśli pisać testy, to czemu ich nie pisać najpierw, na świeżo? Testy pisane później mogą nie być już tak dobre...

Zakładając np. taką sytuację, że napisałeś jakąś tam część z tego projektu, po czym to dokładnie sprawdziłeś i stwierdzasz że funkcjonuje poprawnie, postanawiasz jeszcze napisać do tego testy automatyczne, gdzie na ich podstawie wyjdzie, że wszystko działa absolutnie prawidłowo. Co przez to zyskasz?

Co do tych testów i użytych tam metod typu AssertEquals, AssertTrue, AssertFalse czy inne tego typu, jaką wartość będzie miał taki test jak nie uwzględnisz tam jakiegoś ekstremalnego przypadku?

Dla przykładu weźmy może to Codility i to przykładowe EQUI. Tam przecież następuje to testowanie z jakimiś tam różnymi przypadkami, w tym także jakieś liczby ekstremalne, z zakresu max int czy -max int, co pozwala wykryć jakieś błędy wynikające chociażby z nieprawidłowego typu jakiejś tam zmiennej. W tym kontekście nie widzę znaczenia czy test był napisany na początku czy dopiero później.

0

Mnie ciekawi czy TDD nie powoduje patologii w architekturze oprogramowania.
Bo jakoś trudno mi sobie wyobrazić równoczesne myślenie o wzorcach projektowych i TDD.
Czy to się nie kłóci?
Można w ten sposób robić klasy typu STL, ale gdy masz do zrobienia stronę na której jest kilka warstw logicznych i wszystko ze sobą musi sensownie gadać to czy TDD w tym nie przeszkadza?

0

Ja staram się używać unit testów tam gdzie jest to możliwe, ale po napisaniu funkcjonalności. Czasem okazuje się, że nie wszystko pokryte jest testami i znajdujemy błędy. Wtedy uzupełniamy testy, które zapewniają, że danego fragmentu kodu już się nie zepsuje. Generalnie nie jest to typowy TDD, ale nam odpowiada ;)

0

Lubię pisać testy przed. W szczególności jak mam jakiś błąd. Wtedy przyjemnie replikować go sobie w teście. Mniej więcej wygląda to tak:

  1. Mam legacy kod z bugiem.
  2. Dopisuje test-case jak ma działać, test jest na żółto bo kod testowany nie działa.
  3. Poprawiam kod i test jest na zielono.
0

TDD niektórzy rozwijają na Tests During Development (mogłem cos przekrecic). Nie ma czegoś takiego jak pisanie testów przed implementacją, bo to się przeplata. Może na poczatku postanie 1. test bez implementacji, ale potem to juz jest równolegle.

0

Aby rozważać TDD trzeba i tak posiadać interfejsy, bo bez tego w jaki sposób rozważać napisanie testów? Nie da się. Więc jakiś kod zawsze trzeba mieć, minimum sygnaturki metod.

2
drorat1 napisał(a):

Zakładając np. taką sytuację, że napisałeś jakąś tam część z tego projektu, po czym to dokładnie sprawdziłeś i stwierdzasz że funkcjonuje poprawnie, postanawiasz jeszcze napisać do tego testy automatyczne, gdzie na ich podstawie wyjdzie, że wszystko działa absolutnie prawidłowo. Co przez to zyskasz?

  1. Jezeli rozwijasz jakis system, to zmiany w jednym miejscu moga miec wplyw na dzialanie systemu w innym miejscu. Dzieki testom masz wieksze szanse sie o tym dowiedziec zanim system trafi na produkcje.

  2. Latwiej sie refaktoryzuje kod majac pewne pokrycie testami bo w momencie refaktoryzacji istnieje niemale ryzyko, ze cos zrobisz nie tak. Majac testy dowiesz sie, ze jakiegos przypadku nie uwzgledniles podczas dopieszczania kodu programu.

  3. Testy pomagaja w zachowaniu wstecznej kompatybilnosci (o ile jest konieczna).

Nalezy zwrocic tez uwage na fakt, ze niektore rzeczy dosc ciezko przetestowac recznie. Przyklad: dana jest aplikacja, ktorej celem jest monitorowanie temperatury pewnego urzadzenia. Jezeli temperatura przekroczy wartosc X to oprogramowanie powinno wyslac powiadomienie z informacja o takim stanie rzeczy. Jak chcesz to przetestowac recznie skoro urzadzenie akurat nie przekracza temperatury X i moze nawet nigdy nie przeroczyc? W testach automatycznych jest to mozliwe poniewaz mozna zmienic sztucznie wartosc zwracana przez metode dokonujaca pomiar temperatury.

drorat1 napisał(a):

Co do tych testów i użytych tam metod typu AssertEquals, AssertTrue, AssertFalse czy inne tego typu, jaką wartość będzie miał taki test jak nie uwzględnisz tam jakiegoś ekstremalnego przypadku?

Jak sie o tym ekstremalnym przypadku dowiesz (zakladam, ze ten ekstremalny przypadek to blad a nie poprawnie dzialajacy kod z tym, ze nie przetestowany pod katem tego przypadku) to zrobisz sobie dodatkowy test, pokrywajacy ten przypadek. Nastepnie poprawisz kod systemu, nad ktorym pracujesz tak aby byl dostosowany do tego ekstremalnego przypadku. To rozwiazanie ma te zalete, ze prawdopodobienstwo, ze blad pojawi sie ponownie jest duzo nizsze.

drorat1 napisał(a):

W tym kontekście nie widzę znaczenia czy test był napisany na początku czy dopiero później.

Moim zdaniem pisanie testow w pierwszej kolejnosci moze pozytywnie wplynac na jakos kodu zrodlowego. W mojej opinii po prostu latwiej sie pisze testy do kodu o wyzszej jakosci, a to troche zniecheca do pojscia na skroty.

vpiotr napisał(a):

Mnie ciekawi czy TDD nie powoduje patologii w architekturze oprogramowania.

Nie wiem jakie zdanie maja inni programisci, ale mnie sie latwiej testuje cos co ma dobra architekture. Jezeli nie jestem ze swoja opinia odosobniony to powiedzialbym, ze TDD (wlasciwie to niekoniecznie TDD ale ogolnie mowiac testy) bardziej te patologie eliminuja niz ja tworza, poniewaz zamiast podejscia "stworze sobie troche gorsza architekture i bede mial szybciej funkcjonalnosc X" byc moze bedziesz mial podejscie "jak sobie teraz pojde na skroty to pozniej bede tracil niepotrzebnie czas na testy". No ale to opinia dosc subiektywna.

Co do tych warstw, gdzie wszystko musi ze soba dzialac: w testach jednostkowych chodzi o to, aby przetestowac fragment kodu w izolacji od reszty kodu (klas i metod zaleznych). Te wszystkie warstwy sobie zaslepiasz (np. stosujac mocki) i zmieniasz ich zachowanie.

Zalozmy, ze:

  1. Pracujesz nad systemem, panelem klienta gdzie kazdy uzytkownik wplaca pieniadze w ramach abonamentu za dana usluge. Jezeli suma wplat z calego okresu dzialalnosci przekroczy 10 000 000 PLN to masz wyslac wiadomosc e-mail do szefa, ze biznes sie kreci i dobrze byloby pomyslec o podwyzkach dla pracownikow :)
  2. Masz klase Mailer zawierajaca metode sendMail.
  3. Masz klase Payments, ktora posiada metode sumAll
  4. Masz klase Scheduler, ktora posiada cyklicznie wykonywana metode checkSumOfAllPayments.

Przykladowy scenariusz testowy metody checkSumOfAllPayments moglby wygladac tak:

  1. Tworzysz mocka klasy Mailer tak aby metoda sendMail nic nie robila.
  2. Tworzysz mocka klasy Payments i zmieniasz zachowanie metody sumAll tak aby bez zadnego odwolywania sie do bazy danych po prostu zwrocila wartosc 10 000 001 (dzialasz na obiekcie, ktory udaje, ze jest implementacja klasy Payments, chociaz w rzeczywistosci nie jest).
  3. W klasie Scheduler pozbywasz sie zaleznosci. Jezeli masz pola typu Mailer i Payments to w ich miejsca wstawiasz mocki, tak aby klasa Scheduler nie odwolywala sie do rzeczywistych implementacji powyzszych klas.
  4. Sprawdzasz czy metoda sendMail zostala wywolana z odpowiednimi parametrami (odpowiedni adres e-mail, temat i tresc).

Innymi slowy: zaleznosci, w tych roznych warstwach Cie kompletnie nie interesuja. No chyba, ze mowa o testach integracyjnych to co innego.

Dodam, ze do mockowania i weryfikacji wywolan sa odpowiednie frameworki.

0
tk napisał(a):

Nalezy zwrocic tez uwage na fakt, ze niektore rzeczy dosc ciezko przetestowac recznie. Przyklad: dana jest aplikacja, ktorej celem jest monitorowanie temperatury pewnego urzadzenia. Jezeli temperatura przekroczy wartosc X to oprogramowanie powinno wyslac powiadomienie z informacja o takim stanie rzeczy. Jak chcesz to przetestowac recznie skoro urzadzenie akurat nie przekracza temperatury X i moze nawet nigdy nie przeroczyc? W testach automatycznych jest to mozliwe poniewaz mozna zmienic sztucznie wartosc zwracana przez metode dokonujaca pomiar temperatury.

Akurat to można by też emulować. Dam przykład. Przypuśćmy że tworzę oprogramowanie które czyta sobie tą temperaturę odczytując po prostu dane z jakiejś ramki wysyłanej przez urządzenie (i tu może być chociażby Arduino z LM35), za pośrednictwem portu szeregowego, to mam 2 opcje.

  1. Jeśli mam możliwość to mogę sobie podłączyć czujnik dający wyższe napięcie co by obrazowało wyższą temperaturę albo po prostu emulować to jakimś potencjomatrem podłączanym do +5V, GND i np. A0
  2. Można również zrobić emulację danych z RS-232 wczytując te dane po prostu z jakiegoś pliku tekstowego, tylko że w tym celu należałoby przygotować pewnie osobną klasę.

I np. tak (JAVA):

public interface IDataReader
{
    double getTemperature();
} 

// 1:
public class SerialDataReader implements IDataReader
{
    @Override
    public double getRemperature()
    {
        //instrukcje z wykorzystaniem klas do obsługi RS-232
    }
}

// 2:
public class TextFileDataReader implements IDataReader
{
    @Override
    public double getRemperature()
    {
        //dane z pliku tekstowego
    }
}

Jest możliwość takiej emulacji i zresztą kiedyś to robiłem. Tylko że w tym przypadku jest problem, że na czas takiego ręcznego testowania muszę podpiąć tą klasę TextFileDataReader zamiast SerialDataReader, co może stwarzać problemy wynikające po prostu z przeoczeń, bo zawsze przecież jest taka opcja, że mogę zapomnieć potem podpiąć z powrotem właściwą klasę.

0
drorat1 napisał(a):
  1. Jeśli mam możliwość to mogę sobie podłączyć czujnik dający wyższe napięcie co by obrazowało wyższą temperaturę albo po prostu emulować to jakimś potencjomatrem podłączanym do +5V, GND i np. A0

Jezeli masz taka mozliwosc to pewnie moglbys tak zrobic. Problem jednak w tym, ze po pierwsze nie zawsze taka mozliwosc jest, a po drugie to nie bardzo widze tutaj ulatwienia, za to widze pewne utrudnienia.

drorat1 napisał(a):
  1. Można również zrobić emulację danych z RS-232 wczytując te dane po prostu z jakiegoś pliku tekstowego, tylko że w tym celu należałoby przygotować pewnie osobną klasę.

I np. tak (JAVA):

No mozna, tylko po co? Utworzyles sobie klase TextFileDataReader. Teraz musisz instancje tej klasy jakos wepchnac w miejsce, gdzie normalnie uzywana bylaby instancja klasy SerialDataReader. Jak bys to zrobil biorac pod uwage, ze nie chcesz robic testow automatycznych? Twoje oprogramowanie byloby uruchamiane z parametrami i w zaleznosci od parametru "x" jakas tam klasa zamiast korzystac z SerialDataReader uzywalaby TextFileDataReader? Dodatkowe warunki w kodzie produkcyjnym, ktore okreslaja ktorego DataReadera nalezy uzyc? Nie widzisz w tym pewnych komplikacji?

Wlasciwie ta klasa TextFileDataReader pelni role takiego mocka z ta roznica, ze jest zrobiona w malo dynamiczny sposob. Efekt tego jest calkiem podobny do mockow generowanych automatycznie przez frameworki z ta roznica, ze te drugie nie rozszerzeaja niepotrzebnie systemu o dodatkowe implementacje (potrzebne tylko po to, zeby cos przetestowac).

Jak juz jestesmy przy javie to ja sobie moge w takim mockito stworzyc implementacje IDataReader w locie. Przyklad wygladalby mniej-wiecej tak:

IDataReader reader = mock(IDataReader.class);
when(reader.getTemperature()).thenReturn(50);

Teraz sobie takiego readera moge uzyc do testow - w tym przypadku zwroci on wartosc 50. Nie musze tworzyc nowych klas a klasa testow jest w osobnym projekcie (lub w tym samym projekcie ale wyizolowana od wlasciwego kodu) i nie miesza sie z kodem odpowiedzialnym za logike. Zaleznosci tez sobie podmienie w kodzie testowym tak aby nie naruszac niepotrzebnie logiki aplikacji.

Swoja droga: co bys zrobil jakby metoda getTemperature nie zwracala wartosci a Ty bys chcial wiedziec czy zostala ona wywolana? Bo ja bym napisal:

verify(reader).getTemperature();

Proste i skuteczne :)

drorat1 napisał(a):

Tylko że w tym przypadku jest problem, że na czas takiego ręcznego testowania muszę podpiąć tą klasę TextFileDataReader zamiast SerialDataReader, co może stwarzać problemy wynikające po prostu z przeoczeń, bo zawsze przecież jest taka opcja, że mogę zapomnieć potem podpiąć z powrotem właściwą klasę.

No i wlasnie dlatego lepiej tego nie robic. Lepiej uzyc po prostu normalnych testow i po pierwsze nie martwic sie, ze czegos tam nie podpiales a po drugie miec mozliwosc wykonania takiego testu np. za miesiac aby upewnic sie ze nic sie nie zepsulo.

PS.
Sorry, ale pisalem troche na szybko.

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