Udostępnij za pośrednictwem


Przesunięcie testowania w lewo za pomocą testów jednostkowych

Testowanie pomaga zapewnić, że kod działa zgodnie z oczekiwaniami, ale czas i wysiłek kompilacji testów zajmuje trochę czasu od innych zadań, takich jak tworzenie funkcji. W przypadku tego kosztu ważne jest wyodrębnienie maksymalnej wartości z testowania. W tym artykule omówiono zasady testów w metodyce DevOps, koncentrując się na wartości testowania jednostkowego i strategii testowej typu 'shift-left'.

Dedykowani testerzy pisali większość testów, a wielu deweloperów nie nauczyło się pisać testów jednostkowych. Pisanie testów może wydawać się zbyt trudne lub jak zbyt wiele pracy. Może istnieć sceptycyzm co do skuteczności strategii testów jednostkowych, złe doświadczenia ze słabo napisanymi testami jednostkowymi lub obawa, że testy jednostkowe zastąpią testy funkcjonalne.

Grafika opisując argumenty dotyczące wdrażania testów jednostkowych.

Aby wdrożyć strategię testowania Metodyki DevOps, bądź pragmatyczny i skupić się na budowaniu tempa. Mimo że można nalegać na testy jednostkowe dla nowego kodu lub istniejącego kodu, który można czysto zrefaktoryzować, w przypadku starszej bazy kodu może być sensowne dopuszczenie pewnych zależności. Jeśli znaczna część kodu produktu korzysta z języka SQL, umożliwienie testom jednostkowym zależności od dostawcy zasobów SQL zamiast stosowania makietowania tej warstwy może być krótkoterminowym podejściem do osiągnięcia postępu.

Gdy organizacje DevOps dojrzewają, staje się łatwiejsze dla kierownictwa, aby ulepszać procesy. Chociaż może istnieć pewien opór wobec zmian, organizacje Agile cenią zmiany, które wyraźnie przynoszą korzyści. Łatwo można sprzedać wizję szybszych testów z mniejszą liczbą błędów, ponieważ oznacza to więcej czasu na inwestowanie w generowanie nowej wartości poprzez rozwój funkcji.

Taksonomia testowa metodyki DevOps

Definiowanie taksonomii testowej jest ważnym aspektem procesu testowania Metodyki DevOps. Test metodyki DevOps klasyfikuje poszczególne testy według ich zależności i czas ich uruchomienia. Deweloperzy powinni zrozumieć odpowiednie typy testów do użycia w różnych scenariuszach i które wymagają testów różnych części procesu. Większość organizacji kategoryzuje testy na czterech poziomach:

  • Testy L0 i L1 to testy jednostkowe lub testy, które zależą od kodu w zestawie testowym i nic innego. L0 to szeroka klasa szybkich testów jednostkowych wykonywanych w pamięci.
  • L2 to testy funkcjonalne , które mogą wymagać zestawu oraz innych zależności, takich jak SQL lub system plików.
  • Testy funkcjonalne L3 są przeprowadzane na wdrożeniach usług, które można testować. Ta kategoria testowa wymaga wdrożenia usługi, ale może używać stubów dla kluczowych zależności usługi.
  • Testy L4 to ograniczona klasa testów integracji, które są uruchamiane względem środowiska produkcyjnego. Testy L4 wymagają pełnego wdrożenia produktu.

Chociaż byłoby idealne, aby wszystkie testy były uruchomione przez cały czas, nie jest to wykonalne. Zespoły mogą wybrać miejsce w procesie DevOps, aby uruchomić każdy test, i użyć strategii shift-left lub shift-right , aby przenieść różne typy testów wcześniej lub później w procesie.

Na przykład, oczekiwania mogą być takie, że deweloperzy zawsze uruchamiają testy L2 przed zatwierdzeniem, pull request automatycznie zostaje odrzucony, jeśli test L3 zakończy się niepowodzeniem, a wdrożenie może zostać zablokowane, jeżeli testy L4 nie powiodą się. Określone reguły mogą się różnić w zależności od organizacji, ale wymuszanie oczekiwań dla wszystkich zespołów w organizacji przenosi wszystkich w kierunku tych samych celów związanych z wizją jakości.

Wytyczne dotyczące testów jednostkowych

Ustaw ścisłe wytyczne dotyczące testów jednostkowych L0 i L1. Testy te muszą być bardzo szybkie i niezawodne. Na przykład średni czas wykonywania testu L0 w zespole powinien być krótszy niż 60 milisekund. Średni czas wykonywania testu L1 w kompilacji powinien wynosić mniej niż 400 milisekund. Żaden test na tym poziomie nie powinien przekraczać 2 sekund.

Jeden zespół firmy Microsoft uruchamia ponad 60 000 testów jednostkowych równolegle w mniej niż sześć minut. Ich celem jest skrócenie tego czasu do mniej niż minuty. Zespół monitoruje czas wykonywania testów jednostkowych za pomocą takich narzędzi, jak poniższy wykres, i zgłasza usterki dotyczące testów, które przekraczają dozwolony czas.

Wykres przedstawiający ciągły nacisk na czas wykonywania testu.

Wskazówki dotyczące testów funkcjonalnych

Testy funkcjonalne muszą być niezależne. Kluczową koncepcją testów L2 jest izolacja. Prawidłowo izolowane testy mogą działać niezawodnie w dowolnej sekwencji, ponieważ mają pełną kontrolę nad środowiskiem, w jakim działają. Stan musi być znany na początku testu. Jeśli jeden test utworzył dane i opuścił go w bazie danych, może uszkodzić przebieg innego testu, który opiera się na innym stanie bazy danych.

Starsze testy, które wymagają tożsamości użytkownika, mogły wykorzystywać zewnętrznych dostawców uwierzytelniania w celu pozyskania tożsamości. Ta praktyka wprowadza kilka wyzwań. Zależność zewnętrzna może być zawodna lub chwilowo niedostępna, co może powodować przerwanie testu. Ta praktyka narusza również zasadę izolacji testowej, ponieważ test może zmienić stan tożsamości, na przykład uprawnienie, co powoduje nieoczekiwany stan domyślny dla innych testów. Rozważ zapobieganie tym problemom, inwestując w obsługę tożsamości w ramach struktury testowej.

Zasady testowania metodyki DevOps

Aby ułatwić przejście portfolio testowego do nowoczesnych procesów DevOps, wyraź wizję jakości. Zespoły powinny przestrzegać następujących zasad testowania podczas definiowania i implementowania strategii testowania Metodyki DevOps.

Diagram przedstawiający przykładową wizję jakości i listę zasad testowania.

Przesunięcie w lewo, aby przetestować wcześniej

Uruchamianie testów może zająć dużo czasu. W miarę skalowania projektów znacznie rośnie liczba testów i typy. Kiedy zestawy testów rosną i trwają godziny lub dni, mogą być odłożone na później i uruchamiają się w ostatniej chwili. Korzyści z testowania jakości kodu stają się widoczne dopiero długo po zatwierdzeniu kodu.

Długotrwałe testy mogą również powodować błędy, które są czasochłonne do zbadania. Zespoły mogą budować tolerancję na niepowodzenia, szczególnie na wczesnym etapie sprintach. Ta tolerancja podważa wartość testowania jako wgląd w jakość bazy kodu. Testy długotrwałe, wykonywane w ostatniej chwili, wprowadzają również nieprzewidywalność do oczekiwań związanych z końcem sprintu, ponieważ nieznany poziom długu technicznego musi zostać spłacony, aby kod nadawał się do wydania.

Celem przesunięcia testowania w lewo jest przeniesienie jakości na wcześniejszym etapie poprzez wykonywanie zadań testowych wcześniej w procesie. Dzięki połączeniu ulepszeń testów i procesów przesunięcie w lewo zmniejsza zarówno czas potrzebny na uruchomienie testów, jak i wpływ awarii w dalszej części cyklu. Przesunięcie w lewo gwarantuje, że większość testów zostanie ukończona przed scaleniem zmiany z gałęzią główną.

Diagram przedstawiający przejście do testowania w lewo.

Oprócz przesunięcia pewnych odpowiedzialności związanych z testowaniem w lewo w celu poprawy jakości kodu, zespoły mogą przesuwać inne aspekty testowe w prawo lub na późniejszy etap w cyklu DevOps, aby ulepszyć końcowy produkt. Aby uzyskać więcej informacji, zobacz Shift right to test in production (Przesunięcie w prawo do testowania w środowisku produkcyjnym).

Pisanie testów na najniższym możliwym poziomie

Napisz więcej testów jednostkowych. Faworyzowanie testów przy użyciu najmniejszych zależności zewnętrznych i skupianie się na uruchamianiu większości testów w ramach kompilacji. Rozważ równoległy system budowania, który może uruchamiać testy jednostkowe dla zestawu natychmiast po dostarczeniu zestawu i powiązanych testów. Nie jest możliwe przetestowanie każdego aspektu usługi na tym poziomie, ale zasadą jest użycie lżejszych testów jednostkowych, jeśli mogą one uzyskać te same wyniki co cięższe testy funkcjonalne.

Dążenie do niezawodności testów

Zawodny test jest kosztowny w utrzymaniu w ramach organizacji. Taki test działa bezpośrednio przeciwko celowi wydajności inżynieryjnej, utrudniając wprowadzanie zmian z ufnością. Deweloperzy powinni mieć możliwość wprowadzania zmian w dowolnym miejscu i szybko zyskać pewność, że nic nie zostało przerwane. Utrzymaj wysoki poziom niezawodności. Nie zaleca się używania testów interfejsu użytkownika, ponieważ są często zawodne.

Pisanie testów funkcjonalnych, które mogą być uruchamiane w dowolnym miejscu

Testy mogą używać wyspecjalizowanych punktów integracji zaprojektowanych specjalnie w celu umożliwienia testowania. Jednym z powodów tej praktyki jest brak możliwości testowania w samym produkcie. Niestety testy takie często zależą od wewnętrznej wiedzy i używają szczegółów implementacji, które nie mają znaczenia z perspektywy testu funkcjonalnego. Te testy są ograniczone do środowisk, które mają tajne informacje i konfigurację niezbędną do wykonywania testów, co zwykle wyklucza środowiska produkcyjne. Testy funkcjonalne powinny używać tylko publicznego interfejsu API produktu.

Projektowanie produktów pod kątem testowania

Organizacje w procesie dojrzewania metodyki DevOps mają pełny wgląd w to, co to znaczy dostarczać wysokiej jakości produkt w tempie chmurowym. Zmiana równowagi zdecydowanie na korzyść testowania jednostkowego nad testowaniem funkcjonalnym wymaga od zespołów dokonania wyborów projektowych i implementacji, które obsługują testowanie. Istnieją różne pomysły na to, co stanowi dobrze zaprojektowany i dobrze zaimplementowany kod do testowania, tak jak istnieją różne style kodowania. Zasada polega na tym, że projektowanie pod kątem możliwości testowania musi stać się główną częścią dyskusji na temat projektowania i jakości kodu.

Traktuj kod testowy jako kod produktu

Jawne stwierdzenie, że kod testowy jest kodem produkcyjnym, jasno pokazuje, że jakość kodu testowego jest równie ważna jak jakość kodu produkcyjnego. Zespoły powinny traktować kod testowy w taki sam sposób, w jaki traktują kod produktu, i stosować ten sam poziom opieki do projektowania i implementacji testów i struktur testowych. Ten wysiłek jest podobny do zarządzania konfiguracją i infrastrukturą jako kodem. Aby był kompletny, przegląd kodu powinien oceniać kod testowy według tego samego wysokiego standardu co kod produktu.

Korzystanie z udostępnionej infrastruktury testów

Obniż poprzeczkę używania infrastruktury testowej do generowania zaufanych sygnałów jakości. Wyświetlanie testów jako usługi udostępnionej dla całego zespołu. Zapisz kod testu jednostkowego wraz z kodem produktu i skompiluj go przy użyciu produktu. Testy uruchamiane w ramach procesu kompilacji muszą być również uruchamiane w ramach narzędzi programistycznych, takich jak Azure DevOps. Jeśli testy mogą być uruchamiane w każdym środowisku od lokalnego programowania za pośrednictwem środowiska produkcyjnego, mają taką samą niezawodność jak kod produktu.

Tworzenie właścicieli kodu odpowiedzialnych za testowanie

Kod testowy powinien znajdować się obok kodu produktu w repozytorium. Aby kod był testowany na granicy składnika, wypychaj odpowiedzialność za testowanie do osoby piszącej kod składnika. Nie polegaj na innych użytkownikach, aby przetestować składnik.

Studium przypadku: Przesunięcie testów na wcześniejszy etap za pomocą testów jednostkowych

Zespół firmy Microsoft zdecydował się zastąpić starsze pakiety testów nowoczesnymi testami jednostkowymi DevOps oraz procesem shift-left. Zespół śledził postępy w co-trzytygodniowych sprintach, co pokazano na poniższym wykresie. Wykres obejmuje sprinty 78-120, które reprezentują 42 sprinty w ciągu 126 tygodni, czyli około dwóch i pół roku wysiłku.

Zespół rozpoczął z 27 tysiącami starszych testów w sprincie 78 i osiągnął zero starszych testów w sprincie 120. Zestaw testów jednostkowych L0 i L1 zastąpił większość starych testów funkcjonalnych. Nowe testy L2 zastąpiły niektóre testy, a wiele starych testów zostało usuniętych.

Diagram przedstawiający przykładowe saldo portfela testów w czasie.

W ramach ścieżki programistycznej, która zajmuje ponad dwa lata, można wiele się nauczyć z samego przebiegu procesu. Ogólnie rzecz biorąc, wysiłek, aby całkowicie ponownie przeprowadzić system testowy w ciągu dwóch lat, był ogromną inwestycją. Nie każdy zespół funkcjonalny pracował w tym samym czasie. Wiele zespołów w całej organizacji zainwestowało czas w każdy sprint, a w niektórych sprintach była to większość działań zespołu. Chociaż trudno jest zmierzyć koszty zmiany, było to nienegocjacyjne wymaganie dotyczące jakości i wyników zespołu.

Wprowadzenie

Na początku zespół pozostawił stare testy funkcjonalne, nazywane testami TRA, w spokoju. Zespół chciał, aby deweloperzy przyjęli ideę pisania testów jednostkowych, szczególnie w przypadku nowych funkcjonalności. Skupiono się na maksymalnym ułatwieniu procesu tworzenia testów L0 i L1. Zespół musiał najpierw opracować tę możliwość i nabrać rozpędu.

Na poprzednim wykresie pokazano, że liczba testów jednostkowych zaczęła rosnąć wcześnie, ponieważ zespół dostrzegł korzyści z tworzenia testów jednostkowych. Testy jednostkowe były łatwiejsze do utrzymania, szybsze do uruchomienia i miały mniej błędów. Łatwo było uzyskać poparcie dla uruchamiania wszystkich testów jednostkowych w procesie pull request.

Zespół nie skupił się na pisaniu nowych testów L2 do sprintu 101. W międzyczasie liczba testów TRA spadła z 27 000 do 14 000 z Sprint 78 do Sprint 101. Nowe testy jednostkowe zastąpiły niektóre testy TRA, ale wiele z nich zostało po prostu usuniętych na podstawie analizy ich przydatności przez zespół.

Testy TRA skoczyły z 2100 do 3800 w sprincie 110, ponieważ więcej testów zostało odnalezionych w drzewie źródłowym i dodanych do grafu. Okazało się, że testy zawsze były przeprowadzane, ale nie były śledzone prawidłowo. To nie był kryzys, ale ważne było, aby być uczciwym i ponownie ocenić w razie potrzeby.

Przyspieszanie

Gdy zespół miał sygnał ciągłej integracji, który był niezwykle szybki i niezawodny, stał się zaufanym wskaźnikiem jakości produktu. Poniższy zrzut ekranu przedstawia proces pull requesta i pipeline ciągłej integracji oraz czas potrzebny na przejście przez różne fazy.

Diagram przedstawiający żądanie ściągnięcia i potok ciągłej integracji w działaniu.

Przejście od pull request do jego zintegrowania trwa około 30 minut, co obejmuje uruchomienie 60 000 testów jednostkowych. Od scalania kodu do buildu CI mija około 22 minuty. Pierwszy sygnał jakości z CI, SelfTest, pojawia się po około godzinie. Następnie większość produktu jest testowana z proponowaną zmianą. W ciągu dwóch godzin od scalania do środowiska selfhost cały produkt jest testowany, a zmiana jest gotowa do wdrożenia w środowisku produkcyjnym.

Korzystanie z metryk

Zespół śledzi kartę wyników, podobnie jak w poniższym przykładzie. Na wysokim poziomie karta wyników śledzi dwa typy metryk: Kondycja lub dług i szybkość.

Diagram przedstawiający kartę wyników metryk na potrzeby śledzenia wydajności testu.

W przypadku metryk kondycji strony na żywo zespół śledzi czas wykrywania, czas łagodzenia i liczbę elementów naprawczych, które zespół ma na stanie. Element naprawczy to praca, którą zespół identyfikuje w retrospektywie na żywo działania witryny, aby zapobiec powtarzaniu się podobnych incydentów. Karta wyników śledzi również, czy zespoły zamykają elementy naprawy w rozsądnym przedziale czasu.

W przypadku metryk kondycji inżynieryjnej zespół śledzi aktywne usterki dla każdego dewelopera. Jeśli zespół ma więcej niż pięć błędów na programistę, zespół musi priorytetowo traktować naprawę tych błędów przed rozwijaniem nowych funkcji. Zespół śledzi również starzejące się usterki w specjalnych kategoriach, takich jak zabezpieczenia.

Metryki prędkości inżynieryjnej mierzą szybkość w różnych częściach potoku ciągłej integracji i ciągłego dostarczania (CI/CD). Ogólnym celem jest zwiększenie prędkości ciągu DevOps: zaczynając od pomysłu, przez wprowadzenie kodu do produkcji, po odbieranie zwrotnych danych od klientów.

Dalsze kroki