Tworzy obiekty, które Twoim zdaniem będą potrzebne w pierwszym teście w TDD

15

Jestem dość nowy w TDD i mam problemy z tworzeniem pierwszego testu, który pojawia się przed jakimkolwiek kodem implementacyjnym. Bez żadnych ram do kodu implementacyjnego mogę napisać mój pierwszy test, jakkolwiek chcę, ale zawsze wydaje się, że jest on skażony moim sposobem myślenia o problemie w Javie / OO.

Na przykład w moim Github ConwaysGameOfLifePrzykład pierwszy test, który napisałem (rule1_zeroNeighbours) Zacząłem od stworzenia obiektu GameOfLife, który nie został jeszcze zaimplementowany; nazywał metodę set, która nie istniała, metodę step, która nie istniała, metodę get, która nie istniała, a następnie użyła asert.

Testy ewoluowały, gdy napisałem więcej testów i zostały przebudowane, ale początkowo wyglądało to mniej więcej tak:

@Test
public void rule1_zeroNeighbours()
{
    GameOfLife gameOfLife = new GameOfLife();
    gameOfLife.set(1, 1, true);
    gameOfLife.step();
    assertEquals(false, gameOfLife.get(1, 1));
}

To wydawało mi się dziwne, kiedy wymuszałem projektowanie implementacji w oparciu o to, jak postanowiłem na tym wczesnym etapie napisać pierwszy test.

W jaki sposób rozumiesz TDD, czy to w porządku? Wydaje mi się, że przestrzegam zasad TDD / XP, ponieważ moje testy i implementacja ewoluowały wraz z refaktoryzacją, więc jeśli ten początkowy projekt okazałby się bezużyteczny, byłby otwarty na zmiany, ale wydaje mi się, że wymuszam kierunek na rozwiązanie, zaczynając w ten sposób.

Jak jeszcze ludzie używają TDD? Mógłbym przejść przez kolejne iteracje refaktoryzacji, zaczynając od braku obiektu GameOfLife, tylko prymitywów i metod statycznych, ale wydaje się to zbyt wymyślone.

Encaitar
źródło
5
TDD nie zastępuje ani starannego planowania, ani starannego wyboru wzorca projektowego. To powiedziawszy, zanim napisałeś jakąkolwiek implementację, aby spełnić pierwsze kilka linii testu, jest to znacznie lepszy czas niż po napisaniu zależności, aby uznać, że twój plan jest głupi, że wybrałeś zły wzorzec, a nawet po prostu że niezręczne lub mylące jest wywoływanie klasy w sposób wymagany przez test.
svidgen

Odpowiedzi:

9

To wydawało mi się dziwne, kiedy wymuszałem projektowanie implementacji w oparciu o to, jak postanowiłem na tym wczesnym etapie napisać pierwszy test.

Myślę, że to jest kluczowy punkt w twoim pytaniu, czy jest to pożądane, zależy od tego, czy skłaniasz się ku pomysłowi codeninji, że powinieneś zaprojektować z góry, a następnie użyć TDD do wypełnienia implementacji, czy pomysł Durrona, że ​​testy powinny być zaangażowane w realizacja projektu oraz jego wdrożenia.

Myślę, że który z nich wolisz (lub gdzie wpadasz w środek), musisz odkryć coś dla siebie. Warto zrozumieć zalety i wady każdego podejścia. Prawdopodobnie jest ich dużo, ale powiedziałbym, że głównymi są:

Pro Upfront Design

  • Niezależnie od tego, jak dobry jest proces TDD w projektowaniu, nie jest idealny. TD bez określonego celu może czasem popaść w ślepy zaułek, a przynajmniej niektórych z tych ślepych zaułków można było uniknąć przy odrobinie wstępnego myślenia o tym, gdzie chcesz skończyć. Ten artykuł na blogu przedstawia ten argument na przykładzie kata z cyframi rzymskimi i ma raczej niezłą, ostateczną implementację.

Profesjonalny projekt jazdy testowej

  • Budując implementację wokół klienta kodu (twoich testów), otrzymujesz zgodność YAGNI prawie za darmo, o ile nie zaczniesz pisać niepotrzebnych przypadków testowych. Mówiąc bardziej ogólnie, otrzymujesz interfejs API zaprojektowany z myślą o jego użyciu przez konsumenta, co jest ostatecznie tym, czego chcesz.

  • Pomysł, aby narysować kilka diagramów UML przed napisaniem kodu, a następnie po prostu uzupełnieniem luk, jest przyjemny, ale rzadko realistyczny. W Code Complete Steve'a McConnella projekt jest znany jako „niegodziwy problem” - problem, którego nie można w pełni zrozumieć bez przynajmniej częściowego jego rozwiązania. Połącz to z faktem, że sam problem może ulec zmianie w wyniku zmieniających się wymagań, a ten model projektowania zaczyna być nieco beznadziejny. Jazda próbna pozwala po prostu odgryźć jeden kawałek pracy na raz - w projektowaniu, a nie tylko we wdrażaniu - i wiedzieć, że przynajmniej przez czas życia zmieniający kolor z czerwonego na zielony, to zadanie będzie nadal aktualne i istotne.

Jeśli chodzi o twój konkretny przykład, jak mówi durron, jeśli zdecydowałeś się na wyeliminowanie projektu, pisząc najprostszy test, wykorzystując minimalny interfejs, jaki można, prawdopodobnie zacząłbyś od prostszego interfejsu niż ten we fragmencie kodu .

Ben Aaronson
źródło
Link był bardzo dobrze przeczytany Ben. Dzięki za udostępnienie tego.
RubberDuck,
1
@RubberDuck Nie ma za co! Właściwie nie do końca się z tym zgadzam, ale myślę, że świetnie się spisuje, argumentując ten punkt widzenia.
Ben Aaronson,
1
Ja też nie jestem pewien, ale to dobrze uzasadnia. Myślę, że właściwa odpowiedź znajduje się gdzieś pośrodku. Musisz mieć plan, ale na pewno, jeśli twoje testy będą dziwne, przeprojektuj. W każdym razie ... ++ dobrze pokaż starą fasolę.
RubberDuck,
17

Aby napisać test w pierwszej kolejności, musisz zaprojektować interfejs API , który następnie zamierzasz wdrożyć. Już zacząłeś niewłaściwą stopę, pisząc test, aby utworzyć cały GameOfLife obiekt i używając go do wdrożenia testu.

Z praktycznych testów jednostkowych z JUnit i Mockito :

Na początku możesz czuć się niezręcznie, pisząc coś, czego nawet nie ma. Wymaga to niewielkiej zmiany nawyków programistycznych, ale po pewnym czasie przekonasz się, że jest to świetna okazja do zaprojektowania. Pisząc najpierw testy, masz szansę na stworzenie interfejsu API, który jest wygodny dla klienta. Twój test jest pierwszym klientem nowo narodzonego API. Na tym właśnie polega TDD: projekt interfejsu API.

W teście niewiele próbujesz zaprojektować interfejs API. Utworzyłeś stanowy system, w którym cała funkcjonalność zawarta jest w GameOfLifeklasie zewnętrznej .

Gdybym miał napisać tę aplikację, pomyślałbym o elementach, które chcę zbudować. Na przykład, mógłbym stworzyć Cellklasę, napisać do tego testy, zanim przejdę do większej aplikacji. Z pewnością stworzyłbym klasę dla struktury danych „nieskończonej w każdym kierunku”, która jest wymagana do poprawnego wdrożenia Conwaya i przetestowania tego. Gdy to wszystko zostało zrobione, pomyślałem o napisaniu ogólnej klasy, która ma mainmetodę i tak dalej.

Łatwo jest przeszlifować krok „napisz test, który się nie powiedzie”. Ale napisanie nieudanego testu, który działa tak, jak chcesz, jest rdzeniem TDD.

durron597
źródło
1
Biorąc pod uwagę, że Cell byłby tylko opakowaniem boolean, ten projekt z pewnością byłby gorszy pod względem wydajności. Chyba że w przyszłości będzie musiał zostać rozszerzony na inne automaty komórkowe z więcej niż dwoma stanami?
user253751,
2
@immibis To sprzeczka o szczegóły. Możesz zacząć od klasy reprezentującej kolekcję komórek. Możesz także migrować / scalać klasę komórek i jej testy z klasą reprezentującą kolekcję komórek później, jeśli wydajność stanowi problem.
Eric
@immibis Liczba aktywnych sąsiadów może być przechowywana ze względu na wydajność. Liczba kleszczy, które komórka
przeżyła
Przedwczesna optymalizacja @immibis jest złem ... Ponadto unikanie prymitywnej obsesji to świetny sposób na napisanie kodu dobrej jakości, bez względu na liczbę obsługiwanych stanów. Spójrz na: jamesshore.com/Blog/PrimitiveObsession.html
Paul
0

Istnieje inna szkoła myślenia na ten temat.

Niektórzy mówią: test nie kompiluje się to błąd - idź napisz najmniejszy dostępny kod produkcyjny.

Niektórzy mówią: dobrze jest napisać test najpierw sprawdzić, czy jest do bani (czy nie), a następnie utworzyć brakujące klasy / metody

Przy pierwszym podejściu jesteś naprawdę w cyklu refrakcji czerwono-zielonej. Z sekundą masz nieco szerszy przegląd tego, co chcesz osiągnąć.

Od Ciebie zależy wybór sposobu pracy. Oba podejścia IMHO są prawidłowe.

timory
źródło
0

Nawet kiedy wdrażam coś w sposób „zhakuj to razem”, wciąż wymyślam zajęcia i kroki, które będą zaangażowane w cały program. Więc przemyślałeś to i zapisałeś te przemyślenia projektowe jako test - to świetnie!

Teraz powtarzaj obie implementacje, aby wykonać ten wstępny test, a następnie dodaj więcej testów, aby ulepszyć i rozszerzyć projekt.

Pomocne może być użycie Ogórka lub podobnego narzędzia do napisania testów.

gbjbaanb
źródło
0

Zanim zaczniesz pisać testy, powinieneś pomyśleć o tym, jak zaprojektować swój system. Powinieneś poświęcić sporo czasu na etapie projektowania. Jeśli to zrobisz, nie poczujesz zamieszania w stosunku do TDD.

TDD jest tylko linkiem do programowania : TDD
1. Dodaj test
2. Uruchom wszystkie testy i sprawdź, czy nowy się nie powiedzie
3. Napisz kod
4. Uruchom testy
5. Kod refaktora
6. Powtórz

TDD pomaga objąć wszystkie wymagane funkcje, które zaplanowałeś przed rozpoczęciem tworzenia oprogramowania. link: Korzyści

codeninja.sj
źródło
0

Z tego powodu nie lubię testów na poziomie systemu napisanych w języku Java lub C #. Spójrz na SpecFlow dla c # lub jednego z frameworków testowych opartych na Cucumber dla java (może JBehave). Wtedy twoje testy mogą wyglądać mniej więcej tak.

wprowadź opis zdjęcia tutaj

I możesz zmienić projekt obiektu bez konieczności zmiany wszystkich testów systemu.

(„Normalne” testy jednostkowe są świetne podczas testowania pojedynczych klas).

Jakie są różnice między frameworkami BDD dla Java?

Ian
źródło