Projektowanie z myślą o testowalności: Przełamywanie kolejnej bariery
4 minuty czytania

W naszej trwającej serii DevOps dla Zwycięstwa dokumentujemy naszą podróż transformacji DevOps. W Rozdziale 1 przedstawiliśmy Trójkąt DevOps: Narzędzia, Architektura i Ludzie w Rozwoju Produktu, gdzie omówiliśmy podstawowe elementy, które napędzają sukces DevOps. W Rozdziale 2 zbadaliśmy Pierwsze Kroki w Naszej Transformacji DevOps, dzieląc się tym, jak położyliśmy podwaliny pod znaczące zmiany.

Teraz, w Rozdziale 3, zajmujemy się kolejnym dużym wyzwaniem, z którym się spotkaliśmy: Projektowaniem pod kątem testowalności. Po usprawnieniu struktury zespołu, doprecyzowaniu naszej Definicji Ukończenia i wdrożeniu automatycznej analizy kodu, zdaliśmy sobie sprawę, że głównym wąskim gardłem w naszym procesie stało się projektowanie oprogramowania – a konkretnie jego testowalność. Praca piętrzyła się w fazie testowania, uniemożliwiając nam osiągnięcie szybszych cykli wydawniczych. Ten rozdział szczegółowo opisuje, jak pokonaliśmy to wyzwanie i poprawiliśmy naszą zdolność do efektywnego dostarczania wysokiej jakości oprogramowania.

Wąskie gardło w testowaniu

Głównym powodem tego opóźnienia było to, że testowanie było głównie manualne, skupiające się na testowaniu interfejsu użytkownika i przepływach pracy użytkownika. Ponieważ interfejs użytkownika był zazwyczaj jednym z ostatnich elementów do ukończenia, automatyzacja była niewielka lub żadna, a testowanie manualne stało się jedynym możliwym podejściem. Zautomatyzowane przypadki testowe interfejsu użytkownika (np. testy Selenium) były zazwyczaj pisane po zakończeniu testów manualnych, głównie w celach regresji.

To poleganie na testach manualnych doprowadziło do –

  • Znaczące opóźnienia w wydaniach oprogramowania.
  • Potrzeba wielu wdrożeń, aby umożliwić równoległe testowanie przez więcej niż jednego członka zespołu QA.

To podkreśliło znaczenie uczynienia kodu bardziej testowalnym w celu optymalizacji przepływu wartości. Jednak osiągnięcie tego okazało się trudniejsze, niż przewidywaliśmy.

Priorytetyzacja testów API i testów jednostkowych

Aby sprostać temu wyzwaniu, skupiliśmy się na dwóch głównych obszarach:

  1. Testy jednostkowe - Zapewnienie możliwości testowania poszczególnych komponentów w izolacji.
  2. Testy API - Zmniejszenie zależności od testowania opartego na interfejsie użytkownika i zależności od baz danych.

Testowanie API: Przeniesienie testowania na wcześniejsze etapy dla szybszej informacji zwrotnej

Zredefiniowaliśmy naszą strategię testowania API, wprowadzając następujące kluczowe usprawnienia:

  1. Dobrze zdefiniowane APIs- APIs zostały zaprojektowane tak, aby były proste, dobrze udokumentowane i dostępne na wczesnym etapie rozwoju.
  2. Unikanie bazy danych jako punktu integracji - Opieranie się na bazach danych w celu integracji tworzyło zależności, które spowalniały testowanie. Zamiast tego przeszliśmy na integrację opartą na API, wykorzystując REST przez HTTP i GraphQL. To zminimalizowało czas konfiguracji bazy danych i poprawiło automatyzację testów.

Ta zmiana znacząco zmniejszyła opóźnienia, umożliwiając szybszą automatyzację i testowanie na wczesnym etapie, pokazując, że skupienie się na projektach opartych na API poprawiło zarówno testowalność, jak i wydajność.

Testowanie jednostkowe: Zmiana sposobu myślenia

Początkowo nasze wysiłki w zakresie testowania jednostkowego doprowadziły do szybkiego wzrostu pokrycia testami, ale szybko zidentyfikowaliśmy problemy:

  • Niektóre testy były powierzchowne, napisane tylko w celu spełnienia celów pokrycia kodu.
  • Brakowało im istotnej walidacji funkcjonalności, przypadków brzegowych i scenariuszy awarii.

Aby temu przeciwdziałać, podkreśliliśmy:

  • Szkolenie deweloperów w zakresie pisania wartościowych testów.
  • Refaktoryzacja kodu w celu poprawy testowalności.

Wyzwania w pisaniu testowalnego kodu

Największą przeszkodą w skutecznym testowaniu jednostkowym był brak testowalności w samym kodzie. Kluczowe problemy obejmowały:

  • Duże, monolityczne funkcje.
  • Ściśle powiązane komponenty.
  • Słabe rozdzielenie odpowiedzialności.
  • Niewystarczająca abstrakcja.

Te problemy utrudniały skuteczne izolowanie i testowanie poszczególnych jednostek. Aby temu zaradzić, my:

  1. Udzieliliśmy wytycznych.: Podzieliliśmy się najlepszymi praktykami w zakresie:
    • Wybieranie funkcji do testowania jednostkowego.
    • Refaktoryzacja kodu w celu poprawy testowalności.
    • Projektowanie i używanie makiet w celu ułatwienia testowania.
  2. Skupienie na stopniowych ulepszeniach: Zachęcano deweloperów do wprowadzania małych, znaczących zmian, aby z czasem poprawić testowalność.

Powolny, ale stały postęp

Mimo tych wysiłków, postęp był powolny, szczególnie w przypadku starszych produktów. Istniejąca architektura ograniczała liczbę testów jednostkowych, które mogliśmy napisać. Jednakże, działania te nie miały jedynie na celu ulepszenia obecnej bazy kodu – były inwestycją w przyszłość:

  • Poprawa umiejętności deweloperów w projektowaniu kodu podlegającego testowaniu zapewniła, że przyszłe projekty nie będą borykać się z tymi samymi problemami.
  • Stopniowe ulepszenia zapobiegły zakłóceniom, jednocześnie stale zwiększając pokrycie automatyzacją testów.
  • Zmiana sposobu myślenia pomogła zespołom postrzegać testowalność nie jako obciążenie, lecz jako konieczność dla trwałego sukcesu DevOps.

Dla zespołów i organizacji rozpoczynających transformację DevOps, testowalność jest kluczowym obszarem zainteresowania. Pomaga ona łagodzić wąskie gardła w fazie rozwoju. Ważne jest jednak, aby zarządzać oczekiwaniami – natychmiastowe rezultaty mogą nie być możliwe, zwłaszcza w przypadku dużych, starszych baz kodu. Refaktoryzacja takiego kodu bez wystarczających testów jednostkowych i regresyjnych stanowi duże wyzwanie.

Lekcje z Pragmatycznego Programisty

W scenariuszach, gdy rozpoczęcie pracy z nową bazą kodu nie jest możliwe, The Pragmatic Programmer: Your Journey to Mastery, autorstwa Andy'ego Hunta i Davida Thomasa, oferuje praktyczne wskazówki:

  • Celowanie w zmiany przyrostowe: Skupienie się na refaktoryzacji małych, łatwych do zarządzania części kodu.
  • Rozdzielanie komponentów: Zmniejszenie zależności w celu ułatwienia testowania poszczególnych jednostek.
  • Podziel funkcje monolityczne: Podziel duże funkcje na mniejsze, bardziej ukierunkowane jednostki.
  • Wprowadź projekt modułowy: Spraw, aby kod był bardziej testowalny i łatwiejszy w utrzymaniu poprzez poprawę modułowości.

Przesłanie jest jasne: po usunięciu początkowych wąskich gardeł, testowalność powinna stać się kluczowym obszarem zainteresowania. Ułatwi to ograniczenia w fazie testowania i umożliwi szybsze dostarczanie wysokiej jakości oprogramowania.

Perspektywy

Nasza podróż w kierunku poprawy testowalności nauczyła nas cennych lekcji na temat roli projektowania oprogramowania w umożliwianiu płynnych transformacji DevOps. Koncentrując się na testowalności, wyeliminowaliśmy wąskie gardła, poprawiliśmy umiejętności programistów i położyliśmy podwaliny pod przyszły sukces. Chociaż natychmiastowe wyniki mogą być powolne, długoterminowe korzyści w zakresie jakości, wydajności i rozwoju zespołu sprawiają, że ta inwestycja jest warta zachodu.

Kontynuując naszą podróż z DevOps, mierzenie sukcesu staje się kolejnym kluczowym wyzwaniem. W Rozdziale 4 zbadamy, jak ustanowiliśmy KPI i pulpity nawigacyjne, aby śledzić nasze postępy i identyfikować dalsze obszary do poprawy.

Bądź na bieżąco, gdy zagłębimy się w to, jak podejmowanie decyzji opartych na danych pomogło nam udoskonalić nasze procesy DevOps i zoptymalizować wydajność!

Subskrybuj blog Freyr

Polityka prywatności