Sztuczna inteligencja pozwoliła pominąć kilkanaście tysięcy test case’ów. Uczenie maszynowe w praktyce
Ericsson kojarzy się z telefonami komórkowymi sprzed ery smartfonów. W rzeczywistości jest to firma, która działa w sektorze komunikacyjnym od prawie 150 lat, dzisiaj budująca i obsługująca sieci mobilne. Z naszych produktów korzystają miliardy ludzi na całym świecie, choć nie zawsze wiedzą, kto je wyprodukował.
Klientami dla Ericssona są operatorzy sieci, a nie użytkownicy telefonów. Szukając sposobów na optymalizację pracy, korzystamy z innowacyjnych narzędzi takich jak sztuczna inteligencja. Poniżej opisuję to, jak wykorzystujemy uczenie maszynowe w praktyce.
Pracuję w obszarze GSM, czyli pierwszej spopularyzowanej technologii komórkowej, rozwijanej od początku lat 90. Sieć 2G nie tylko zapewnia zasięg słabo zurbanizowanym obszarom świata, ale także w centrach wielkich miast, gdzie nadaje równolegle do 4G oraz 5G, żeby zapewnić działanie bankomatom, terminalom płatniczym czy nawet połączeniom głosowym, gdy wyższe technologie nie są w stanie ich obsłużyć.
Praca z tak dojrzałym produktem ma swoją specyfikę. Zakupiło i wdrożyło go wielu klientów. Codziennie obsługuje on miliardy użytkowników, co oznacza, że stabilność działania jest krytycznie ważna. Nie możemy pozwolić sobie na błędy, które mogłyby skutkować odcięciem połowy kraju od dostępu do sieci, a w konsekwencji dostępu do telefonów alarmowych czy gotówki.
Rozwój produktu, a zwłaszcza dużych funkcjonalności, diametralnie wpływających na jego działanie, nie jest już tak dynamiczny, jak dekadę temu (choć może nie powinienem tego pisać, biorąc pod uwagę to, co akurat szykujemy do wypuszczenia). Większość ludzi została przerzucona do wyższych technologii, które są na innym etapie rozwoju. Stosunkowo niewielkie zasoby ludzkie (porównując z sytuacją choćby sprzed pięciu lat) oznaczają, że dostarczeń kodu jest mniej. Nie zmienia to jednak faktu, że każde z nich trzeba jak najdokładniej przetestować przed dostarczeniem kolejnego kawałka funkcjonalności.
Spis treści
Dostarczanie kodu
Zespoły zachęcane są do sukcesywnego dostarczania kodu. Nikomu nie jest jednak na rękę masowe dostarczanie, które nazywam big bang, w piątek po południu. Zbyt duża liczba „zakolejkowanego” kodu do testu na ograniczonej liczbie serwerów oznacza, że będą wykonywały się długo. A każde z nich potencjalnie zawiera krytyczny błąd, który może skończyć się niepowodzeniem dla kolejnych w sekwencji.
Dlatego kolejne dostawy kodu spływają sukcesywnie przez cały tydzień. Z reguły obejmują zmiany w pojedynczych blokach lub grupach bloków kodu, a nie całe gotowe funkcjonalności, dotykające wielu obszarów działania centrali. Podczas dostarczania, deweloper powinien oznaczyć, na jakie obszary miał wpływ. Czy zmiany dotyczyły zarządzania komórkami sieci, zestawianiem połączeń głosowych, ruchu pakietowego, a może ograniczały się jedynie do dokumentacji. To tylko kilka przykładów tzw. Impact Area.
Typowa kampania testowa
W tym miejscu warto wspomnieć, jak wygląda kampania testowa. Zespoły deweloperskie przed dostarczeniem wykonują testy funkcjonalne nowego kodu. Natomiast po dostarczeniu wykonują się automatyczne test suity, utrzymywane przez organizację Continous Integration (CI).
Testy są podzielone na kilka etapów, nazywanych promotion level. Pierwszy mówi o tym, czy kod w ogóle się kompiluje, czy unit testy przechodzą. Następnie sprawdzane są najbardziej podstawowe funkcjonalności w grupie testów nazywanej First Feedback. To jest dopiero pierwszy promotion level, który daje informację czy software nadaje się do ładowania na centrale w laboratorium.
Drugi promotion level (PL2) zaczyna się od sprawdzania, czy stare funkcjonalności (Legacy) działają na nowym kodzie. Następnie uruchamiane są: krótki Stability Test oraz pakiet negatywnych testów (Robustness), który sprawdza odporność oprogramowania na zdarzenia zaburzające działanie centrali.
Dopiero tak przetestowany software możemy uznać za stabilny. W teorii mógłby zostać posłany do klientów, jednak w praktyce jest to dopiero kwalifikacja do bardziej długotrwałych i skomplikowanych testów pogrupowanych w kolejne dwa promotion levele. Tam testowane jest zachowanie centrali pod dużym obciążeniem, długotrwałe stability, ze sprawdzeniem charakterystyk. Testowane jest także zachowanie bez symulatorów, gdzie prawdziwe jest wszystko od telefonu komórkowego po sieć szkieletową.
Optymalizacja i szukanie oszczędności
Cała weryfikacja po dostarczeniu to długotrwały i kosztowny proces, ale Ericssonowi zależy na jakości produktu i zadowoleniu klientów. Stąd przez długie lata firma poświęcała temu bardzo dużo uwagi.
Długo i dokładnie nie znaczy jednak, że nie można by lepiej. Jeszcze kilkanaście lat temu kompilacja z testami jednostkowymi i przygotowanie działającego środowiska testowego trwały dwa dni. Potem przeprowadzane ręcznie testy zajmowały kolejne dni. W tamtych czasach proces dostarczania wyglądał jednak inaczej, odbywał się w większych porcjach, co po części wynikało z pracy w metodyce waterfall. W tamtych czasach testem zajmowało się wiele wyspecjalizowanych zespołów z kilku krajów.
Późniejsze przejście na metodyki zwinne (Agile) sprawiło, że dostarczenia kodu stały się częstsze. Testerzy w większości trafili do zespołów deweloperskich. Testowanie musiało stać się szybsze i bardziej efektywne. Jak już pisałem wcześniej, dostarczanie kodu odbywa się na bieżąco, a nie w zbiorczych pakietach. W takim razie dostarczeń jest sporo. Dzięki podziałowi na promotion levele nie trzeba wykonywać całego zakresu testów na każdym dostarczeniu. Wystarczy osiągnąć PL1, żeby potwierdzić, iż kod nie zawiera krytycznych błędów w podstawowych funkcjonalnościach. A jeśli błąd się znajdzie, przynajmniej łatwo wskazać, co zawiniło i szybko dostarczyć poprawkę lub wycofać wadliwą zmianę.
Brak konieczności uruchamiania testów wyższego poziomu, trwających po kilka-kilkadziesiąt godzin, pozwala na znaczne oszczędności czasu i zasobów. Uruchamianie zakresu PL2 tylko na wybranych, stabilnych dostarczeniach okazało się optymalne pod względem poświęconego czasu i znajdowanych błędów. W weekendy, kiedy nowy kod nie spływa, środowiska testowe mogą poświęcić czas na dokładne sprawdzenie tego dostarczenia, które zostało wybrane spośród tych, które osiągnęły PL2 w danym tygodniu.
Używanie testów automatycznych optymalizowanych pod kątem czasu trwania, potrzebnych zasobów oraz możliwości zrównoleglania przyniosło wymierne korzyści. Nie tylko zmniejszyła się ilość sprzętu potrzebna do wykonania testów, to jeszcze może on pracować przez całą dobę. Mimo wszystko test suity z poziomu PL1 muszą być uruchamiane za każdym razem. Samych test case’ów Legacy i Regression powstało ponad 200 i choćby wykonywały się błyskawicznie, to ich liczba sprawiała, że na werdykt trzeba było czekać kilka godzin. Widać w tym pole do dalszej optymalizacji.
Uczenie maszynowe w praktyce
Na pomysł jak zoptymalizować czas wykonywania cyklu testów wpadł jeden z członków zespołu zajmującego się utrzymaniem testów automatycznych. Postanowił wykorzystać uczenie maszynowe, które dzięki zebranej historii wykonywanych test case’ów, przestanie wykonywać te z nich, które zostaną uznane za nieprzydatne do oceny jakości nowego kodu.
Na początek, wykorzystując używaną przez developerów Impact Area, należało zmapować test case’y na testowane obszary kodu i funkcjonalności. To pozwalało od razu wykluczać niektóre z test case’y podczas testowania zupełnie niezwiązanego z nimi dostarczenia. Zarówno pominięcie test case’a, jak i jego zakończenie sukcesem lub porażką (spowodowaną błędem), było zbierane w statystykach służących do uczenia sztucznej inteligencji.
Nadrzędnym celem przeprowadzania testów jest znajdowanie błędów. Powtarzanie w kółko testów, które kończą się sukcesem, może zwiększać przekonanie o dobrej jakości kodu, jednak na dłuższą metę jest to marnowanie czasu i zasobów. Algorytm powinien uczyć się pomijać taki test. Jednak najważniejsze, żeby nie pomijać testów, które akurat mogły znaleźć błąd. Wyważenie obu tych celów nie jest sprawą trywialną i stąd zastosowanie sztucznej inteligencji zamiast prostego automatu, który test wykona lub pominie na podstawie odgórnie narzuconych założeń.
W uczeniu maszynowym wykorzystywane są parametry takie jak:
- ranking podobieństwa, który określa dopasowanie test case’a do dostarczanego kodu (tu ważne jest przygotowane wcześniej mapowanie wszystkich test case’ów na Impact Area). Im wyżej w rankingu, tym większe prawdopodobieństwo wybrania test case’a.
- wskaźnik niepowodzeń, czyli liczba znalezionych błędów pośród wszystkich wykonań test case’a. Test, który zwykle się udaje, dostaje niższą wagę.
- waga dostarczenia będąca sumą dostarczeń w poszczególnych blokach kodu, od ostatniego dostarczenia przetestowanego przez dany test case. Im więcej edytowanych bloków kodu oraz im więcej czasu minęło, odkąd były pokrywane danym test case’em, tym większa waga test case’a.
- liczba powtórzeń od ostatniego sukcesu. Jeśli test raz za razem kończy się porażką, to przed ponownym wykonaniem należałoby w końcu poprawić błędy w kodzie lub przyjrzeć się czy procedura testu nie jest błędna.
To tylko ogólny opis. W sumie powstało ponad 40 liczbowych parametrów opisujących każdy z test case’ów. Maszyna potrafi zapamiętać całą historię powtórzeń testu. Im jest ich więcej, tym dokładniejsze może być przewidywanie czy test case znajdzie błąd, czy można go pominąć.
Co istotne, parametry można w każdej chwili wyzerować i zmusić maszynę do ponownej nauki. Na przykład w sytuacji, gdyby w istotny sposób zmienił się sposób działania centrali albo często zgłaszane były błędy, które powinny zostać wykryte w pomijanych testach.
Żeby jednak nie zostawiać pełni kontroli w rękach sztucznej inteligencji, a także, żeby zapewnić jej dodatkowe dane do uczenia się, co jakiś czas wykonywany jest pełen zakres testów. W ten sposób można być pewnym, że każdy test case, nawet taki notorycznie pomijany, ma regularnie odświeżane statystyki. A potencjalne błędy nie przedostają się przez okrojoną kampanię testową.