Sot, shumica e produkteve softuerike zhvillohen në ekipe. Kushtet për suksesin e zhvillimit në grup mund të përshkruhen me një skemë të thjeshtë.

Pasi të keni shkruar kodin, duhet të siguroheni që ai:
- Funksionon.
- Nuk dëmton asgjë, përfshirë kodin që kanë shkruar kolegët tuaj.
Nëse të dy kushtet përmbushen, atëherë jeni në rrugën e duhur për sukses. Për të kontrolluar lehtësisht këto kushte dhe për të mos devijuar nga rruga e duhur, u shpik Continuous Integration.
CI është një proces pune, ku ju integroheni sa më shpesh në kodin e përbashkët të produktit. Dhe jo thjesht integroidi, por gjithashtu kontrolloni vazhdimisht që gjithçka të funksionojë. Duke qenë se duhet të kontrolloni shumë dhe shpesh, është e mençur të mendoni për automatizimin. Mund të kontrolloni gjithçka me dorë, por nuk ia vlen, dhe ja pse.
- Njerëzit janë të shtrenjtë. Çmimi i punës së çdo programuesi është më i lartë se çmimi i punës së çdo serveri.
- Njerëzit bëjnë gabime. Prandaj mund të lindin situata ku janë ekzekutuar teste në degën e gabuar apo janë ndërtuar komitete të gabuara për testuesit.
- Njerëzit janë të dembelë. Herë pas here, kur përfundoj një detyrë, më vjen në mendje: «Çfarë ka për të kontrolluar këtu? Kam shkruar dy rreshta — sigurisht që punon!» Mendoj se disa prej jush ndoshta ndjejnë të njëjtat mendime herë pas here. Por duhet të kontrolloni gjithmonë.
Si implementuam dhe zhvilluam Continuous Integration në ekipin e zhvillimit të aplikacioneve mobile të Avito, si arritëm nga 0 në 450 ndërtime në ditë, dhe çfarë ndërtimi makinat mbledhin 200 orë në ditë, tregon Nikolai Nesterov () — pjesëmarrës në të gjitha ndryshimet evolucionare të CI/CD për aplikacionin Android.
Rrëfimi është i ndërtuar mbi shembuj nga ekipi Android, por shumica e qasjeve janë gjithashtu të aplikueshme dhe në iOS.

Dikur shumë kohë më parë, në ekipin Android të Avito punonte një person. Nga definicioni, ai nuk kishte nevojë për asgjë nga Continuous Integration: nuk kishte asnjë me të cilin të integrohej.
Por aplikacioni u rrit, shfaqeshin gjithnjë e më shumë detyra të reja, për pasojë rritej ekipi. Në një çast, erdhi koha për të organizuar më formalisht procesin e integrimit të kodit. U vendos të përdorej Git flow.

Koncepcioni i Git flow është i njohur: në projekt ka një degë të zakonshme develop, dhe për çdo karakteristikë të re, zhvilluesit krijojnë një degë të veçantë, e komitojnë aty, e pushojnë, dhe, kur duan të integrojnë kodin e tyre në degën develop, hapin një pull request. Për t'u shkëmbyer dije dhe për të diskutuar qasje, kemi futur kontrollin e kodit, që do të thotë që kolegët duhet të verifikojnë dhe të konfirmojnë kodin e njëri-tjetrit.
Kontrolli
Të shikosh kodin me sy është e shkëlqyer, por e pamjaftueshme. Prandaj futen kontrollet automatike.
- Së pari kontrollojmë ndërtimin e ARC.
- Shumë teste Junit.
- Llogarisim mbulimin e kodit, që të përfitojmë nga testet.
Për të kuptuar se si duhet të ekzekutohen këto kontrolle, do të shikojmë procesin e zhvillimit në Avito.
Në mënyrë skematike, mund ta paraqesim kështu:
- Zhvilluesi shkruan kodin në laptopin e tij. Mund të ndërlidhen kontrollet e integrimit këtu — ose me një hook komiti, ose thjesht duke e ekzekutuar kontrollin në sfond.
- Pas pushimit të kodit nga zhvilluesi, ai hap një pull request. Për të kaluar kodin e tij në degën develop, është e nevojshme të kalojmë kontrollin e kodit dhe të grumbullojmë numrin e duhur të konfirmimeve. Mund të aktivizoni kontrollet dhe ndërtimet këtu: derisa të mos jenë të suksesshme të gjitha ndërtimet, pull request-i nuk mund të bashkohet.
- Pasi që pull request-i është lidhur dhe kodi ka kaluar në develop, mund të zgjedhësh një kohë të përshtatshme: për shembull, natën, kur të gjitha serverët janë të lirë, dhe të bësh kontrollet sa të duash.
Askush nuk e pëlqeu të ekzekutojë kontrollin në laptopin e tij. Kur zhvilluesi përfundon një funksionalitet, ai dëshiron ta dërgojë sa më shpejt dhe të hapë një pull request. Nëse në atë moment nisin disa verifikime të gjata, kjo jo vetëm që nuk është shumë e këndshme, por edhe ngadalëson zhvillimin: ndërsa laptopi bën kontrolle, është e pamundur të punosh normalisht.
Na pëlqeu shumë të bënim kontrollet natën, sepse ka shumë kohë dhe serverë, mund të shfrytëzojmë sa të duam. Por, fatkeqësisht, kur kodi i funksionalitetit ka kaluar në develop, zhvilluesi tashmë ka shumë më pak motivim për të rregulluar gabimet që gjeti CI. Herë pas here e kapja veten duke menduar, kur shikoja në raportin e mëngjesit për të gjitha gabimet e gjetura, se do t’i rregulloja ato ndonjëherë më vonë, sepse tani në Jira ka një detyrë të re super që e dëshiroj të filloj ta bëj.
Nëse kontrollet bllokojnë pull request-in, atëherë motivimi është i mjaftueshëm, sepse derisa ndërtimet të mos bëhen të gjelbra, kodi nuk do të kalojë në develop, e pra, detyra nuk do të përfundojë.
Në fund, ne zgjodhëm një strategji të tillë: gjatë natës realizojmë sa më shumë kontrolle të mundshme, dhe verifikimet më kritike dhe, mbi të gjitha, ato që janë të shpejta, i ekzekutojmë në pull request. Por nuk ndalemi këtu - paralelisht optimizojmë shpejtësinë e kalimit të kontrolleve në mënyrë që t'i transferojmë ato nga moda e natës në kontrollet për pull request.
Në atë kohë, të gjitha ndërtimet tona kalonin mjaft shpejt, prandaj thjesht aktivizuam bllokuesin për pull request ndërtimin ARK, testet Junit dhe llogaritjen e mbulimit të kodit. Aktivizuam, menduam - dhe u dorëzuam nga mbulimi i kodit, sepse menduam se nuk na duhet.
Për konfigurimin e bazës CI, kemi nevojitur dy ditë (këtu dhe më tej, vlerësimi temporal është përafërsisht, nevojitet për shkallën).
Pas kësaj filluam të mendojmë më tej - a e kontrollojmë saktësisht? A e ekzekutojmë ndërtimin në pull request siç duhet?
Ne realizonim ndërtimin në commitin e fundit të degës nga e cila ishte hapur pull request. Por kontrollimet e këtij commit tregojnë vetëm se ai kod që zhvilluesi shkroi punon. Por ato nuk dëshmojnë se ai nuk ka thyer asgjë. Në fakt, duhet të kontrollojmë gjendjen e degës develop pasi që veçoria të jetë e integruar në të.

Për këtë shkak, ne kemi shkruar një skript të thjeshtë bash. premerge.sh:
#!/usr/bin/env bash
set -e
git fetch origin develop
git merge origin/developKëtu thjesht ndodhin të gjitha ndryshimet më të reja nga develop dhe përfshihen në degën aktuale. Ne e kemi shtuar skriptin premerge.sh si hapin e parë të të gjitha ndërtimeve dhe kemi filluar të kontrollojmë saktësisht atë që duam, pra integrimin..
Për lokalizimin e problemeve, kërkimin e zgjidhjeve dhe shkruajnë këtë skript, kaluan tre ditë.
Aplikacioni u zhvillua, detyrat e reja rriteshin, ekipi u rrit, dhe premerge.sh filloi ndonjëherë të na zhgënjente. Ndryshimet konfliktuese hynin në develop dhe thyenin ndërtimin.
Një shembull se si ndodh kjo:

Dy zhvillues fillojnë të punojnë njëkohësisht në funksionet A dhe B. Zhvilluesi i funksionit A zbulon një funksion të papërdorur në projekt answer() dhe, si një skaut i mirë, e fshin atë. Ndërkohë, zhvilluesi i funksionit B në degën e tij shton një thirrje të re për këtë funksion.
Zhvilluesit përfundojnë punën dhe në të njëjtën kohë hapin pull request. Ndërtimet fillojnë, premerge.sh kontrollon të dy pull request-et në raport me gjendjen e freskët të develop — të gjitha kontrollet janë të gjelbra. Pas kësaj, mërgjohet pull request-i i funksionit A, mërgjohet pull request-i i funksionit B… Bam! Develop thyhet, sepse në kodin develop ka një thirrje për një funksion që nuk ekziston.

Kur nuk arrihet të zhvillohet, kjo është një katastrofë lokale. Ekipi i tërë nuk mund të mbledhë asgjë dhe ta dorëzojë për testim.
A ka ndodhur që unë shpesh kam punuar në detyra infrastrukturore: analiza, rrjeti, bazat e të dhënave. Në kuptimin që unë shkruaja ato funksione dhe klasa që përdorin zhvilluesit e tjerë. Për këtë arsye, shpesh kam rënë në situata të ngjashme. Madje, një periudhë, kisha një imazh të tillë të varur.

Pasi kjo nuk na kënaqte, filluam të shqyrtonim mundësi se si ta parandalojmë këtë.
Si të mos prishim zhvillimin
Mundësia e parë: rëndësia e rikonstruimit të të gjithë kërkesave të integrimit gjatë përditësimit të zhvillimit. Në shembullin tonë, nëse kërkesa e integrimit për karakteristikën A do të jetë e para në zhvillim, kërkesa e karakteristikës B do të rikonstruohet, dhe, për rrjedhojë, verifikimet nuk do të kalojnë për shkak të gabimit të kompilim.
Për të kuptuar sa kohë do të duhen për këtë, le të shqyrtojmë një shembull me dy PR. Hapim dy PR: dy ndërtime, dy kthime verifikimesh. Pas përfshirjes së PR të parë në zhvillim, e dyta duhet të rikonstruohet. Pra, për dy PR ne nevojitim tre kthime verifikimesh: 2 + 1 = 3.
Në thelb, është në rregull. Por ne shikuam statistikat, dhe situata tipike në ekipin tonë kishte 10 PR të hapura, dhe numri i verifikimeve është shuma e progresionit: 10 + 9 +… + 1 = 55. Pra, për të pranuar 10 PR, duhet të rikomandojmë 55 herë. Dhe kjo është në situatën ideale, kur të gjitha verifikimet kalojnë për herë të parë, kur askush nuk hap një pull request shtesë gjatë procesimit të këtyre dhjetë.
Imagjinoni veten si një zhvillues që duhet të përpiqet të shtypë butonin 'merge' i pari, sepse nëse e bën fqinjë, do të duhet të presë derisa të kalojnë të gjitha ndërtimet përsëri... Jo, kështu nuk shkon, kjo do ta ngadalësonte seriozisht zhvillimin.
Një mundësi tjetër: të ndërtojmë pull request pas shqyrtimit të kodit. Pra ndaj, hapni një pull request, mblidhni numrin e nevojshëm të aprovimeve nga kolegët, realizoni ndryshimet e nevojshme, dhe pastaj filloni ndërtimet. Nëse ato janë të suksesshme, pull request-i bashkohet me develop. Në këtë rast, nuk ka nevojë për ringjallje të mëtejshme, por ngadalëson shumë reagimin. Unë, si zhvillues, kur hap një pull request, dua të shoh menjëherë nëse ai ndërtokohet. Për shembull, nëse ndonjë test dështon, duhet ta riparoj shpejt. Në rastin e ndërtimit të vonuar, reagimi ngadalësohet, çka do të thotë se e gjithë zhvillimi po ngadalësohet. Kjo gjithashtu nuk na përmbushte.
Më në fund, mbeti vetëm varianti i tretë — të riciklohet. I gjithë kodi ynë, të gjitha burimet tona ruhen në një depozitë në serverin Bitbucket. Prandaj, na duhej të zhvillonim një plugin për Bitbucket.

Ky kyçës është një plugin që tejkalon mekanizmin e bashkimit të kërkesave për tërheqje. Fillimi është standard: hapet një PR, aktivizohen të gjitha ndërtimet, kalon shqyrtimin e kodit. Por pas kalimit të shqyrtimit të kodit, kur zhvilluesi vendos të shtypë "bashko", plugin kontrollon në lidhje me cilin gjendje zhvillimi ishin kryer verifikimet. Nëse pas ndërtimeve zhvillimi është azhurnuar, plugin nuk do t'i lejojë të bashkohen një kërkesë për tërheqje në degën kryesore. Ai thjesht do të rilançojë ndërtimet në lidhje me zhvillimin e ri.

Në shembullin tonë me ndryshime konfliktuese, këto ndërtime nuk do të kalojnë për shkak të një gabimi në kompilim. Prandaj, zhvilluesi i veçorisë B do të duhet të rregullojë kodin, të rilançojë kontrollimet, atëherë plugin automatikisht do të aplikojë kërkesën për tërheqje.
Para përdorimit të këtij plani, kishim mesatarisht 2,7 lançime kontrolli për çdo kërkesë tërheqjeje. Me pluginin, tani kemi 3,6 lançime. Kjo na përshtatet.
Është e rëndësishme të theksohet se ky plug-in ka një disavantazh: ai rikthen ndërtimin vetëm një herë. Kjo do të thotë se ende ka një dritare të vogël përmes së cilës mund të kalojnë ndryshime konfliktuale në develop. Por probabiliteti i kësaj është i ulët, dhe ne bëmë këtë kompromis midis numrit të ekzekutimeve dhe probabilitetit të dështimit. Gjatë dy viteve ndodhi vetëm njëherë, kështu që ndoshta nuk ishte kot.
Na u deshën dy javë për të shkruar versionin e parë të plug-in-it për Bitbucket.
Kontrollime të reja
Ndërkohë, ekipi ynë vazhdoi të rritej. U shtuan kontrollime të reja.
Menduam: përse të riparojmë gabimet, nëse mund t'i parandalojmë ato? Dhe prandaj implementuam analizën statike të kodit. Filluam me lint, që është pjesë e Android SDK. Por në atë kohë nuk dinte të punonte me kodin Kotlin, dhe ne kishim tashmë 75% të aplikacionit të shkruar në Kotlin. Prandaj, lint-it iu shtuan kontrollimet e Android Studio.
Për këtë na duhej të shtrembëronim shumë: të marrim Android Studio, ta paketojmë në Docker dhe ta ekzekutojmë në CI me një monitor virtual, për ta bërë ta mendojë se është hapur në një laptop të vërtetë. Por kjo funksiononte.
Gjithashtu gjatë kësaj kohe filluam të shkruajmë shumë teste instrumentation dhe implementuam testimi me skrinshot. Kjo është kur gjenerohet një skrinshot referencë për një pamje të vogël të veçantë, dhe testi përfshin që të merret një skrinshot nga pamja dhe të krahasohet me referencën piksel për piksel. Nëse ka ndonjë dallim, atëherë ndonjëherë dizajni ka devijuar ose diçka nuk është në rregull me stilet.
Por testet e instrumentimit dhe testet me skrinshot duhet të ekzekutohen në pajisje: në emulatorë ose në pajisje të vërteta. Duke marrë parasysh se ka shumë teste dhe ato ekzekutohen shpesh, nevojitet një fermë e tërë. Të krijosh fermën tënde është shumë e lodhshme, prandaj ne gjetëm një variant të gatshëm — Firebase Test Lab.
Firebase Test Lab
u zgjodh sepse Firebase është një produkt i Google, pra duhet të jetë i besueshëm dhe është e dyshimtë që do të shuhet ndonjëherë. Çmimet janë të arsyeshme: 5$ për çdo orë punë të një pajisjeje reale, 1$ për çdo orë punë të një emulatori.
Implementimi i Firebase Test Lab në CI-në tonë zuri rreth tre javë.
Por megjithatë, ekipi vazhdoi të rritet, dhe Firebase, fatkeqësisht, filloi të na japë probleme. Në atë kohë, ai nuk kishte asnjë SLA. Ndodhte që Firebase na bënte të prisnim derisa të lironte numrin e nevojshëm të pajisjeve për testim, dhe nuk nisi menjëherë siç dëshironim. Pritja në radhë zgjaste deri në gjysmë ore, që ishte tepër e gjatë. Testet e Instrumentacionit kryheshin në çdo PR, vonesat e ngadalësuan zhvillimin, dhe më pas erdhi faturimi për muajin me një shumë të madhe. Në përgjithësi, u vendos që të hiqnim dorë nga Firebase dhe të zhvillonim në shtëpi, duke qenë se ekipi kishte arritur një madhësi të mjaftueshme.
Docker + Python + bash
Morrëm docker, vendosëm në të emulatorët, shkruam një program të thjeshtë në Python, i cili në momentin e duhur aktivizon numrin e nevojshëm të emulatorëve në versionin e duhur dhe kur është e nevojshme i ndalon. Dhe, sigurisht, disa skripte bash — ku mund të shkojë pa to?
Krijimi i një mjedisi testimi të vet është marrë pesë javë.
Si rezultat, për çdo kërkesë për tërheqje kishte një listë të gjerë dhe bllokuese të kontrollit:
- Ndërtimi i ARK;
- Testet Junit;
- Lint;
- Kontrollet e Android Studio;
- Testet e Instrumentacionit;
- Testet e Screenshot.
Kjo parandaloi shumë prishje të mundshme. Teknikisht gjithçka funksiononte, por zhvilluesit ankohej se pritja për rezultatet ishte tepër e gjatë.
Sa e gjatë është 'tepër e gjatë'? Ne e eksportuam të dhënat nga Bitbucket dhe TeamCity në sistemin e analizës dhe kuptuam se koha mesatare e pritjes është 45 minuta. Pra, zhvilluesi, kur hap një pull request, mesatarisht pret rezultatet e ndërtimit për 45 minuta. Në mendimin tim, kjo është shumë, dhe nuk mund të punojmë kështu.
Sigurisht, ne vendosëm të përshpejtojmë të gjitha ndërtimet tona.
Po e përshpejtojmë
Duke parë se ndërtimet shpesh ishin në pritje, ne së pari blemë pajisje shtesë — zgjerimi intensiv është më e thjeshtë. Ndërtimet nuk ishin më në pritje, por koha e pritjes u ul vetëm pak, sepse disa kontrolle vetë zgjatnin shumë.
Eliminojmë kontrollet shumë të gjata
Integrimi ynë i vazhdueshëm mund të kapte këto lloje gabimesh dhe problem.
- Nuk po ndërtohet. CI mund të kapë një gabim kompilimi, kur për shkak të ndryshimeve konfuze diçka nuk ndërtohet. Siç e thashë, atëherë askush nuk mund të ndërtojë asgjë, zhvillimi ndalon, dhe të gjithë janë të shqetësuar.
- Gabim në sjellje. Për shembull, kur aplikacioni ndërtohet, por kur klikoni në buton bie, ose butoni nuk reagon fare. Kjo është e keqe, sepse një gabim i tillë mund të arrijë te përdoruesi.
- Gabimi në dizajn. Për shembull, butoni funksionon, por ështëzhvendosur 10 piksele në të majtë.
- Rritja e borxhit teknik.
Duke parë këtë listë, kuptuam se vetëm dy pikat e para janë kritike. Këto probleme duam t'i kapim në radhë të parë. Gabimet në dizajn zbulohet në fazën e shqyrtimit të projektit dhe atëherë janë lehtësisht të rregullueshme. Puna me borxhin teknik kërkon një proces dhe planifikim të veçantë, prandaj vendosëm të mos e kontrollonim atë në pull request.
Duke u nisur nga kjo klasifikim, ne rishikoi të gjithë listën e kontrollit. E hoqëm Lint dhe e transferuam ekzekutimin e tij për natën: thjesht që të jepte një raport për numrin e problemeve në projekt. Me borxhin teknik u dakorduam të punojmë veçmas, dhe i hoqëm krejtësisht Android Studio checks. Android Studio në Docker për të ekzekutuar inspektime tingëllon interesante, por sjell shumë probleme në mbështetje. Çdo përditësim i versioneve të Android Studio është një luftë me përfundime të paqarta. Po ashtu, ishte e vështirë të mbaheshin testet e ekranit, sepse biblioteka nuk punonte shumë stabilisht, ndodhnin goditje të gabuara. Testet e ekranit janë hequr nga lista e verifikimeve..
Në fund na mbetën:
- Ndërtimi i ARK;
- Testet Junit;
- Testet e Instrumentimit.
Cache i largët i Gradle.
Pa verifikime të rënda, çdo gjë u bë më mirë. Por nuk ka kufij për përsosmëri!
Aplikacioni ynë tashmë ishte i ndarë në rreth 150 modulet gradle. Zakonisht në një rast të tillë, cache i largët i Gradle punon mirë, prandaj vendosëm ta provojmë atë.
Cache i largët i Gradle është një shërbim që mund të ruajë artefaktet e ndërtimit për detyra të veçanta në modulet e veçanta. Gradle, në vend që të kompilonte në të vërtetë kodin, godet cache-in e largët përmes HTTP dhe pyet nëse dikush e ka kryer tashmë këtë detyrë. Nëse po, thjesht shkarkon rezultatin.
Të nisësh cache-in e largët të Gradle është e lehtë, sepse Gradle ofron një imazh Docker. Na arriti ta bënim këtë për tre orë.
Mjaftonte të nisje Docker dhe të shkruaje një rresht në projekt. Megjithatë, megjithëse mund ta fillosh shpejt, për të siguruar që gjithçka të funksionojë mirë do të kërkojë mjaft kohë.
Më poshtë është grafiku i humbjeve të cache-it.

Në fillim, përqindja e humbjeve të cache-it ishte afërsisht 65. Pas tri javësh, arritëm ta ulim këtë vlerë në 20%. Doli se detyrat që mblidhte aplikacioni Android kishin varësi tranzitive të çuditshme, për shkak të të cilave Gradle humbiste nga cache-i.
Duke lidhur cache-in, ne ndjeshëm përshpejtuam ndërtimin. Por përveç ndërtimit, po ekzekutohen gjithashtu testet e instrumentation-it, të cilat kërkojnë kohë të gjatë. Ndoshta, nuk është e nevojshme të ekzekutohen të gjitha testet për çdo pull request. Për ta zbuluar këtë, përdorim analizën e ndikimit.
Analiza e ndikimit
Në pull request, ne mbledhim git diff dhe gjejmë modulët e Gradle që janë ndryshuar.

Ka kuptim të ekzekutohen vetëm ato teste të instrumentation-it që kontrollojnë modulët e ndryshuar dhe të gjithë modulët që varen nga ata. Nuk ka kuptim të ekzekutohen testet për modulet fqinj: aty nuk ka ndryshuar kodi dhe asgjë nuk mund të prishet.
Me testet e instrumentation nuk është aq e thjeshtë, pasi ato duhet të jenë në modulin më të lartë të aplikacionit. Ne kemi aplikuar një heuristikë me analizën e byte-code për të kuptuar se cili modul i përket çdonjëri test.
Modernizimi i funksionimit të testeve të instrumentation, në mënyrë që ato të kontrollonin vetëm modulët e angazhuar, zuri rreth tetë javë.
Masat për shkurtimin e verifikimeve funksionuan me sukses. Nga 45 minuta, arritëm përafërsisht në 15. Një çerek ore për të pritur ndërtimin tani është normale.
Por tani zhvilluesit filluan të ankoheshin se nuk ishte e qartë se cilat ndërtime po ekzekutoheshin, ku të shihnin logun, pse ndërtimi ishte i kuq, cili test kishte dështuar, etj.

Problemet me informacionin e kthyer ngadalësojnë zhvillimin, prandaj ne përpiqeshim të ofronim informacionin më të kuptueshëm dhe të detajuar për çdo PR dhe ndërtim. Filluam me komente në Bitbucket për PR duke treguar se cili ndërtim kishte dështuar dhe pse, dhe dërguam mesazhe adresore në Slack. Në fund, krijuam një dashboard për faqen e PR me një listë të të gjitha ndërtimeve që po ekzekutohen aktualisht dhe gjendjen e tyre: në radhë, ekzekutohet, ka dështuar ose ka përfunduar. Mund të klikoni në ndërtim dhe të shkoni në logun e tij.

I ishte shpenzuar gjashtë javë për një feedback të detajuar.
Planet
Të kalojmë në historinë më të re. Pas zgjidhjes së çështjes së feedback-ut, arritëm në një nivel të ri — vendosëm të ndërtojmë fermën tonë të emulatoreve. Kur ka shumë teste dhe emulatore, është e vështirë të menaxhohen ato. Si rezultat, të gjithë emulatorët tanë u zhvendosën në klastrin k8s me menaxhim fleksibël të burimeve.
Përveç kësaj, ka edhe plane të tjera.
- Kthe Lint (dhe analizën e tjera statike). Ne tashmë po punojmë në këtë drejtim.
- Të ekzekutojmë të gjitha testet end-to-end në të gjitha versionet e SDK.
Pra, ndoqëm historinë e zhvillimit të Continuous Integration në Avito. Tani dua të jap disa këshilla nga këndvështrimi i një profesionisti.
Këshilla
Nëse do të mund të jap vetëm një këshillë, kjo do të ishte:
Ju lutem, kini kujdes me skriptet shell!
Bash është një mjet shumë fleksibël dhe i fuqishëm, është shumë e lehtë dhe e shpejtë për të shkruar skripte me të. Por mund të bjerë në kurthe, dhe ne, fatkeqësisht, rënë në të.
Këtu filloi gjithçka me skriptet e thjeshta, të cilat ekzekutoheshin në makinat tona të ndërtimit:
#!/usr/bin/env bash
./gradlew assembleDebugMegjithëse, siç dihet, gjithçka zhvillohet dhe komplikohet me kalimin e kohës - le të ekzekutojmë një skript nga i tjetër, le të kalojmë disa parametra aty - në fund, duhej të shkruhej një funksion që përcakton se në cilin nivel të thellësisë së bash-it ndodhemi tani për të vendosur thonjzat e nevojshëm për ta ekzekutuar.

A mund ta imagjinoni punën e domosdoshme për zhvillimin e atyre skripteve. Këshilloj të mos bini në këtë kurth.
Çfarë mund të përdoret si zëvendësim?
- Çdo gjuhë skriptimi. Të shkruash në Python ose Kotlin Script është më e përshtatshme sepse kjo është programim, jo skriptime.
- Ose të përshkruani të gjitha logjikat e ndërtimit në formën e Custom gradle tasks për projektin tuaj.
Ne vendosëm të zgjedhim variantin e dytë dhe tani po fshijmë me kujdes të gjithë skriptet bash dhe po shkruajmë shumë gradle-tasks të personalizuara.
Këshilla №2: mbani infrastrukturën në kod.
Është e përshtatshme kur konfigurimi i Continuous Integration nuk ruhet në ndërfaqen e përdoruesit të Jenkins ose TeamCity, etj., por në formën e skedarëve tekstualë direkt në repositorin e projektit. Kjo ofron versionim. Nuk do të jetë e vështirë të rikthehesh ose të ndash kodin në një degë tjetër.
Skriptet mund të ruhen në projekt. Por çfarë të bëjmë me ambientin?
Këshilla №3: Docker mund të ndihmojë me ambientin.
Për zhvilluesit Android, ai padyshim do të ndihmojë, për iOS, fatkeqësisht, ende jo.
Ky është një shembull i një docker file të thjeshtë, i cili përmban jdk dhe android-sdk:
FROM openjdk:8
ENV SDK_URL="https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip"
ANDROID_HOME="/usr/local/android-sdk"
ANDROID_VERSION=26
ANDROID_BUILD_TOOLS_VERSION=26.0.2
# Shkarko Android SDK
RUN mkdir "$ANDROID_HOME" .android
&& cd "$ANDROID_HOME"
&& curl -o sdk.zip $SDK_URL
&& unzip sdk.zip
&& rm sdk.zip
&& yes | $ANDROID_HOME/tools/bin/sdkmanager --licenses
# Instaloni Android Build Tool dhe Libraritë
RUN $ANDROID_HOME/tools/bin/sdkmanager --update
RUN $ANDROID_HOME/tools/bin/sdkmanager "build-tools;${ANDROID_BUILD_TOOLS_VERSION}"
"platforms;android-${ANDROID_VERSION}"
"platform-tools"
RUN mkdir /application
WORKDIR /application
Duke shkruar këtë docker file (do t'ju tregoj një sekret, nuk është e nevojshme ta shkruani, mund ta shkarkoni një të gatshëm nga GitHub) dhe duke ndërtuar imazhin, ju merrni një makinë virtuale, në të cilën mund të ndërtoni aplikacione dhe të ekzekutoni testet Junit.
Dy argumentet kryesore pse ka kuptim: shkallëzueshmëria dhe përsëritshmëria. Me përdorimin e Docker-it, mund të ngrini shpejt një duzinë agjentësh ndërtimi, të cilët do të kenë të njëjtën mjedis si ai i mëparshmi. Kjo lehtëson shumë jetën e inxhinierëve CI. Të vendosni android-sdk në Docker është shumë e thjeshtë, me emulatorët pak më të ndërlikuar: do t'ju duhet të bëni pak përpjekje (ose sërish të shkarkoni një projekt të gatshëm nga GitHub).
Këshilla nr. 4: mos harroni se kontrollimet bëhen jo për shkak të kontrollimeve, por për njerëzit.
Për zhvilluesit, feedback-i i shpejtë dhe, mbi të gjitha, i qartë është shumë i rëndësishëm: çfarë ka shkuar keq, cili test ka dështuar, ku mund të shihni log-un e ndërtimit.
Këshilla nr. 5: jini pragmatik në zhvillimin e Integrimit të Vazhdushëm.
Kuptoni qartë cilët lloje gabimesh dëshironi të parandaloni, sa resurse, kohë dhe kohë makinerike jeni të gatshëm të shpenzoni. Kontrollimet shumë të gjata mund, për shembull, të zhvendosen për në natë. Ndërsa nga ato që kapin gabime të parëndësishme, mund të hiqni dorë plotësisht.
Këshilla nr. 6: përdorni mjete të gatshme.
Aktualisht ka shumë kompani që ofrojnë CI në cloud.

Për ekipet e vogla, kjo është një zgjidhje e mirë. Nuk ka nevojë të mbani asgjë, thjesht paguani pak para, ngjitni aplikacionin tuaj dhe madje realizoni teste të instrumentimit.
Këshilla №7: në një ekip të madh, zgjidhjet in-house janë më të favorshme.
Mirëpo, herë pas here, me rritjen e ekipit, zgjidhjet in-house do të bëhen më të favorshme. Ka një moment me këto zgjidhje. Në ekonomi, ekziston ligji i kthimit në rënie: në çdo projekt, çdo përmirësim tjetër bëhet gjithnjë e më i vështirë, kërkon gjithnjë e më shumë investime.
Ekonomia përshkruan gjithë jetën tonë, përfshirë Integrimin e Vazhdimshëm. Kam ndërtuar një grafik të burimeve të punës për çdo fazë të zhvillimit të Integrimit tonë të Vazhdimshëm.

Është e qartë se çdo përmirësim bëhet gjithnjë e më i vështirë. Duke parë këtë grafik, mund të kuptohet se zhvillimi i Integrimit të Vazhdimshëm duhet të jetë në përputhje me rritjen e madhësisë së ekipit. Për një ekip prej dy personash, të shpenzosh 50 ditë në zhvillimin e një ferme të brendshme të simulatorëve — është ide e keqe. Por po ashtu, për një ekip të madh, të mos merresh fare me Integrimin e Vazhdimshëm — është gjithashtu një ide e keqe, sepse do të shpenzosh edhe më shumë kohë në problemet e integrimit, riparimin e komunikimeve etj.
Ne filluam duke e kuptuar se automatizimi është i nevojshëm, sepse njerëzit kushtojnë, ata gabojnë dhe janë leniv. Por automatizimin e bëjnë gjithashtu njerëzit. Prandaj, të gjitha këto probleme i përkasin edhe automatizimit.
- Automatizimi është i kushtueshëm. Kujtoni grafikët e konsumit të punës.
- Gjatë automatizimit, njerëzit gabojnë.
- Ndonjëherë është shumë leni për të automatizuar, sepse gjithçka funksionon siç duhet. Pse duhet të përmirësojmë edhe diçka tjetër, pse të gjithë ky Continuous Integration?
Por kam statistikë: në 20% të kompilimeve zbulojmë gabime. Dhe kjo nuk ndodh sepse zhvilluesit tanë shkruajnë keq kodin. Kjo ndodh sepse zhvilluesit janë të bindur se, nëse ata bëjnë ndonjë gabim, ai nuk do të shkojë në develop, do të zbulohen nga kontrollet automatike. Prandaj, zhvilluesit mund të shpenzojnë më shumë kohë në shkruarjen e kodit dhe gjërat interesante, e jo të testojnë diçka lokalisht.
Merrni me Continuous Integration. Por me masë.
Për më tepër, Nikolai Nesterov jo vetëm që bën prezantime të shkëlqyera, por është gjithashtu pjesë e komitetit organizativ dhe ndihmon të tjerët për të përgatitur prezantime informuese për ju. Plotësinë dhe dobishmërinë e programit të konferencës së ardhshme mund ta vlerësoni nga temat në . Dhe për detajet e tjera, erdhni më 22-23 Prill në Infopërmbytjen.
Burimi: habr.com
