Projektowanie pod kątem testowalności: Przełamywanie kolejnej bariery
4 min read

W naszej serii DevOps for the Win dokumentujemy naszą podróż w kierunku transformacji DevOps. W rozdziale 1 przedstawiliśmy Trójkąt DevOps: Narzędzia, architektura i ludzie w rozwoju produktugdzie omówiliśmy podstawowe elementy, które napędzają sukces DevOps. W rozdziale 2 zbadaliśmy Pierwsze kroki w naszej transformacji DevOpsdzieląc się tym, jak położyliśmy podwaliny pod znaczące zmiany.

W rozdziale 3 zajmujemy się kolejnym poważnym wyzwaniem, z jakim się spotkaliśmy: projektowaniem pod kątem testowalności. Po usprawnieniu struktury naszego zespołu, udoskonaleniu definicji „zakończonego zadania” 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ę na etapie testowania, us szybszych cykli wydawania nowych wersji. W tym rozdziale opisujemy, w jaki sposób pokonaliśmy to wyzwanie i poprawiliśmy naszą zdolność do efektywnego dostarczania wysokiej jakości oprogramowania.

Wąskie gardło testowania

Głównym powodem tego opóźnienia było to, że testowanie odbywało się głównie ręcznie, koncentrując się na testach interfejsu użytkownika i przepływach pracy użytkowników. Ponieważ interfejs użytkownika był zwykle jednym z ostatnich elementów do ukończenia, automatyzacja była niewielka lub żadna, a testowanie ręczne stało się jedynym realnym podejściem. Zautomatyzowane przypadki testowe interfejsu użytkownika (np. testy Selenium) były zazwyczaj pisane po zakończeniu testów ręcznych, głównie do celów regresji.

Poleganie na testach manualnych skutkowało

  • Znaczne opóźnienia w wydaniach oprogramowania.
  • Konieczność wielokrotnego wdrażania w celu umożliwienia równoległego testowania przez więcej niż jednego członka QA .

Podkreśliło to znaczenie uczynienia kodu bardziej testowalnym w celu optymalizacji przepływu wartości. Osiągnięcie tego celu okazało się jednak trudniejsze, niż się spodziewaliśmy.

Ustalanie priorytetów 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 testów opartych na interfejsie użytkownika i zależności od bazy danych.

Testowanie API: Przesunięcie w lewo dla szybszej informacji zwrotnej

Przedefiniowaliśmy naszą strategię testowania API, wprowadzając te kluczowe ulepszenia:

  1. Dobrze zdefiniowane APIs APIs zaprojektowane tak, aby były proste, dobrze udokumentowane i dostępne na wczesnym etapie rozwoju.
  2. Unikanie bazy danych jako punktu integracji - poleganie na bazach danych w celu integracji stworzyło zależności, które spowolniły testowanie. Zamiast tego przeszliśmy na integrację opartą na API przy użyciu REST przez HTTP i GraphQL. Zminimalizowało to czas konfiguracji bazy danych i usprawniło automatyzację testów.

Zmiana ta znacznie 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 testów jednostkowych doprowadziły do szybkiego wzrostu pokrycia testami, ale szybko zidentyfikowaliśmy problemy:

  • Niektóre testy były powierzchowne, napisane tylko po to, aby spełnić cele pokrycia kodu.
  • Brakowało im znaczącej walidacji funkcjonalności, przypadków brzegowych i scenariuszy awarii.

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

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

Wyzwania związane z pisaniem testowalnego kodu

Największą przeszkodą w skutecznym testowaniu jednostkowym był brak testowalności w samej bazie kodu. Kluczowe kwestie obejmowały:

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

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

  1. Dostarczone wytyczne: Podzieliliśmy się najlepszymi praktykami w zakresie:
    • Wybieranie funkcji do testów jednostkowych.
    • Refaktoryzacja kodu w celu poprawy testowalności.
    • Projektowanie i używanie makiet w celu ułatwienia testowania.
  2. Koncentracja na stopniowych ulepszeniach: Deweloperzy byli zachęcani do wprowadzania małych, znaczących zmian w celu poprawy testowalności w czasie.

Powolny, ale stały postęp

Pomimo tych wysiłków postęp był powolny, zwłaszcza w przypadku starszych produktów. Istniejąca architektura ograniczała liczbę testów jednostkowych, które mogliśmy napisać. Działania te miały jednak na celu nie tylko ulepszenie obecnej bazy kodu - były one inwestycją w przyszłość:

  • Poprawa umiejętności programistów w zakresie projektowania testowalnego kodu zapewniła, że przyszłe projekty nie będą miały tych samych problemów.
  • Przyrostowe ulepszenia zapobiegły zakłóceniom, jednocześnie stale zwiększając pokrycie automatyzacji testów.
  • Zmiana sposobu myślenia pomogła zespołom postrzegać testowalność nie jako obciążenie, ale jako konieczność dla trwałego sukcesu DevOps.

Dla zespołów i organizacji rozpoczynających transformację DevOps, testowalność jest krytycznym obszarem zainteresowania. Pomaga złagodzić wąskie gardła w fazie rozwoju. Ważne jest jednak, aby zarządzać oczekiwaniami - natychmiastowe wyniki 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 jest dużym wyzwaniem.

Lekcje od pragmatycznego programisty

W przypadku scenariuszy, w których rozpoczęcie od nowej bazy kodu nie jest wykonalne, The Pragmatic Programmer: Your Journey to Mastery, autorstwa Andy'ego Hunta i Davida Thomasa oferuje praktyczne wskazówki:

  • Ukierunkowanie na zmiany przyrostowe: Skoncentruj się na refaktoryzacji małych, łatwych do zarządzania części bazy kodu.
  • Rozdzielanie komponentów: Zmniejsz zależności, aby ułatwić testowanie poszczególnych jednostek.
  • Rozbij funkcje monolityczne: Podziel duże funkcje na mniejsze, bardziej skoncentrowane jednostki.
  • Zastosuj projektowanie modułowe: Uczyń kod bardziej testowalnym i łatwym w utrzymaniu poprzez poprawę modułowości.

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

Patrząc w przyszłość

Nasza droga do poprawy testowalności nauczyła us lekcji na temat roli projektowania oprogramowania w umożliwianiu płynnych transformacji DevOps. Skupiają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łe sukcesy. Chociaż natychmiastowe rezultaty mogą być powolne, długoterminowe korzyści w zakresie jakości, wydajności i rozwoju zespołu sprawiają, że inwestycja ta jest opłacalna.

W miarę jak kontynuujemy naszą podróż DevOps, mierzenie sukcesu staje się kolejnym kluczowym wyzwaniem. W rozdziale 4 zbadamy, w jaki sposób ustanowiliśmy wskaźniki KPI i pulpity nawigacyjne, aby śledzić nasze postępy i identyfikować dalsze obszary wymagające poprawy.

Bądźcie na bieżąco, bo zaraz opowiemy, jak podejmowanie decyzji oparte na danych pomogło us procesy DevOps i zoptymalizować wydajność!