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:
- Testy jednostkowe - Zapewnienie możliwości testowania poszczególnych komponentów w izolacji.
- 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:
- Dobrze zdefiniowane APIs- APIs zostały zaprojektowane tak, aby były proste, dobrze udokumentowane i dostępne na wczesnym etapie rozwoju.
- 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:
- 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.
- 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ść!