Badam techniki i strategie skalowania naszej rosnącej liczby testów integracyjnych naszego obecnego produktu, aby mogły (po ludzku) pozostać częścią naszego rozwoju i procesu CI.
Przy ponad 200 testach integracyjnych osiągamy już 1 godzinę, aby ukończyć pełny test (na komputerze stacjonarnym), a to negatywnie wpływa na zdolność dewelopera do tolerowania uruchamiania całego pakietu w ramach rutynowych procesów wypychania. Co wpływa na motywację do zdyscyplinowania ich dobrego tworzenia. Testujemy integrację tylko kluczowych scenariuszy od przodu do tyłu i używamy środowiska, które odzwierciedla produkcję, które jest budowane od podstaw przy każdym uruchomieniu testowym.
Ze względu na czas potrzebny do uruchomienia, tworzy straszliwą pętlę sprzężenia zwrotnego i wiele zmarnowanych cykli czekających na maszynach na zakończenie testów, bez względu na to, jak skoncentrowane są testy. Nie zwracaj uwagi na droższy negatywny wpływ na przepływ i postęp, zdrowie psychiczne i zrównoważony rozwój.
Oczekujemy, że przeprowadzimy 10-krotnie więcej testów integracyjnych, zanim ten produkt zacznie zwalniać (tak naprawdę nie ma pojęcia, ale nie wydaje się, żebyśmy zaczynali jeszcze pod względem funkcji). W pewnym momencie musimy się spodziewać, że będziemy w kilkuset lub kilku tysiącach testów integracyjnych.
Żeby było jasne, postaraj się, aby dyskusja ta nie była dyskusją na temat testów jednostkowych a testów integracyjnych (które nigdy nie powinny być przedmiotem wymiany). Przeprowadzamy zarówno testy jednostkowe z TDD ORAZ testy integracyjne w tym produkcie. W rzeczywistości przeprowadzamy testy integracyjne na różnych warstwach architektury usług, które mamy, gdzie ma to dla nas sens, ponieważ musimy zweryfikować, gdzie wprowadzamy przełomowe zmiany przy zmianie wzorców w naszej architekturze na inne obszary system.
Trochę o naszym stosie technologii. Obecnie testujemy środowisko emulacji (intensywnie wykorzystujące procesor i pamięć), aby przeprowadzać nasze testy od początku do końca. Który składa się z usług sieci Web Azure REST frontend backend noSql (ATS). Symulujemy nasze środowisko produkcyjne, uruchamiając emulator pulpitu Azure + IISExpress. Jesteśmy ograniczeni do jednego emulatora i jednego lokalnego repozytorium zaplecza na maszynę programistyczną.
Mamy również CI oparty na chmurze, który przeprowadza ten sam test w tym samym emulowanym środowisku, a testy trwają dwa razy dłużej (ponad 2 godziny) w chmurze u naszego obecnego dostawcy CI. Osiągnęliśmy limity SLA dla dostawców CI w chmurze pod względem wydajności sprzętowej i przekroczyliśmy ich limit czasu testowania. Aby być uczciwym, ich specyfikacje nie są złe, ale w połowie tak dobre, jak wbudowana chropowata maszyna stacjonarna.
Stosujemy strategię testowania polegającą na przebudowaniu naszego magazynu danych dla każdej logicznej grupy testów i wstępnym załadowaniu danych testowych. Kompleksowo zapewniając integralność danych, daje to 5-15% wpływ na każdy test. Uważamy więc, że niewiele można zoptymalizować tę strategię testowania na tym etapie opracowywania produktu.
Długa i krótka z tego jest taka: chociaż możemy zoptymalizować przepustowość każdego testu (nawet jeśli nawet o 30% -50% każdy), nadal nie będziemy skutecznie skalować w najbliższej przyszłości za pomocą kilkuset testów. 1 godzina teraz jest nawet znacznie wyższa niż ludzka tolerancja, potrzebujemy rzędu ogólnej poprawy całego procesu, aby był on zrównoważony.
Badam więc, jakie techniki i strategie możemy zastosować, aby radykalnie skrócić czas testowania.
- Pisanie mniej testów nie jest opcją. Proszę nie dyskutować tego w tym wątku.
- Korzystanie z szybszego sprzętu jest zdecydowanie opcją, choć bardzo kosztowne.
- Równoległe uruchamianie grup testów / scenariuszy na osobnym sprzęcie jest zdecydowanie preferowaną opcją.
- Tworzenie grup testów wokół opracowywanych funkcji i scenariuszy jest wiarygodne, ale ostatecznie nie jest wiarygodne, jeśli chodzi o wykazanie pełnego zasięgu lub pewności, że zmiana nie wpłynie na system.
- Technicznie możliwe jest uruchamianie w środowisku pomostowym skalowanym w chmurze zamiast w emulatorze pulpitu, chociaż zaczynamy dodawać czasy wdrażania do uruchomień testowych (około 20 minut na początku uruchomienia testowego w celu wdrożenia).
- Podział elementów systemu na niezależne elementy logiczne jest do pewnego stopnia prawdopodobny, ale spodziewamy się w tym ograniczonym przebiegu, ponieważ oczekuje się, że interakcje między składnikami będą rosły z czasem. (tj. zmiana jest możliwa, może wpłynąć na innych w nieoczekiwany sposób - jak to często się dzieje, gdy system jest rozwijany stopniowo)
Chciałem zobaczyć, jakich strategii (i narzędzi) używają inni w tej przestrzeni.
(Muszę wierzyć, że inni mogą mieć tego rodzaju trudności przy korzystaniu z niektórych zestawów technologii).
[Aktualizacja: 16.12.2016: W końcu zainwestowaliśmy więcej w testy równoległe CI, aby omówić wynik: http://www.mindkin.co.nz/blog/2015/12/16/16-jobs]
źródło
Odpowiedzi:
Pracowałem w miejscu, które zajęło 5 godzin (na 30 komputerach), aby przeprowadzić testy integracyjne. Przebudowałem bazę kodu i zamiast tego przeprowadziłem testy jednostkowe dla nowych rzeczy. Testy jednostkowe trwały 30 sekund (na 1 maszynie). Aha, i błędy też spadły. I czas rozwoju, ponieważ dokładnie wiedzieliśmy , co zepsuło się z testami szczegółowymi.
Krótko mówiąc, ty nie. Pełne testy integracyjne rosną wykładniczo wraz ze wzrostem bazy kodu (więcej kodu oznacza więcej testów, a więcej kodu oznacza, że wszystkie testy trwają dłużej, ponieważ jest więcej „integracji” do pracy). Twierdziłbym, że wszystko w zakresie „godzin” traci większość korzyści płynących z ciągłej integracji, ponieważ nie ma tam pętli sprzężenia zwrotnego. Nawet ulepszenie o rząd wielkości nie wystarczy, abyś był dobry - i nie jest blisko, abyś był skalowalny.
Dlatego zalecałbym ograniczenie testów integracyjnych do najszerszych, najważniejszych testów dymu. Można je następnie uruchamiać co noc lub z przerwami mniej niż ciągłymi, co znacznie zmniejsza potrzebę wydajności. Testy jednostkowe, które rosną tylko liniowo, gdy dodajesz więcej kodu (wzrost testów, środowisko uruchomieniowe dla jednego testu nie), są drogą do skalowania.
źródło
Testy integracyjne zawsze będą długotrwałe, ponieważ powinny naśladować prawdziwego użytkownika. Z tego właśnie powodu nie powinieneś uruchamiać ich wszystkich synchronicznie!
Biorąc pod uwagę, że już uruchamiasz rzeczy w chmurze, wydaje mi się, że jesteś w doskonałej pozycji, aby skalować swoje testy na wielu komputerach.
W skrajnym przypadku uruchom jedno nowe środowisko na test i uruchom je wszystkie jednocześnie. Twoje testy integracyjne potrwają tylko tak długo, jak najdłużej trwający test.
źródło
Ograniczenie / optymalizacja testów wydaje mi się najlepszym pomysłem, ale w przypadku, gdy nie jest to opcja, mam alternatywę do zaproponowania (ale wymaga zbudowania kilku prostych, zastrzeżonych narzędzi).
Napotkałem podobny problem, ale nie w naszych testach integracyjnych (te działały w ciągu kilku minut). Zamiast tego było to po prostu w naszych kompilacjach: zbudowanie dużej bazy kodu C zajęłoby wiele godzin.
Co widziałem jak niezwykle rozrzutny był fakt, że byliśmy odbudowy całą rzecz od zera (około 20000 plików źródłowych / jednostki kompilacji), nawet jeśli tylko kilka plików źródłowych zmieniło i godziny zatem wydatki na zmiany, które powinny podjąć tylko sekund lub minut w najgorszym przypadku.
Więc próbowaliśmy przyrostowego łączenia na naszych serwerach kompilacji, ale to było niewiarygodne. Czasami dawałby fałszywe negatywy i nie opierałby się na niektórych zatwierdzeniach, ale tylko wtedy odniósłby sukces przy pełnej przebudowie. Co gorsza, czasami dawałby fałszywe alarmy i informował o powodzeniu kompilacji, tylko aby programista połączył zepsutą kompilację w głównej gałęzi. Wróciliśmy więc do przebudowywania wszystkiego za każdym razem, gdy programista wypychał zmiany ze swojego prywatnego oddziału.
Tak bardzo tego nienawidziłem. Chodziłem do sal konferencyjnych z połową programistów grających w gry wideo i po prostu dlatego, że nie miałem nic więcej do roboty, czekając na kompilacje. Próbowałem uzyskać przewagę produktywności poprzez wielozadaniowość i uruchomienie nowego oddziału po zatwierdzeniu, aby móc pracować nad kodem w oczekiwaniu na kompilacje, ale gdy test lub kompilacja zakończyła się niepowodzeniem, stało się zbyt bolesne, aby kolejkować zmiany po tym punkcie i spróbuj naprawić wszystko i zszyj to wszystko z powrotem.
Projekt boczny podczas oczekiwania, zintegruj później
Zamiast tego stworzyłem szkielet aplikacji - ten sam rodzaj podstawowego interfejsu użytkownika i odpowiednich części pakietu SDK, dla którego opracowałem odrębny projekt. Następnie napisałbym przeciwko temu niezależny kod, czekając na kompilacje poza głównym projektem. To przynajmniej dało mi trochę kodu do zrobienia, abym mógł pozostać nieco produktywny, a następnie zacząłem integrować pracę wykonaną całkowicie poza produktem z projektem później - fragmenty kodu. To jedna ze strategii dla deweloperów, jeśli czekają dużo.
Ręczne analizowanie plików źródłowych, aby dowiedzieć się, co odbudować / uruchomić ponownie
Jednak nienawidziłem tego, jak traciliśmy tyle czasu na odbudowę wszystkiego przez cały czas. W ciągu kilku weekendów wziąłem na siebie obowiązek napisania kodu, który faktycznie skanowałby pliki w poszukiwaniu zmian i odbudowy tylko odpowiednich projektów - wciąż pełna przebudowa, brak przyrostowego łączenia, ale tylko projektów, które należy odbudować ( których pliki zależne, parsowane rekurencyjnie, uległy zmianie). Było to całkowicie niezawodne i po wyczerpującym zademonstrowaniu i przetestowaniu go, mogliśmy skorzystać z tego rozwiązania. Skróciło to średni czas kompilacji z godzin do kilku minut, ponieważ przebudowywaliśmy tylko niezbędne projekty (chociaż zmiany w centralnym zestawie SDK mogły nadal potrwać godzinę, ale robiliśmy to znacznie rzadziej niż zmiany zlokalizowane).
Ta sama strategia powinna mieć zastosowanie do testów integracyjnych. Po prostu rekursywnie analizuj pliki źródłowe, aby dowiedzieć się, od jakich plików będą zależeć testy integracyjne (np.
import
W Javie,#include
w C lub C ++) po stronie serwera, a pliki dołączone / zaimportowane z tych plików i tak dalej, tworząc pełny wykres zależności plików dołączania / importowania dla systemu. W przeciwieństwie do analizowania kompilacji, które tworzy DAG, wykres powinien zostać przekierowany, ponieważ interesuje go każdy zmieniony plik zawierający kod, który można wykonać pośrednio *. Ponownie uruchom test integracji tylko, jeśli którykolwiek z tych plików na wykresie dla interesującego testu integracji zmienił się. Nawet w przypadku milionów wierszy kodu parsowanie było łatwe w mniej niż minutę. Jeśli masz pliki inne niż kod źródłowy, które mogą wpływać na test integracji, takie jak pliki treści, być może możesz zapisać metadane w komentarzu w kodzie źródłowym wskazującym na te zależności w testach integracji, aby w przypadku zmiany tych plików zewnętrznych testy również uruchom ponownie.* Na przykład, jeśli test.c zawiera foo.h, który jest również zawarty w foo.c, to zmiana na test.c, foo.h lub foo.c powinna oznaczać zintegrowany test jako wymagający nowego uruchomienia.
Może to zająć cały dzień lub dwa, aby zaprogramować i przetestować, szczególnie w środowisku formalnym, ale myślę, że powinien działać nawet na testy integracyjne i warto, jeśli nie masz innego wyjścia, jak czekać w kompilacjach w godzinach do zakończenia (ze względu na proces budowy, testowania, pakowania lub cokolwiek innego). To może przełożyć się na tak wiele roboczogodzin utraconych w ciągu zaledwie kilku miesięcy, które skróciłyby czas potrzebny na zbudowanie tego rodzaju zastrzeżonego rozwiązania, a także zabiły energię zespołu i zwiększyły stres spowodowany konfliktami w większych połączeniach, przy mniejszym nakładzie pracy często w wyniku zmarnowanego czasu oczekiwania. Jest to po prostu złe dla całego zespołu, gdy spędzają dużo czasu na czekaniu na rzeczy.wszystko do przebudowania / ponownego uruchomienia / przepakowania przy każdej drobnej zmianie.
źródło
Wygląda na to, że masz zbyt wiele testów integracji. Przypomnij piramidę testową . Testy integracyjne należą do pośrodku.
Jako przykład przyjmować repozytorium metodą
set(key,object)
,get(key)
. To repozytorium jest szeroko stosowane w całej bazie kodu. Wszystkie metody zależne od tego repozytorium zostaną przetestowane przy użyciu fałszywego repozytorium. Teraz potrzebujesz tylko dwóch testów integracyjnych, jednego dla zestawu i jednego dla pobrania.Niektóre z tych testów integracji można prawdopodobnie przekształcić w testy jednostkowe. Na przykład testy end-end moim zdaniem powinny tylko sprawdzać, czy witryna jest poprawnie skonfigurowana z poprawnym łańcuchem połączenia i poprawnymi domenami.
Testy integracyjne powinny sprawdzić, czy ORM, repozytoria i abstrakcje kolejek są poprawne. Zasadniczo do testowania integracji nie jest potrzebny kod domeny - tylko abstrakcje.
Prawie wszystko inne może być testowane jednostkowo za pomocą implementacji zależnych / unieruchomionych / fałszywych / w pamięci.
źródło
Z mojego doświadczenia w środowisku Agile lub DevOps, w którym potoki ciągłego dostarczania są powszechne, testy integracji powinny być przeprowadzane po zakończeniu lub modyfikacji każdego modułu. Na przykład w wielu środowiskach ciągłego dostarczania często zdarza się, że wiele wdrożeń kodu przypada na jednego programistę dziennie. Przeprowadzenie szybkiego zestawu testów integracyjnych na końcu każdej fazy programowania przed wdrożeniem powinno być standardową praktyką w tego typu środowisku. Dodatkową informacją jest świetny eBook do czytania w tym temacie - Praktyczny przewodnik po testach w DevOps , napisany przez Katrinę Clokie.
Aby skutecznie testować w ten sposób, nowy komponent musi zostać przetestowany z istniejącymi ukończonymi modułami w dedykowanym środowisku testowym lub z komponentami i sterownikami. W zależności od potrzeb ogólnie dobrym pomysłem jest przechowywanie biblioteki kodów pośredniczących i sterowników dla każdego modułu aplikacji w folderze lub bibliotece, aby umożliwić szybkie powtarzalne testowanie integracji. Utrzymywanie takich skrótów i sterowników w ten sposób ułatwia wprowadzanie iteracyjnych zmian, aktualizowanie ich i optymalne spełnianie bieżących potrzeb testowych.
Inną opcją do rozważenia jest rozwiązanie opracowane pierwotnie około 2002 r., Zwane wirtualizacją usług. Tworzy to środowisko wirtualne, symulując interakcję modułu z istniejącymi zasobami do celów testowych w złożonym przedsiębiorczym DevOps lub środowisku Agile.
Ten artykuł może być przydatny, aby dowiedzieć się więcej na temat przeprowadzania testów integracyjnych w przedsiębiorstwie
źródło
Czy zmierzyłeś każdy test, aby zobaczyć, gdzie jest brany czas? A następnie zmierzył wydajność bazy kodu, jeśli występuje wyjątkowo powolny bit. Czy ogólny problem jest jednym z testów lub wdrożenia, czy może jednym i drugim?
Zazwyczaj chcesz zmniejszyć wpływ testu integracji, aby zminimalizować uruchamianie ich przy stosunkowo niewielkich zmianach. Następnie możesz zostawić pełny test do uruchomienia „QA”, które wykonujesz, gdy gałąź zostanie awansowana na wyższy poziom. Masz więc testy jednostkowe dla gałęzi deweloperów, po scaleniu uruchom zredukowane testy integracyjne i uruchom pełny test integracyjny po scaleniu z gałęzią kandydującą do wydania.
Oznacza to, że nie musisz odbudowywać, przepakowywać i ponownie wdrażać wszystkiego przy każdym zatwierdzeniu. Możesz zorganizować konfigurację w środowisku deweloperskim, aby wykonać możliwie najtańsze wdrożenie, ufając, że będzie w porządku. Zamiast rozruszać całą maszynę wirtualną i wdrożyć cały produkt, pozostaw maszynę wirtualną ze starą wersją na miejscu i na przykład skopiuj nowe pliki binarne (YMMV w zależności od tego, co musisz zrobić).
To ogólne optymistyczne podejście nadal wymaga pełnego testu, ale można to przeprowadzić na późniejszym etapie, gdy czas jest mniej pilny. (np. możesz uruchomić pełny test raz w nocy, jeśli są jakieś problemy, programista może je rozwiązać rano). Ma to również tę zaletę, że odświeża produkt na platformie integracyjnej do testów na następny dzień - może się zdezaktualizować, ponieważ deweloperzy zmieniają rzeczy, ale tylko o 1 dzień.
Mamy podobny problem z uruchomionym narzędziem do analizy statycznej opartym na bezpieczeństwie. Pełne przebiegi zajęłyby wieki, więc przenieśliśmy uruchamianie go z zatwierdzeń programistycznych do zatwierdzenia integracji (tj. Mieliśmy system, w którym deweloperzy stwierdzili, że zostały zakończone, a następnie połączono z oddziałem „poziomu 2”, w którym przeprowadzono więcej testów, w tym perf testy. Po zakończeniu został on włączony do oddziału kontroli jakości w celu wdrożenia. Pomysł polega na usunięciu regularnych uruchomień, które występowałyby w sposób ciągły, z uruchomieniami wykonywanymi co noc - deweloperzy otrzymaliby wyniki rano i nie wpłynęłyby na ich rozwój. skup się na później w cyklu deweloperskim).
źródło
W pewnym momencie pełny zestaw testów integracyjnych może potrwać wiele godzin, nawet na drogim sprzęcie. Jedną z opcji jest nie uruchamianie większości tych testów przy każdym zatwierdzeniu, a zamiast tego uruchamianie ich co noc lub w trybie ciągłym wsadowym (raz na wiele zatwierdzeń).
Stwarza to jednak nowy problem - programiści nie otrzymują natychmiastowej informacji zwrotnej, a uszkodzone kompilacje mogą pozostać niezauważone. Aby to naprawić, ważne jest, aby wiedzieli, że coś jest zawsze uszkodzone. Zbuduj narzędzia do powiadamiania, takie jak Catlight lub powiadomienie zasobnika TeamCity .
Ale będzie jeszcze jeden problem. Nawet gdy programista zauważy, że kompilacja jest zepsuta, może nie spieszyć się, aby to sprawdzić. W końcu ktoś może już to sprawdzać, prawda?
Z tego powodu te dwa narzędzia mają funkcję „kompilacji dochodzenia”. Powie, czy ktoś z zespołu programistów faktycznie sprawdza i naprawia zepsutą kompilację. Deweloperzy mogą zgłosić się na ochotnika, aby sprawdzić kompilację, a do tego czasu wszyscy w zespole będą zirytowani czerwoną ikoną obok zegara.
źródło
Wygląda na to, że baza kodu rośnie, a niektóre zarządzanie kodem pomoże. Używamy Javy, więc z góry przepraszamy, jeśli to założę.
Sklep Java, w którym pracuję, wykorzystuje to podejście i rzadko czekamy na testy integracyjne.
źródło
Innym możliwym podejściem do utrzymywania testów integracji potoku CI (lub dowolnego rodzaju weryfikacji, w tym kompilacji) z długim czasem wykonania lub wymagającym ograniczonych i / lub kosztownych zasobów, jest przejście z tradycyjnych systemów CI na podstawie weryfikacji po zatwierdzeniu (które są podatne na zatory ) do jednego opartego na weryfikacjach przed zatwierdzeniem .
Zamiast bezpośrednio zatwierdzać zmiany w programiści oddziału, przesyłają je do scentralizowanego automatycznego systemu weryfikacji, który dokonuje weryfikacji i:
Takie podejście umożliwia łączenie i testowanie wielu przesłanych zmian, potencjalnie wielokrotnie zwiększając efektywną szybkość weryfikacji CI.
Jednym z takich przykładów jest system bramkowania oparty na Gerrit / Zuul wykorzystywany przez OpenStack .
Kolejnym jest ApartCI ( zrzeczenie się odpowiedzialności - jestem jego twórcą i założycielem firmy, która go oferuje).
źródło