Test-Driven Development – dlaczego robisz to źle?

Sebastian Malaca
Ikona kalendarza
6 maja 2021

Test-Driven Development jest jedną z tych praktyk, które pomimo tego, że są w świadomości programistów od bardzo dawna, nie są szeroko stosowane. Znam wiele osób, które w trakcie rozmowy zgadzają się ze wszystkimi korzyściami, które niesie ze sobą TDD, a mimo to, Ci sami ludzie często nie praktykują TDD twierdząc, że „u mnie w projekcie to nie działa”.

Najczęściej jednak problem nie leży w specyficznym projekcie. Zazwyczaj wynika on z braku doświadczenia i umiejętności w korzystaniu z TDD. Jednym z częstych błędów popełnianych przez osoby zaczynające swoją przygodę z Test-Driven Development jest chęć wykorzystania tej techniki do zaimplementowania każdej nowej klasy czy też metody. W tym artykule wytłumaczę, dlaczego jest to zły pomysł i co można zrobić, aby się przed tym błędem uchronić. Jeżeli chcesz dowiedzieć się więcej na ten temat zapoznaj się również z kursem Masterclass Clean Architecture.

Na czym polega Test-Driven Development?

Test-Driven Development jest techniką pomagającą tworzyć kod produkcyjny, z którego łatwo korzystać i który nie posiada nadmiarowej implementacji. Ważne jest, aby pamiętać, że TDD nie jest techniką, której celem jest tworzenie dobrych testów (chociaż jest to z pewnością pozytywny efekt uboczny). Testy są narzędziem pomagającym pisać lepszy kod produkcyjny.

Cała idea TDD sprowadza się do tego, aby cykl dodawania kodu rozbić na trzy fazy:

  1. Red – tworzymy test, który się uruchamia, ale nie przechodzi.
  2. Green – dodajemy kod, który sprawia, że odpalenie testów kończy się sukcesem.
  3. Refactoring – poprawa jakości napisanego kodu.
obraz119.webp

Jak powstaje problem?

Spójrzmy na przykład stosowania Test-Driven Development:

  1. Rozpoczynamy od stworzenia pierwszego scenariusza testowego, który uruchamiamy, a który nie przechodzi:
obraz2.webp
  1. Następnie dodajemy kod, który spełnia wymagania zawarte w pierwszym teście:
obraz3.webp
  1. Kolejnym krokiem jest refaktoryzacja testów oraz/lub kodu produkcyjnego jeżeli jest taka potrzeba:
obraz4.webp
  1. Dodajemy kolejny scenariusz testowy:
obraz5.webp
  1. Dodajemy kod spełniający nowe wymagania:
obraz6.webp
  1. Kolejna refaktoryzacja:
obraz7.webp

Jak widać, podczas refaktoryzacji wydzieliliśmy z naszej klasy SystemUnderTests (SUT) dwie zależności. To jest właśnie moment, w którym wielu programistów popełnia błąd – zaczynają dodawać testy do naszych nowo powstałych zależności. Co więcej, często zdarza się, że starają się to zrobić wykorzystując Test-Driven Development.

obraz8.webp

Takie postępowanie często sprawiają, że programiści porzucają TDD ze względu na zbyt duży koszt i ciężar, który ze sobą niesie. I szczerze mówiąc, mógłbym się z tym zgodzić gdyby nie to, że … nie ma to nic wspólnego Test-Driven Development.

Dlaczego to jest złe?

Na początku jeszcze raz powtórzę, że dopisanie testów do nowo wydzielonych zależności nie jest w żaden sposób powiązane z TDD. Nie w taki sposób działa ta technika. Dlaczego jednak zrobienie tej przerwy jest takie problematyczne?

  1. Scenariusze testowe, które dodajemy w krokach Red, mają na celu implementację funkcjonalności, która realizuje nowe wymagania. To jest główny powód, dla którego SUT w ogóle powstaje i jest rozwijany podczas kolejnych cykli. Jeżeli zaczynamy pisać testy do zależności, które powstały w wyniku refaktoryzacji i zaczynamy je rozwijać korzystając z TDD może okazać się, że dodamy tam funkcjonalność, która tylko wydaje się przydatna później.

  2. Dodając testy do każdej zależności, musimy oderwać się od dodawania kolejnych scenariuszy testowych mających na celu rozwój głównej funkcjonalności (SUT), a co za tym idzie, spowalniamy sam proces realizacji tych wymagań.

  3. W kolejnych cyklach, dodając większą ilość scenariuszy i modyfikując implementację SUT możemy dojść do wniosku, że wcześniejsze refaktoryzacje (wydzielenie klas Dependency1 oraz Dependency2) jednak nie są potrzebne i można się ich pozbyć lub zmodyfikować. Dopóki nie skończysz implementacji SUT struktura klas może się zmieniać wielokrotnie. Jeżeli tak się stanie, to wysiłek włożony w testy zależności zostanie zmarnowany.

Jak robić to dobrze?

  1. Skup się na rozwoju SUT – funkcjonalność, która ma być za pomocą SUT dostarczona jest naszym głównym zadaniem.
  2. Nie trać swojego czasu na poprawianie i rozwój zależności, które jeszcze mogą się zmienić.
  3. Nie trać swojego czasu na testowanie zależności, które mogą zniknąć zaraz po tym jak dodasz kolejny scenariusz testowy.
  4. Nie przestawaj rozwijać SUT aż do momentu kiedy jesteś w stanie dodać kolejny test, który nie przejdzie. Dopiero gdy nie znajdziesz takiego testu, możesz skupić się na rozwoju i testach wydzielonych zależności.
obraz9.webp

Dlatego też naszym kolejnym krokiem nie powinno być dodawanie testów do Dependency1 i Dependency2 tylko dodanie kolejnego scenariusza testowego naszej funkcjonalności (SUT):

obraz10.webp

TDD to nie koniec

Oczywiście nie oznacza to, że testy klas Dependency1 oraz Dependency2 nie zostaną dodane. Prawda jest taka, że po skończeniu TDD, gdy nie będziesz w stanie dodać żadnego nowego scenariusza, który nie przechodzi, możesz nadal dostrzec miejsca/scenariusze, które warto dodatkowo zweryfikować:

  1. Możliwe, że będziesz chciał dodać testy do zależności, które powstały podczas refaktoryzacji.
  2. Czasami istnieje potrzeba dodania testów, gdzie dane wejściowe są bardziej złożone i skomplikowane niż te wykorzystane podczas kroków TDD.

Podsumowanie

Test-Driven Development to technika rozwoju kodu produkcyjnego, a nie pisania testów. To oznacza, że po skończeniu TDD mogą istnieć jeszcze elementy kodu, które będziecie chcieli zweryfikować. Należy również pamiętać, że tę technikę najczęściej stosujemy, dodając nowe funkcjonalności i to właśnie o kształt i implementacje tych funkcjonalności (niezależnie od tego z ilu elementów się składają) TDD pomaga nam zadbać. To właśnie funkcjonalność, a nie każda pojedyncza klasa jest miejscem, gdzie wykorzystanie Test-Driven Development przynosi nam więcej pożytku, niż kosztuje wysiłku.

Chcesz poszerzyć swoją wiedzę?

Sprawdź kurs Masterclass Clean Architecture. Kurs omawia wykorzystanie dobrych praktyk związanych z architekturą, jakością oprogramowania oraz jego utrzymywaniem. Podczas kursu zapoznasz się z teorią, najczęstszymi problemami oraz praktycznym zastosowaniem wzorców/praktyk/technik takich jak architektura hexagonalna, CQRS, test-driven development, domain-driven design i wiele innych. Kurs NIE MA na celu kompleksowego omówienia każdej z technik, a pokazanie ich praktycznego zastosowania w codziennym rozwoju aplikacji.

obraz12.webp

Przeczytaj także

Ikona kalendarza

27 wrzesień

Sages wdraża system Omega-PSIR oraz System Oceny Pracowniczej w SGH

Wdrożenie Omega-PSIR i Systemu Oceny Pracowniczej w SGH. Sprawdź, jak nasze rozwiązania wspierają zarządzanie uczelnią i potencjałem ...

Ikona kalendarza

12 wrzesień

Playwright vs Cypress vs Selenium - czy warto postawić na nowe?

Playwright, Selenium czy Cypress? Odkryj kluczowe różnice i zalety każdego z tych narzędzi do automatyzacji testów aplikacji internet...

Ikona kalendarza

22 sierpień

Nowa era zarządzania wiedzą: Omega-PSIR w Akademii Leona Koźmińskiego

Akademia Leona Koźmińskiego w Warszawie, jedna z wiodących uczelni wyższych w Polsce, od kwietnia 2023 roku korzysta z wdrożonego prz...