Podejście systemowe Być może największym aspektem budowania systemów, który doceniam od czasu, gdy przeniosłem swoją uwagę z działalności akademickiej na tworzenie oprogramowania typu open source, jest znaczenie testowania i automatyzacji testów.
W środowisku akademickim nie będzie przesadą stwierdzenie, że uczymy studentów testowania tylko w takim zakresie, w jakim potrzebujemy przypadków testowych do oceny ich rozwiązań, a naszych absolwentów prosimy o przeprowadzanie testów porównawczych w celu gromadzenia danych ilościowych do naszych prac badawczych, ale to w zasadzie tyle To.
Oczywiście istnieją wyjątki – na przykład programy nauczania skupiające się na inżynierii oprogramowania – ale z mojego doświadczenia wynika, że znaczenie przypisywane testowaniu w środowisku akademickim nie odpowiada jego znaczeniu w praktyce.
Powiedziałem, że doceniam rolę testowania oprogramowania, ale nie jestem pewien, czy rozumiem to jasno i wystarczająco głęboko, aby wyjaśnić to komukolwiek innemu. Ponieważ podejście do naszych systemów jest takie, chciałbym lepiej zrozumieć „dlaczego”, ale przede wszystkim to, co widzę i słyszę, to mnóstwo żargonu: testy jednostkowe, testy dymne, testy namaczania, testy regresji, testy integracyjne itp.
Problem, jaki mam z tym i podobnymi terminami, polega na tym, że mają one bardziej charakter opisowy niż nakazowy. Jasne, testowanie integracyjne (na przykład) jest dobrą rzeczą i rozumiem, dlaczego można przekonująco twierdzić, że konkretny test jest testem integracyjnym, ale nie jestem pewien, czy rozumiem, dlaczego byłoby to konieczne lub wystarczające w ogólnym schemacie rzeczy. (Jeśli ten przykład jest zbyt niejasny, oto inny opublikowany przez A Użytkownik Reddita.)
Wyjątkiem mogą być testy jednostkowe, w przypadku których pokrycie kodu jest wymierną metryką, ale nawet wtedy z mojego doświadczenia wynika, że większą wagę przywiązuje się do możliwości pomiaru postępu niż do jego rzeczywistego wkładu w tworzenie kodu wysokiej jakości.
W tym kontekście ostatnio próbowałem uporządkować ponad 700 zadań związanych z kontrolą jakości (które wiążą się ze znacznymi miesięcznymi opłatami AWS), które zgromadziły się w ciągu ostatnich pięciu lat w… Projekt eteryczny.
Nie sądzę, że konkretna funkcjonalność jest szczególnie ważna — Ether składa się z czterech podsystemów opartych na mikrousługach, każdy wdrożony jako obciążenie Kubernetes w chmurze brzegowej — chociaż podsystemy będą prawdopodobnie zarządzane jako niezależne projekty open source, każdy z własnym zespołem Własność od deweloperów. Jednak projekty mają wspólne narzędzia (np. Jenkins) i zasilają ten sam potok CI/CD, co czyni je dość reprezentatywnymi dla praktyki budowania systemów z integracji wielu źródeł nadrzędnych.
Z mojego „studium przypadku” jasno wynika, że istnieją nietrywialne kompromisy, w których konkurencyjne wymagania pchają się w różnych kierunkach. Jednym z nich jest napięcie między szybkością działania funkcji a jakością kodu i tutaj automatyzacja testów odgrywa kluczową rolę: dostarczanie narzędzi pomagających zespołom inżynierskim osiągnąć jedno i drugie.
Najlepszą praktyką (przyjętą przez Aether) jest tzw. strategia Shift Left: dostarczanie testów na możliwie najwcześniejszym etapie cyklu rozwojowego (tj. w stronę „lewego” końca rurociągu CI/CD). Jednak Shift Left jest łatwiejszy w teorii niż w praktyce, ponieważ testowanie wiąże się z kosztami, zarówno pod względem czasu (programiści czekający na uruchomienie testów), jak i zasobów (maszyny wirtualne i fizyczne potrzebne do uruchomienia testów).
Co się dzieje w praktyce?
W praktyce zaobserwowałem duże poleganie na programistach, którzy ręcznie przeprowadzali testy funkcjonalne na poziomie komponentów. Są to testy, o których myśli większość ludzi, gdy myślą o testowaniu (i gdy zamieszczają żarty na temat testowania na Reddicie), podczas których niezależni inżynierowie ds. kontroli jakości wnoszą wartość, szukając problemów pomijanych przez programistów, ale wciąż nie przewidujących krytycznych przypadków brzegowych.
W przypadku Aether jeden z głównych testów funkcjonalnych sprawdza, jak dobrze programiści wdrażają Aether Protokół 3GPP Specyfikacje – zadanie tak złożone, że testy zwykle kupuje się od zewnętrznego dostawcy. Jeśli chodzi o testy automatyczne, potok CI/CD wykonuje głównie testy formalne (np. czy się kompiluje, czy ma odpowiednią informację o prawach autorskich, czy programista podpisał umowę CLA) jako bramę do integracji łatki z bazą kodu.
Stanowi to duże obciążenie dla testów integracyjnych po fuzji, gdzie głównym problemem jest zapewnienie odpowiedniego „pokrycia konfiguracji”, tj. sprawdzenie, czy niezależnie opracowane podsystemy są skonfigurowane w sposób odzwierciedlający sposób, w jaki zostaną wdrożone jako spójna całość. . Zasięg jednostki jest prosty. Nie obejmuje całego systemu.
Dla mnie kluczową ideą jest świadomość, że zarządzanie konfiguracją i efektywność testów są ze sobą ściśle powiązane. (Również dlatego tak ważna jest automatyzacja potoków CI/CD.)
Aby uczynić to bardziej konkretnym, posłużę się konkretnym przykładem z eteru (który moim zdaniem nie jest wyjątkowy).
Aby przetestować nową funkcję — na przykład możliwość uruchamiania wielu funkcji na poziomie użytkownika (Współczynnik ochrony UV), każdy obsługujący inną partycję (kawałek) urządzeń bezprzewodowych – konieczne jest wdrożenie zestawu (a) Mobile Core implementującego UPF, (b) kontrolera wykonawczego, który kojarzy urządzenia z instancjami UPF oraz (c) generator obciążenia, który wysyła znaczący ruch przez każdy UPF.
Każdy z trzech komponentów ma swój własny „plik konfiguracyjny”, który tester integracji musi dopasować w sposób zapewniający ogólny wynik. W luźno powiązanym systemie chmurowym, takim jak Aether, integracja oznacza skoordynowaną konfigurację.
Teraz wyobraź sobie, że robisz to w przypadku każdej nowej wprowadzonej funkcji i albo liczba unikalnych konfiguracji wzrasta, albo odkrywasz, w jaki sposób odpowiednie funkcje się zbiegają, selektywnie decydując, które kombinacje testować, a których nie.
Nie mam dobrej odpowiedzi na pytanie, jak to zrobić, ale wiem, że wymaga to wiedzy i wewnętrznego osądu. Doświadczenie pokazuje również, że wiele błędów zostanie wykrytych dopiero w wyniku użytkowania, co mówi mi, że testowanie przed wydaniem i monitorowanie po wydaniu są ze sobą ściśle powiązane. Traktowanie zarządzania wydaniami (w tym wdrożeń etapowych) jako kolejnej fazy strategii testowania jest rozsądnym podejściem całościowym.
Wracając do tego, od czego zacząłem – próbując zrozumieć testowanie oprogramowania przez pryzmat systemów – nie sądzę, że spełniłem moje osobiste kryteria akceptacji. Istnieje kilka zasad projektowania, z którymi należy się zapoznać, ale zadanie to nadal wydaje się być połączeniem sztuki i inżynierii w równym stopniu.
Jeśli chodzi o sortowanie, które podjąłem w puli zadań Aether QA, nadal mam trudności z oddzieleniem ziarna od plew. Jest to naturalny wynik ewoluującego systemu, bez jasnego planu wyłączenia starszych testów. Prace nad tą kwestią wciąż trwają, ale jasny wniosek jest taki, że rygorystyczne podstawy w zakresie testowania oprogramowania dobrze odniosą zarówno studenci, jak i praktycy. ®