Konferencje

100% code coverage, testy mutacyjne i fuzz testing: Rozmowa z Mateuszem Wojczalem, CTO Escola S.A.

Metryka 100% Code Coverage często jest uważana za wyznacznik doskonałej jakości testów oprogramowania, ale czy to naprawdę takie proste? Chcieliśmy dowiedzieć się więcej o tym temacie oraz o roli testów mutacyjnych i fuzz testingu w zapewnianiu jakości oprogramowania. Rozmawialiśmy więc z Mateuszem Wojczalem, szefem technologicznym spółki Escola S.A. Okazuje się, że te zaawansowane techniki testowe mają wiele do zaoferowania. Wymagają jednak mądrego podejścia i integracji w procesie rozwoju aplikacji. Sprawdź dlaczego 100% Code Coverage to nie wszystko!

Mateusz Wojczal, dyrektor ds. technologii w Escola S.A. będzie jednym z prelegentów w Focus Hotel Premium w Gdańsku. Pod koniec miesiąca, czyli 26 września 2023 r. ekspert przedstawi temat code coverage, testów mutacyjnych oraz fuzz testingu. Postanowiliśmy zapytać go o co w tym wszystkim chodzi.

Mateusz Wojczal

Escola / Wellms

Full-stack web developer/DevOps od 2005 roku. Od początku związany z aplikacjami mobilnymi i webowymi. Zna mało i wielkoformatowe oraz multimedialne i interaktywne ekspozycje. Zaczynał w ActionScript. Kodował w PHP, JavaScript, Node.js i innych technologiach. Dzisiaj działa w TypeScript. W 2011 założył Qunabu, a 8 lat później został CTO Escola. Twórca pierwszego headlessowego open source’owego LMS – Wellms. Autor podcastów Escola DevTalk i Filozofii programowania. Trener warsztatów, Juror hackatonów i wielokrony prelegent konferencji technologicznych. Organizuje Gdańsk TypeScript Meetup, Zafascynowany funkcyjnym programowaniem, testowaniem automatycznym, historią programowania i DevOpsem. Ewangelista Domain Driven Design, Facilitator i modelarz Event Stormingu.

Czego nie powie Ci metryka 100% Code Coverage

Metryka całkowitego pokrycia kodem, znana również jako 100% Code Coverage, stała się popularnym wskaźnikiem jakości testów oprogramowania. Jednakże, osiągnięcie pokrycia całego kodu nie oznacza, że  zostały uwzględnione wszystkie możliwe przypadki testowe. Mateusz Wojczal podkreśla, że to jedno z najczęstszych błędnych przekonań. – Jest to jeden z największych błędów w ocenie tej metryki. Owszem nasz kod może być przetestowany i posiadać taką metrykę, czyli stan kiedy wszystkie linie kodu są objęte przynajmniej jednym testem. Ale wcale nie znaczy to to, że mamy kod dobrej jakości. Powiem więcej, zazwyczaj nie mamy – mówi Wojczal.

Zagrożenia związane z całościowym pokryciem kodu

Skupienie się wyłącznie na osiągnięciu 100% Code Coverage może prowadzić do nadmiernego skomplikowania testów lub tworzenia testów, które nie sprawdzają istotnych aspektów kodu. Wojczal podkreśla, że ważne jest używanie tej metryki jako jednego z wielu wskaźników jakości testów. Zgodnie z Prawem Goodharda, głównym zagrożeniem jest poświęcenie zbyt wielu zasobów na osiągnięcie tej metryki, zamiast na rzeczy, które mogą być równie lub nawet bardziej istotne z punktu widzenia bezpieczeństwa produktu.

To analogiczna sytuacja do nacisku wszystkich sił na zapewnie stu procent googlowego wskaźnika Pagespeed Insight, co wcale nie musi mieć wielkiego przełożenie na resztę wskaźników biznesowych strony. – Są momenty, w których osiągnięcie tej metryki jest nieopłacalne. Czasem wiąże się z bardzo dużym kosztem czasowym lub bardzo dużym kosztem zasobów. A niekoniecznie przyniesie to cokolwiek innego, niż osiągnięcie takiej metryki. W tym sensie, że to wcale nie jest obietnica poprawnie działającego dobrego kodu – podkreśla programista.

Mateusz Wojczal

Testy mutacyjne. Czym są i dlaczego są ważne?

Testy mutacyjne to rodzaj testów oprogramowania, które polegają na celowym wprowadzaniu błędów (mutacji) do kodu programu w celu oceny jakości testów jednostkowych. Testy te pozwalają sprawdzić, czy nasze testy są wystarczająco silne, aby wykryć zmiany w kodzie. Wojczal podkreśla, że są one użyteczne w ocenie nie tylko kodu, ale i testów.  – Testy mutacyjne ogólnie rzecz biorąc polegają na ocenie jakości innych testów. To testy, które celowo wprowadzają złośliwe zmiany w kodzie i następnie sprawdzają, czy istniejące testy reagują na te zmiany. Jeśli testy nie wykrywają tych zmian, możemy wyciągnąć wniosek, że albo nasz kod jest wadliwy, albo istniejące testy są niewystarczające. W ten sposób testy mutacyjne pozwalają na ocenę jakości zarówno kodu, jak i testów – powiedział CTO.

Przykładem takiej sytuacji może być zmiana operatora „równa się” na operator „zaprzeczenie” i sprawdzenie, czy istniejące testy nadal przechodzą, czy też aplikacja nadal działa poprawnie w określonym fragmencie kodu. Innym przypadkiem może być zamiana operatora „większe lub równe” na „większe”, a następnie sprawdzenie, czy kod wciąż zachowuje się zgodnie z naszymi oczekiwaniami, czyli czy testy nadal przechodzą pomyślnie. Jeśli testy te wciąż przechodzą, może to sugerować, że kod lub testy zostały napisane nieprawidłowo. To jest rodzaj sygnału ostrzegawczego, że coś może być nie tak i wymaga dodatkowej uwagi.

Korzyści i wyzwania związane z testami mutacyjnymi

Korzyści płynące z zastosowania testów mutacyjnych są znaczące. Słowami eksperta, główną zaletą jest automatyzacja procesu generowania testów dzięki istniejącym testom jednostkowym. Dzięki nim, podłączenie podstawowych testów staje się kwestią konfiguracji w procesie ciągłej integracji i dostarczenia (CI/CD). Oznacza to, że nie musimy pisać tych testów ręcznie, co znacznie przyspiesza proces wdrażania testów mutacyjnych.

Ale jednym z głównych problemów jest czas. Mateusz podał przykład, kiedy jednostkowy test wykonuje się w ciągu jednej sekundy, a trzeba przeprowadzić 100 takich testów, to ilość czasu potrzebna do przetestowania różnych wariantów mutantów urośnie wykładniczo. Każda zmiana w kodzie generuje nową serię testów mutacyjnych. Na przykład, zmiana znaku, liczby, wartości atrybutów czy widoczności atrybutów może generować wiele różnych mutacji. To oznacza, że ilość testów może wzrosnąć w wartościach liczonych do potęgi, co znacznie wydłuża czas trwania testów.

W związku z tym, efektywne zarządzanie czasem wykonywania testów mutacyjnych stanowi wyzwanie. Istnieją różne podejścia i metody, które pozwalają na zoptymalizowanie tego procesu. Dla projektów o większym rozmiarze i złożoności, może być konieczne zastosowanie rozwiązań, które pozwalają na równoległe wykonywanie testów mutacyjnych, aby skrócić czas ich przeprowadzania. Ważne jest także dokładne dostosowanie liczby mutacji do potrzeb projektu, aby nie przeciążać całego procesu.

A co z testowaniem opartym na przypadkowości?

Tak, mowa o Fuzz testingu, czyli technice testowania oprogramowania, która polega na wprowadzaniu losowych, zniekształconych lub nieprawidłowych danych wejściowych do programu w celu wykrycia błędów lub luk w zabezpieczeniach. Jest to szczególnie istotne w kontekście mobilnych aplikacji, gdzie użytkownicy mogą dostarczać różnorodne dane wejściowe. Fuzz testing pozwala na automatyczne generowanie dużej liczby testów, co może pomóc w wykryciu trudno dostrzegalnych błędów.

– Fuzz testing ma na celu eksplorowanie aplikacji w miejscach, gdzie oczekuje się określonego rodzaju danych wejściowych, zwanych inputem. W praktyce oznacza to wprowadzanie do aplikacji wartości, które są nieoczekiwane lub skrajne. Na przykład, jeśli mamy formularz obsługujący pliki graficzne, przeprowadzamy testy, próbując załadować plik o nietypowym formacie, na przykład jakiś “egzotyczny” plik. Przeprowadzamy również testy z różnymi rodzajami danych wejściowych, np. strumieniami. Ale także wartościami, które wykraczają poza standardowy zakres danych dla danej funkcji – mówi szef technologii w Escola.

Można powiedzieć, że fuzz testing to rodzaj testowania, który skupia się na ekstremalnych wartościach i nieoczekiwanych scenariuszach, znanych jako przypadki graniczne (corner case) lub brzegowe (edge case). Jego celem jest sprawdzenie, jak aplikacja reaguje na takie niekonwencjonalne dane wejściowe. Wojczal wyjaśnia tę koncepcję, podkreślając, że fuzz testing to narzędzie do eksplorowania granic możliwości aplikacji i identyfikowania potencjalnych błędów lub luk w jej funkcjonowaniu.

Jak fuzje pomagają w wykryciu krytycznych błędów w oprogramowaniu mobilnym?

Gdy pracujemy nad tworzeniem aplikacji mobilnych i projektujemy API, oczekujemy, że pewne rodzaje danych będą przekazywane jako argumenty funkcji lub parametry. W kontekście statycznego typowania, jeśli na przykład pewne zapytanie API oczekuje wartości liczbowej, logiczne jest, że powinno to działać zgodnie z założeniem, niezależnie od tego, czy przekazujemy bardzo niską liczbę czy bardzo wysoką. W rzeczywistości, gdybyśmy przekazali nieprawidłowy typ danych, na przykład ciąg znaków lub plik, API powinno wykryć to jako nieprawidłową wartość i odpowiednio zareagować.

Jednakże, czasami API nie działa zgodnie z oczekiwaniami i może reagować w nieprzewidywalny sposób na nieprawidłowe dane wejściowe. W takich sytuacjach testy fuzji pozwalają na eksplorowanie różnych scenariuszy, w których użytkownicy mogą niekontrolowanie wprowadzać nieprawidłowe dane do naszej aplikacji. Narzędzia do testów fuzji automatycznie generują te przypadki testowe i pozwalają nam zidentyfikować potencjalne problemy z zachowaniem aplikacji w obliczu niepoprawnych danych wejściowych. Jest to szczególnie ważne w kontekście projektowania aplikacji mobilnych, gdzie interakcje użytkownika mogą być bardziej zróżnicowane i trudniejsze do przewidzenia.

Stosowane razem stanowią test changer!

Warto zauważyć, że testy mutacyjne skupiają się na jakości testów jednostkowych, podczas gdy fuzz testing koncentruje się na nieoczekiwanych danych wejściowych i bezpieczeństwie. Mateusz Wojczal podkreśla, że warto stosować te techniki razem, szczególnie z punktu widzenia bezpieczeństwa. Testy mutacyjne pomagają ocenić jakość testów jednostkowych, podczas gdy fuzz testing może wykryć problemy związane z nieoczekiwanymi danymi wejściowymi.

Przyszłość testowania w kontekście mobilnych technologii

Technologie mobilne dynamicznie ewoluują, a wraz z nimi języki programowania i sposoby ich testowania. Obecnie możemy wyróżnić technologie natywne takie jak Swift i Kotlin, które posiadają własne frameworki do testów mutacyjnych i fuzji. Często określa się jako testowanie „white box”. Mają one dostęp do kodu źródłowego aplikacji i testują go bezpośrednio. Warto zaznaczyć, że język Swift i Kotlin to zupełnie różne technologie, co oznacza, że posiadają unikalne narzędzia i frameworki do testowania.

Oprócz technologii natywnych istnieją także rozwiązania hybrydowe, takie jak Flutter czy React Native. Dla tych platform również dostępne są odpowiednie frameworki do przeprowadzania testów mutacyjnych i fuzyjnych.  – Jeśli chcemy efektywnie przeprowadzić testy i zapewnić wysoką jakość ostatecznej aplikacji musimy dokonać wyboru kompatybilnych narzędzi do testowania. Ponieważ opierają się na analizie kodu źródłowego, narzędzia do nich muszą być zgodne z technologią, w jakiej dana aplikacja mobilna jest rozwijana. Istnieją różne frameworki i narzędzia dostosowane do specyfiki różnych języków i technologii mobilnych, takich jak Swift, Kotlin, Flutter czy React Native. Wybór odpowiednich narzędzi zależy od konkretnego projektu i technologii używanej do tworzenia aplikacji – kwituje ekspert.

Spotkaj się z Mateuszem na 4Developers!

100% Code Coverage, testy mutacyjne i fuzz testing to zaawansowane techniki testowania oprogramowania, które mogą znacząco poprawić jakość naszych aplikacji. Kluczem do ich skutecznego wykorzystania jest mądre podejście i integracja w procesie rozwoju. Mateusz Wojczal dowodzi, że warto spojrzeć na testowanie oprogramowania bardziej holistycznie i nie ograniczać się tylko do jednej metryki. Dzięki temu będziemy mogli dostarczyć bardziej niezawodne, ale przede wszystkim bezpieczniejsze aplikacje.

Jeśli interesuje Cię głębszy obraz, możesz spotkać się z Mateuszem w Gdańsku. Kompleksowe sprawdzanie kodu to jeden z głównych tematów na tegorocznej konferencji programistycznej 4developers.

Udostępnij
Zobacz także