{"id":31304,"date":"2019-10-31T21:40:34","date_gmt":"2019-10-31T18:40:34","guid":{"rendered":"https:\/\/prohoster.info\/blog\/evolyutsiya-ci-v-komande-mobilnoj-razrabotki\/"},"modified":"2019-10-31T21:40:34","modified_gmt":"2019-10-31T18:40:34","slug":"evolyutsiya-ci-v-komande-mobilnoj-razrabotki","status":"publish","type":"post","link":"https:\/\/prohoster.info\/et\/blog\/administrirovanie\/evolyutsiya-ci-v-komande-mobilnoj-razrabotki","title":{"rendered":"CI arengu evolutsioon mobiilide arendusmeeskonnas","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>T\u00e4na arendatakse enamik tarkvaratooteid meeskondades. Meeskonnat\u00f6\u00f6 edusamme saab kujutada lihtsa skeemina.<\/p>\n<p><img decoding=\"async\" alt=\"CI arengu evolutsioon mobiilide arendusmeeskonnas\" src=\"\/wp-content\/uploads\/2019\/04\/b283cfd7772d8f479be0754ebbe51b19.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nP\u00e4rast koodi kirjutamist peate veenduma, et see:<\/p>\n<ol>\n<li>T\u00f6\u00f6taks.<\/li>\n<li>Ei rikuks midagi, sealhulgas teie kolleegide kirjutatud koodi.<\/li>\n<\/ol>\n<p>\nKui m\u00f5lemad tingimused on t\u00e4idetud, siis olete edukuse teel. Kui soovite neid tingimusi h\u00f5lpsasti kontrollida ja mitte kasulikult teelt k\u00f5rvale kalduda, leiutati pidev integratsioon.<\/p>\n<p>CI on t\u00f6\u00f6protsess, kus integreerite oma koodi toote \u00fcldisesse koodi v\u00f5imalikult tihti. Ja mitte ainult integreerite, vaid ka pidevalt kontrollite, et k\u00f5ik t\u00f6\u00f6taks. Kuna tuleb palju ja tihti kontrollida, tasub m\u00f5elda automatiseerimise peale. K\u00f5ike saab manuaalselt kontrollida, kuid see ei ole tark, ja siin on miks.<br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<ul>\n<li><strong>Inimesed on kallid<\/strong>. \u00dche programmeerija t\u00f6\u00f6 tund maksab rohkem kui \u00fche serveri t\u00f6\u00f6 tund.<\/li>\n<li><strong>Inimesed eksivad<\/strong>. Seet\u00f5ttu v\u00f5ivad tekkida olukorrad, kui testid k\u00e4ivitatakse vale haru peal v\u00f5i koostatakse vale kommity testijate jaoks.<\/li>\n<li><strong>Inimesed on laisad<\/strong>. Perioodiliselt, kui l\u00f5petan m\u00f5ne \u00fclesande, tuleb mul p\u00e4he m\u00f5te: \u201eMida siin kontrollida? Kirjutasin kaks rida \u2014 kindlasti t\u00f6\u00f6tab k\u00f5ik!\u201d Arvan, et m\u00f5nel teist tulevad sellised m\u00f5tted vahel ka meelde. Kuid kontrollida tuleb alati.<\/li>\n<\/ul>\n<p>\nKuidas Rakendati ja Arendati Pidevat Integratsiooni Avito mobiiliarenduse meeskonnas, kuidas j\u00f5uti nullist 450 kogumiseni p\u00e4evas ja mida kogumismasinad iga p\u00e4ev 200 tunni eest teevad, r\u00e4\u00e4gib Nikolai Nesterov (<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/users\/nnesterov\/\" class=\"user_link\">nnesterov<\/a><\/noindex>) \u2014 osaleja k\u00f5igis CI\/CD Android-rakenduse evolutsioonimuutustes.<\/p>\n<p>Lugu on \u00fcles ehitatud Android meeskonna n\u00e4itel, kuid enamik l\u00e4henemisi on rakendatavad ka iOS-is.<\/p>\n<p><center><div class=\"youtube-placeholder\" data-id=\"lz8MNATTUCU\" onclick=\"loadVideo(this)\">\r\n        <img decoding=\"async\" src=\"https:\/\/img.youtube.com\/vi\/lz8MNATTUCU\/hqdefault.jpg\" alt=\"M\u00e4ngi videot\" loading=\"lazy\" width=\"480\" height=\"360\" style=\"width:100%;height:auto;\">\r\n        <div class=\"play-button\"><\/div>\r\n    <\/div><\/center><br \/>\nKaua aega tagasi t\u00f6\u00f6tas Avito Android meeskonnas \u00fcks inimene. Talle ei olnud definitsiooni j\u00e4rgi pidev integratsioon vajalik: ei olnud kellegagi integreerida.<\/p>\n<p>Kuid rakendus kasvas, ilmus \u00fcha rohkem uusi \u00fclesandeid ja vastavalt suurenes meeskond. \u00dchel hetkel tuli aeg koodi integreerimise protsess formaalsemaks muuta. Otsustati kasutada Git flow'd.<\/p>\n<p><img decoding=\"async\" alt=\"CI arengu evolutsioon mobiilide arendusmeeskonnas\" src=\"\/wp-content\/uploads\/2019\/04\/f433effc5ab5a59de3d6bd20a7a87057.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nGit flow kontseptsioon on teada: projektis on \u00fcks \u00fchine arendusbranch, ja iga uue funktsiooni jaoks l\u00f5ikavad arendajad eraldi haru, komiteerivad sellesse, t\u00f5ukavad seda ja kui nad tahavad oma koodi arendusbranchi \u00fchendada, avavad nad pull requesti. Teadmiste vahetamiseks ja l\u00e4henemiste arutamiseks oleme sisse viinud koodide \u00fclevaate, kus kolleegid peavad \u00fcksteise koodi kontrollima ja kinnitama.<\/p>\n<h2>Kontrollid<\/h2>\n<p>\nKoodi vaatamine on \u00e4ge, kuid sellega ei piisa. Seet\u00f5ttu kehtestatakse automaatsed kontrollid.<\/p>\n<ul>\n<li>Esmalt kontrollime <strong>ARKi kogumist<\/strong>.<\/li>\n<li>Palju <strong>Junit teste<\/strong>.<\/li>\n<li><strong>Arvutame koodi katvuse<\/strong>, kuna k\u00e4ivitame testid.<\/li>\n<\/ul>\n<p>\nEt aru saada, kuidas neid kontrolle k\u00e4ivitada, vaatame arendusprotsessi Avitos.<\/p>\n<p>Skeemiliselt saab seda ette kujutada nii:<\/p>\n<ul>\n<li>Arendaja kirjutab koodi oma s\u00fclearvutis. Integration checke saab k\u00e4ivitada otse siin \u2014 kas commit hook'iga v\u00f5i lihtsalt testide taustal k\u00e4itamisega.<\/li>\n<li>P\u00e4rast seda, kui arendaja on koodi t\u00f5uganud, avab ta pull requesti. Selleks, et tema kood p\u00e4\u00e4seks arendusbranchi, peab olema l\u00e4bi viidud koodide \u00fclevaade ja vajalik arv kinnitusi. Siia on v\u00f5imalik \u00fchendata kontrollid ja build'id: kuni k\u00f5ik build'id ei ole edukad, ei saa pull requesti \u00fchendada.<\/li>\n<li>P\u00e4rast seda, kui pull request on \u00fchendatud ja kood on l\u00e4inud arendusbranchi, saab valida sobiva aja: n\u00e4iteks \u00f6\u00f6sel, kui k\u00f5ik serverid on vabad, ja k\u00e4ivitada teste nii palju kui v\u00f5imalik.<\/li>\n<\/ul>\n<p>\nKontrollide k\u00e4itamine oma s\u00fclearvutis ei meeldinud kellelegi. Kui arendaja on funktsiooni l\u00f5petanud, tahab ta selle kiirelt t\u00f5ukama ja avada pull requesti. Kui sel hetkel k\u00e4ivitatakse pikad kontrollid, pole see mitte ainult ebamugav, vaid aeglustab ka arendust: kuni s\u00fclearvuti kontrollib, ei saa seal normaalselt t\u00f6\u00f6tada.<\/p>\n<p>\u00d6\u00f6sel kontrolle k\u00e4ivitada meeldis meile v\u00e4ga, kuna aega ja servere on palju, saab ennast v\u00e4lja elada. Kuid kahjuks, kui funktsiooni kood oli l\u00e4inud arendusbranchi, oli arendajal juba palju v\u00e4hem motivatsiooni parandada vigu, mida CI leidis. Ma vahepeal p\u00fc\u00fcdsin end korduvalt selle m\u00f5ttega, kui vaatasin hommikusel aruandel k\u00f5ik leitud vead, et parandan need kunagi hiljem, kuna praegu on Jira's \u00e4ge uus \u00fclesanne, mida tahaks alustada.<\/p>\n<p>Kui kontrollid blokeerivad pull requesti, on motivatsiooni piisavalt, sest seni, kuni build'id ei ole edukad, ei p\u00e4\u00e4se kood arendusbranchi, ja seega \u00fclesanne ei saa olema l\u00f5petatud.<\/p>\n<p>L\u00f5puks valisime sellise strateegia: \u00f6\u00f6sel jooksutame maksimaalset kontrollide kogumit, samas kui k\u00f5ige kriitilisemad ja, mis k\u00f5ige t\u00e4htsam, kiiremad neist k\u00e4ivitame pull request'i raames. Kuid sellega ei piirdunud \u2014 samaaegselt optimeerime kontrollide l\u00e4bimise kiirus, et viia need \u00f6isest re\u017eiimist pull request'i kontrollideks.<\/p>\n<p>Sel hetkel l\u00e4bisid k\u00f5ik meie ehitused piisavalt kiiresti, seet\u00f5ttu l\u00fclitasime lihtsalt pull request'i blokeerijana sisse ARK'i ehituse, Junit-testid ja code coverage'i arvutamise. L\u00fclitasime sisse, m\u00f5tlesime \u2014 ja loobusime code coverage'ist, kuna arvutasime, et see ei ole meile vajalik.<\/p>\n<p><strong><em>Kogu p\u00f5hilise CI seadistamise jaoks l\u00e4ks meil kaks p\u00e4eva (siin ja edaspidi on ajahinnang umbkaudne, vajalik mastaabi m\u00f5istmiseks). <\/em><\/strong><\/p>\n<p>P\u00e4rast seda hakkasime edasi m\u00f5tlema \u2014 kas me kontrollime \u00f5igesti? Kas me k\u00e4ivitame ehitused pull request'i raames \u00f5igesti?<\/p>\n<p>Me k\u00e4ivitasime ehituse viimase commit'i peal haru, millest pull request avati. Kuid selle commit'i kontrollid v\u00f5ivad n\u00e4idata ainult seda, et arendaja kirjutatud kood t\u00f6\u00f6tab. Kuid nad ei t\u00f5esta, et ta ei ole midagi rikkunud. Tegelikult tuleb kontrollida develop haru seisundit p\u00e4rast seda, kui feature on sellesse lisatud.<\/p>\n<p><img decoding=\"async\" alt=\"CI arengu evolutsioon mobiilide arendusmeeskonnas\" src=\"\/wp-content\/uploads\/2019\/04\/c1a179b68b02c04031177f9bf51a81ed.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nSelleks kirjutasime lihtsa bash-skripti <strong>premerge.sh:<\/strong><\/p>\n<pre><code class=\"bash\">#!\/usr\/bin\/env bash\n\nset -e\n\ngit fetch origin develop\n\ngit merge origin\/develop<\/code><\/pre>\n<p>\nSiin t\u00f5mmatakse lihtsalt k\u00f5ik uusimad muudatused develop'ist ja lisatakse praegusse haru. Lisame skripti premerge.sh k\u00f5igi ehituste esimeseks sammuks ning hakkame kontrollima t\u00e4pselt seda, mida soovime, ehk siis <strong>integreerimist<\/strong>.<\/p>\n<p><strong><em>Probleemide lokaliseerimise, lahenduse leidmise ja selle skripti kirjutamiseks l\u00e4ks kolm p\u00e4eva.<\/em><\/strong><\/p>\n<p>Rakendus arenes, \u00fclesandeid ilmus \u00fcha rohkem, meeskond kasvas ja premerge.sh hakkas meid m\u00f5nikord alt vedama. Develop'isse imbusid konflikti muutused, mis rikkusid ehitust.<\/p>\n<p>N\u00e4ide sellest, kuidas see juhtub:<\/p>\n<p><img decoding=\"async\" alt=\"CI arengu evolutsioon mobiilide arendusmeeskonnas\" src=\"\/wp-content\/uploads\/2019\/04\/6762d9ce4e431549455c49f2317a8f20.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nKaks arendajat alustavad samal ajal feature'ite A ja B loomist. Arendaja feature'ist A leiab projektis kasutamata funktsiooni <code>answer()<\/code> ja, nagu hea skaut, kustutab selle. Samal ajal lisab arendaja feature'ist B oma harusse uue kutsungi sellele funktsioonile.<\/p>\n<p>Arendajad l\u00f5petavad oma t\u00f6\u00f6 ja avavad samal ajal pull request'i. Ehitus k\u00e4ivitub, premerge.sh kontrollib m\u00f5lemat pull request'i v\u00e4rske develop'i seisundi suhtes \u2014 k\u00f5ik kontrollid on rohelised. P\u00e4rast seda liidetakse feature A pull request, seej\u00e4rel liidetakse feature B pull request... Pomm! Develop puruneb, kuna develop'i koodis on kutsung mitteolevale funktsioonile.<\/p>\n<p><img decoding=\"async\" alt=\"CI arengu evolutsioon mobiilide arendusmeeskonnas\" src=\"\/wp-content\/uploads\/2019\/04\/0261dcc9f7f2e5e018ab136081b4679b.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nKui develop ei kogune, siis see <strong>kohalik katastroof<\/strong>. Kogu meeskond ei suuda midagi kokku koguda ja testimiseks anda.<\/p>\n<p>Nii juhtus, et ma tegelesin k\u00f5ige sagedamini infrastruktuuri\u00fclesannetega: anal\u00fc\u00fcs, v\u00f5rk, andmebaasid. Just mina kirjutasin need funktsioonid ja klassid, mida teised arendajad kasutavad. Selle t\u00f5ttu sattusin tihti sarnastesse olukordadesse. Mul oli isegi aega, mil selline pilt seisis.<\/p>\n<p><img decoding=\"async\" alt=\"CI arengu evolutsioon mobiilide arendusmeeskonnas\" src=\"\/wp-content\/uploads\/2019\/04\/0eb1aadd05b33ce995027ab52d5c1e53.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nKuna see meid ei rahuldanud, hakkasime v\u00e4lja t\u00f6\u00f6tama variantide, kuidas seda ennetada.<\/p>\n<h2>Kuidas mitte developit l\u00f5hkuda<\/h2>\n<p>\nEsimene variant: <strong>k\u00f5ik pull requestid uuendamisel uuesti kokku panna. <\/strong>Kui meie n\u00e4ites pull request funktsiooniga A esimene j\u00f5uab developisse, siis pull request funktsiooniga B pannakse uuesti kokku ja seega ei l\u00e4bene kontrollide t\u00f5ttu kompileerimisviga.<\/p>\n<p>Kuna aru saada, kui palju aega see v\u00f5tab, vaatame kahe PR n\u00e4idet. Avame kaks PR: kaks ehitust, kaks kontrolli k\u00e4ivitamist. P\u00e4rast seda, kui esimene PR on developisse sulandunud, tuleb teine uuesti kokku panna. Kokku kulub kahe PR jaoks kolme kontrolli k\u00e4ivitamist: 2 + 1 = 3.<\/p>\n<p>P\u00f5him\u00f5tteliselt pole paha. Aga me vaatasime statistikat ja meie meeskonnas oli t\u00fc\u00fcpiliseks olukorraks 10 avatud PR-i, ja siis on kontrollide arv: aritmeetilise progresseerumise summa: 10 + 9 +\u2026 + 1 = 55. See t\u00e4hendab, et 10 PR-i aktsepteerimiseks tuleb uuesti kokku panna 55 korda. Ja see on ideaalsetes tingimustes, kui k\u00f5ik kontrollid l\u00e4hevad esimesel korral l\u00e4bi, ja keegi ei ava t\u00e4iendavat pull requesti, kuni see k\u00fcmme t\u00f6\u00f6deldakse.<\/p>\n<p>Kujutage ette end arendajana, kellele tuleb pingutada, et vajutada nuppu 'merge' esimesena, sest kui seda teeb naaber, tuleb oodata, kuni k\u00f5ik koosteprotsessid uuesti l\u00e4hevad... Ei, nii ei saa, see aeglustab t\u00f5siselt arendust.<\/p>\n<p>Teine v\u00f5imalik viis: <strong>koguda pull request p\u00e4rast koodivaatust. <\/strong>See t\u00e4hendab, et avate pull requesti, kogute vajalik arv kinnitusi kolleegidelt, parandate, mis on vajalik, ja seej\u00e4rel k\u00e4itate ehitused. Kui need on edukad, liidetakse pull request developiga. Sel juhul pole t\u00e4iendavaid uuesti k\u00e4ivitamisi, kuid tagasiside aeglustub oluliselt. Mina arendajana tahan pull requesti avades kohe n\u00e4ha, kas see koostatakse. N\u00e4iteks, kui m\u00f5ni test kukub, tuleb see kiiresti parandada. Edasil\u00fckatud kooste puhul aeglustub tagasiside, ja seega kogu arendus. See ei rahuldanud ka meid.<\/p>\n<p>Kokkuv\u00f5ttes j\u00e4i ainult kolmas variant \u2014 <strong>ratast leiutada<\/strong>Kogu meie kood ja k\u00f5ik meie l\u00e4htekoodid on salvestatud Bitbucket-serveri hoidlas. Seega pidime arendama Bitbucketile pistikprogrammi.<\/p>\n<p><img decoding=\"async\" alt=\"CI arengu evolutsioon mobiilide arendusmeeskonnas\" src=\"\/wp-content\/uploads\/2019\/04\/5e4b1f770ea1a3449b616c3101640294.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nSee plugin \u00fcletab pull requestide \u00fchinemisprotsessi. Protsess algab tavap\u00e4raselt: avatakse PR, k\u00e4ivitatakse k\u00f5ik ehitused ja tehakse koodiarvustus. Kuid p\u00e4rast seda, kui koodiarvustus on l\u00e4bitud ja arendaja otsustab vajutada \"merge\", kontrollib plugin, millise develop'i oleku alusel testid jooksid. Kui p\u00e4rast ehitusi on develop uuendatud, ei luba plugin sellist pull requesti peamise haru lisamist. Selle asemel k\u00e4ivitab ta ehitused uue develop'i alusel.<\/p>\n<p><img decoding=\"async\" alt=\"CI arengu evolutsioon mobiilide arendusmeeskonnas\" src=\"\/wp-content\/uploads\/2019\/04\/0edee880e1f2d286a0c64033103ac136.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nMeie konfliktsete muudatuste n\u00e4ites ei l\u00e4binud sellised ehitused kompileerimisvea t\u00f5ttu. Seega peab funktsiooni B arendaja koodi parandama, kontrollid uuesti k\u00e4ivitama, siis rakendab pistikprogramm automaatselt pull requesti.<\/p>\n<p>Enne selle pistikprogrammi rakendamist oli meil keskmiselt 2,7 kontrolli k\u00e4ivitamist \u00fche pull requesti kohta. Pistikprogrammi kasutamise j\u00e4rel on see kasvanud 3,6 kontrolli k\u00e4ivitamiseni. See rahuldab meid.<\/p>\n<p>Tasub m\u00e4rkida, et sellel pistikprogrammil on puudus: see k\u00e4ivitab ehituse ainult \u00fcks kord. See t\u00e4hendab, et j\u00e4\u00e4b v\u00e4ike aken, mille kaudu v\u00f5ivad konfliktse muudatusega arendusse sattuda. Kuid selle t\u00f5en\u00e4osus on v\u00e4ike ning me lubasime endale selle kompromissi k\u00e4ivituste arvu ja t\u00f5rgete t\u00f5en\u00e4osuse vahel. Kahe aasta jooksul juhtus see vaid \u00fcks kord, seega arvatavasti on see \u00f5igustatud.<\/p>\n<p><strong><em>Bitbucket'i pistikprogrammi esimese versiooni kirjutamiseks kulus meil kaks n\u00e4dalat. <\/em><\/strong><\/p>\n<h3>Uued kontrollid<\/h3>\n<p>\nSamaanl ajal kasvas meie meeskond. Uusi kontrolli lisandusid.<\/p>\n<p>M\u00f5tlesime: miks parandada vigu, kui neid saab ennetada? Seet\u00f5ttu rakendasime <strong>staatilise koodi anal\u00fc\u00fcsi<\/strong>. Alustasime lint'ist, mis on osa Android SDK-st. Kuid see ei osanud toona \u00fcldse t\u00f6\u00f6tada Kotlin'i koodiga, samas kui meie rakendusest oli juba 75% kirjutatud Kotlin-is. Seega lisasime lint'ile sisse ehitatud <strong>Android Studio kontrollid.<\/strong><\/p>\n<p>Selle jaoks pidime t\u00f5eliselt vaeva n\u00e4gema: v\u00f5tma Android Studio, pakendama selle Dockerisse ja k\u00e4ivitama CI's virtuaalse monitoriga, et see arvaks, et see k\u00e4ivitatakse reaalses s\u00fclearvutis. Kuid see toimis.<\/p>\n<p>Samuti hakkasime sel ajal kirjutama palju <strong>instrumentatsiooniteste<\/strong> ja rakendasime <strong>ekraanipildistamise testimist.<\/strong>See, kui genereeritakse referentskuva eraldi v\u00e4ikese vaate jaoks, ja test seisneb selles, et antud vaates v\u00f5etakse kuvat\u00f5mmis ja v\u00f5rreldakse seda referentsiga otse piksliti. Kui on erinevus, siis kuskil on skeem vale v\u00f5i on stiilides midagi valesti.<\/p>\n<p>Kuid instrumentatsiooni testid ja kuvat\u00f5mmise testid tuleb k\u00e4ivitada seadmetes: emulaatorites v\u00f5i reaalses seadmes. Arvestades, et teste on palju ja neid k\u00e4itatakse tihti, on vajalik terve farm. Oma farmi loomine oleks liiga t\u00f6\u00f6mahukas, seet\u00f5ttu leidsime alternatiivi \u2014 Firebase Test Lab.<\/p>\n<h3>Firebase Test Lab<\/h3>\n<p>\nValiti, sest Firebase on Google'i toode, mis t\u00e4hendab, et see peaks olema usaldusv\u00e4\u00e4rne ja ei sure t\u00f5en\u00e4oliselt kunagi v\u00e4lja. Hind on taskukohane: 5 $ tunnis reaalse seadme kasutamise eest, 1 $ tunnis emulaatori kasutamise eest.<\/p>\n<p><strong><em>Firebase Test Labi rakendamiseks meie CI-s kulus umbes kolm n\u00e4dalat.<\/em><\/strong><\/p>\n<p>Kuid meeskond j\u00e4tkas kasvu ning Firebase hakkas meid kahjuks alt vedama. Sel ajal puudus sellel mistahes SLA. M\u00f5nikord pidi Firebase ootama, kuni vajalik arv seadmeid teste jaoks on vabastatud, selle asemel, et hakata teste kohe k\u00e4ivitama, nagu me soovisime. Ooteaeg oli kuni pool tundi, mis on v\u00e4ga pikk. Instrumentatsiooni teste k\u00e4idi iga PR-i puhul ning viivitused aeglustasid arendust, seej\u00e4rel saime arve kuu kohta, kust tuli ringisumma. \u00dches\u00f5naga, otsustasime Firebase'ist loobuda ja teha seda ise, kuna meeskond oli piisavalt kasvanud.<\/p>\n<h3>Docker + Python + bash<\/h3>\n<p>\nV\u00f5tsime Docker'i, panime emulaatorid sisse, kirjutasime lihtsa programmi Pythonis, mis vajaliku hetke saabudes k\u00e4ivitab vajalikul arvu emulaatoreid vajalikus versioonis ja kui vaja, l\u00f5petab need. Ja muidugi paar bash-skripti \u2014 need on h\u00e4davajalikud.<\/p>\n<p><strong><em>Oma testimiskeskkonna loomisele kulus viis n\u00e4dalat.<\/em><\/strong><\/p>\n<p>Kokkuv\u00f5ttes tuli igale pull requestile ulatuslik, liitumist blokeeriv kontrollide loetelu:<\/p>\n<ul>\n<li>ARK-i kogumine;<\/li>\n<li>Junit-testid;<\/li>\n<li>Lint;<\/li>\n<li>Android Studio kontrollid;<\/li>\n<li>Instrumentatsiooni testid;<\/li>\n<li>Kuvat\u00f5mmise testid.<\/li>\n<\/ul>\n<p>\nSee takistas palju v\u00f5imalikke rikkeid. Tehniliselt t\u00f6\u00f6tab k\u00f5ik, kuid arendajad kurtsid, et tulemusi oodati liiga kaua.<\/p>\n<p>Liiga kaua \u2014 kui kaua? Me eksportisime andmed Bitbucket'ist ja TeamCity'st anal\u00fc\u00fcsimisse ja m\u00f5istsime, et <strong>keskmine ooteaeg on 45 minutit<\/strong>. See t\u00e4hendab, et arendaja ootab, et avades pull request, keskmiselt 45 minutit tulemusi. Minu arvates on see v\u00e4ga palju ja nii ei saa t\u00f6\u00f6tada.<\/p>\n<p>Muidugi, me otsustasime kiirendada k\u00f5iki meie ehitusi.<\/p>\n<h2>Kiirendame<\/h2>\n<p>\nKui n\u00e4gime, et ehitused seisavad sageli j\u00e4rjekorras, siis esimesena <strong>ostsime juurde riistvara<\/strong> \u2014 ulatuslik areng on k\u00f5ige lihtsam. Ehitused ei seisa enam j\u00e4rjekorras, kuid ooteaeg v\u00e4henes vaid veidi, kuna m\u00f5ned kontrollimised kestavad endiselt liiga kaua.<\/p>\n<h3>Eemaldame liiga pikad kontrollid<\/h3>\n<p>\nMeie pidev integratsioon suudab tuvastada selliseid vigu ja probleeme.<\/p>\n<ul>\n<li><strong>Ei kogu<\/strong>. CI suudab leida kompileerimisviga, kui konfliktsete muudatuste t\u00f5ttu midagi ei kogu. Nagu ma juba \u00fctlesin, sel juhul ei saa keegi midagi koguda, arendamine seiskub ja k\u00f5ik on n\u00e4rvis.<\/li>\n<li><strong>Viga k\u00e4itumises<\/strong>. N\u00e4iteks, kui rakendus kogub, kuid nuppu vajutades kukub kokku v\u00f5i nupp ei reageeri \u00fcldse. See on halb, sest selline viga v\u00f5ib kasutajani j\u00f5uda.<\/li>\n<li><strong>Viga kujunduses<\/strong>. N\u00e4iteks, nupp reageerib, kuid on 10 pikslit vasakule nihkunud.<\/li>\n<li><strong>Tehnilise v\u00f5la suurenemine<\/strong>.<\/li>\n<\/ul>\n<p>\nVaadates seda nimekirja, m\u00f5istsime, et kriitilised on ainult esimesed kaks punkti. Selliseid probleeme tahame me esmaj\u00e4rjekorras avastada. Kujunduse \u00fclevaate etapis avastatakse kujunduse vead ja neid on lihtne parandada. Tehnilise v\u00f5laga tegelemine n\u00f5uab eraldi protsessi ja planeerimist, seet\u00f5ttu otsustasime seda pull request'is mitte kontrollida.<\/p>\n<p>Selle klassifitseerimise p\u00f5hjal l\u00e4bisime kogu kontrollide nimekirja. <strong>T\u00f5mbasime Linti l\u00e4bi<\/strong> ja edastasime selle k\u00e4ivitamise \u00f6\u00f6sse: lihtsalt, et see annaks aru, kui palju probleeme projektis on. Tehnilise v\u00f5laga oleme kokku leppinud, et t\u00f6\u00f6tame eraldi, ning <strong>Android Studio kontrollidest loobusime t\u00e4ielikult<\/strong>. Android Studio Dockeris kontrollide k\u00e4ivitamine k\u00f5lab huvitavalt, kuid toob palju ebameeldivusi toetuses. Iga Android Studio versiooni uuendamine - see on v\u00f5itlus arusaamatute vigadega. Samuti oli raske toetada ekraanikuva teste, kuna biblioteka ei t\u00f6\u00f6tanud v\u00e4ga stabiilselt, esines valeh\u00e4ireid. <strong>Ekraanikuva teste eemaldati kontrollide nimekirjast<\/strong>.<\/p>\n<p>Kokku oli meil j\u00e4\u00e4nud:<\/p>\n<ul>\n<li>ARK-i kogumine;<\/li>\n<li>Junit-testid;<\/li>\n<li>Instrumentatsiooni testid.<\/li>\n<\/ul>\n<h3>Gradle kaugvahend<\/h3>\n<p>\nIlma raskete kontrollideta on k\u00f5ik parem. Kuid t\u00e4iuslikkusel ei ole piire!<\/p>\n<p>Meie rakendus oli juba jagatud umbes 150 gradle mooduliks. T\u00fc\u00fcpiliselt toimib sellisel juhul Gradle kaugvahend h\u00e4sti ja otsustasime seda proovida.<\/p>\n<p>Gradle remote cache on teenus, mis suudab vahem\u00e4lu hoida ehitusartefakte eraldi \u00fclesannete jaoks erinevates moodulites. Gradle, selle asemel et koodi tegelikult kompileerida, k\u00fcsib remote cache'ist HTTP kaudu, kas keegi on juba seda \u00fclesannet t\u00e4itnud. Kui jah, laadib ta lihtsalt tulemuse alla.<\/p>\n<p><strong><em>Gradle remote cache'i k\u00e4ivitamine on lihtne, sest Gradle pakub Docker'i kujundust. Suutsime selle teostada kolme tunni jooksul.<\/em><\/strong><\/p>\n<p>Vaja oli lihtsalt k\u00e4ivitada Docker ja kirjutada projektis \u00fcks rida. Kuigi see saab kiiresti k\u00e4ivituda, et k\u00f5ik h\u00e4sti t\u00f6\u00f6taks, on vajalikul ajakulul \u00fcsna palju aega.<\/p>\n<p>Allpool on vahekaart cache misses.<\/p>\n<p><img decoding=\"async\" alt=\"CI arengu evolutsioon mobiilide arendusmeeskonnas\" src=\"\/wp-content\/uploads\/2019\/04\/458ef23b5506b04f3bd385be0c18607a.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nAlguses oli cache'ist m\u00f6\u00f6dalaskmise protsent umbes 65. kolme n\u00e4dala p\u00e4rast suutsime selle v\u00e4\u00e4rtuse langetada 20%-ni. Selgus, et Android rakenduse \u00fclesannete kogumine sisaldab kummalisi transitiivseid s\u00f5ltuvusi, mille t\u00f5ttu Gradle j\u00e4i vahem\u00e4lu m\u00f6\u00f6da.<\/p>\n<p>Cache'i kasutuselev\u00f5tuga kiirus kasvas m\u00e4rkimisv\u00e4\u00e4rselt. Kuid peale ehituse k\u00e4ivitatakse ka instrumentatsiooni teste, mis v\u00f5ivad v\u00f5tta kaua aega. V\u00f5ib-olla ei ole m\u00f5tet k\u00f5iki testide kontrollida iga pull request'i korral. Selle v\u00e4lja selgitamiseks kasutame m\u00f5jude anal\u00fc\u00fcsi.<\/p>\n<h3>M\u00f5jude anal\u00fc\u00fcs<\/h3>\n<p>\nPull request'i korral kogume git diff'i ja leiame muudetud Gradle moodulid.<\/p>\n<p><img decoding=\"async\" alt=\"CI arengu evolutsioon mobiilide arendusmeeskonnas\" src=\"\/wp-content\/uploads\/2019\/04\/471ca37206a0da4d5741c8ee1dbb7f03.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nM\u00f5istlik on k\u00e4ivitada ainult need instrumentatsiooni testid, mis kontrollivad muudetud mooduleid ja k\u00f5iki mooduleid, mis neist s\u00f5ltuvad. Naaber moodulite testide k\u00e4itamine ei ole m\u00f5ttekas: seal kood ei muutunud ja midagi ei tohi puruneda.<\/p>\n<p>Instrumentatsiooni testide puhul ei ole k\u00f5ik nii lihtne, kuna need peavad olema rakenduse k\u00f5ige k\u00f5rgemal tasemel moodulis. Kasutasime bait-koodi anal\u00fc\u00fcsile p\u00f5hinevat heuristikat, et m\u00f5ista, millise mooduliga iga test on seotud.<\/p>\n<p><strong><em>Instrumentatsiooni testide t\u00f6\u00f6 moderniseerimine, et nad kontrolliksid ainult kaasatud mooduleid, v\u00f5ttis aega umbes kaheksa n\u00e4dalat.<\/em><\/strong><\/p>\n<p>Kontrollimise kiirendamise meetmed toimisid edukalt. 45 minutist j\u00f5udsime umbes 15 minutini. Veerand tundi ootamine build'is on n\u00fc\u00fcd normaalne.<\/p>\n<p>Aga n\u00fc\u00fcd on arendajad hakanud kurtma, et neile ei ole selge, millised build'id k\u00e4ivitatakse, kust logsid vaadata, miks build on punane, milline test kukkus ja nii edasi.<\/p>\n<p><img decoding=\"async\" alt=\"CI arengu evolutsioon mobiilide arendusmeeskonnas\" src=\"\/wp-content\/uploads\/2019\/04\/aa76151174cd4e5a45ff281818e312f5.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nTagasiside probleemid aeglustavad arendust, seega oleme p\u00fc\u00fcdnud tagada v\u00f5imalikult arusaadava ja \u00fcksikasjaliku teabe iga PR ja buildi kohta. Alustasime Bitbucketis PR kommentaaridega, m\u00e4rkides, milline build eba\u00f5nnestus ja miks, ning saatsime adresseeritud s\u00f5numeid Slackis. L\u00f5puks l\u00f5ime PR-i juhtpaneeli lehe, kus on k\u00f5igi praegu k\u00e4imasolevate buildide nimekiri ja nende olek: ootel, k\u00e4imas, eba\u00f5nnestunud v\u00f5i l\u00f5petatud. Saate buildile kl\u00f5psata ja p\u00e4\u00e4seda selle logisse.<\/p>\n<p><img decoding=\"async\" alt=\"CI arengu evolutsioon mobiilide arendusmeeskonnas\" src=\"\/wp-content\/uploads\/2019\/04\/2d16b7a6b6d23e23903d291ef1526999.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\n<strong><em>\u00dcksikasjalikule tagasisidele kulus kuus n\u00e4dalat.<\/em><\/strong><\/p>\n<h2>Plaane<\/h2>\n<p>\nLiigume edasi viimastele uudistele. Tagasiside probleemide lahendamisega t\u00f5stsime oma tegevuse uuele tasemele \u2014 otsustasime ehitada oma emulaatorite farmi. Kui teste ja emulaatoreid on palju, on nende haldamine keeruline. L\u00f5puks kolisid k\u00f5ik meie emulaatorid k8s-klassi, kus ressursside haldamine on paindlik.<\/p>\n<p>Lisaks on veel muid plaane.<\/p>\n<ul>\n<li><strong>Tagasi Linti<\/strong> (ja muude staatiliste anal\u00fc\u00fcside). Me juba t\u00f6\u00f6tame selle suunas.<\/li>\n<li>K\u00e4ivitage PR-i blokeerija k\u00f5ik <strong>end-to-end testid<\/strong> k\u00f5ikide SDK versioonide peal.<\/li>\n<\/ul>\n<p>\nNii et me oleme j\u00e4lginud Continuous Integrationi arengu ajalugu Avitos. N\u00fc\u00fcd tahan anda m\u00f5ned n\u00e4pun\u00e4ited kogenud vaatenurgast.<\/p>\n<h1>N\u00f5uanded<\/h1>\n<p>\nKui ma saaksin anda ainult \u00fche n\u00f5uande, siis see oleks:<\/p>\n<blockquote><p>Palun olge ettevaatlikud shell-skriptidega!<\/p><\/blockquote>\n<p>\nBash on v\u00e4ga paindlik ja v\u00f5imas t\u00f6\u00f6riist, selle abil on mugav ja kiire skripte kirjutada. Kuid sellega v\u00f5ib sattuda l\u00f5ksu, ja me, kahjuks, sattusime sellesse.<\/p>\n<p>K\u00f5ik algas lihtsatest skriptidest, mis k\u00e4ivitati meie build-masinal:<\/p>\n<pre><code class=\"bash\">#!\/usr\/bin\/env bash\n.\/gradlew assembleDebug<\/code><\/pre>\n<p>\nKuid nagu teada, k\u00f5ik areneb ja keerukamaks ajaga \u2014 laseme \u00fchel skriptil teisest k\u00e4ivituda, laseme sinna m\u00f5ned parameetrid \u2014 l\u00f5puks tuli kirjutada funktsioon, mis m\u00e4\u00e4rab, millisel bashi sisetase me praegu oleme, et sobitada \u00f5iged jutum\u00e4rgid, nii et see k\u00f5ik k\u00e4ivituks.<\/p>\n<p><img decoding=\"async\" alt=\"CI arengu evolutsioon mobiilide arendusmeeskonnas\" src=\"\/wp-content\/uploads\/2019\/04\/244a574f7b5d3b4fc77cfc4ccda2db69.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nV\u00f5ite ette kujutada, kui palju ressursse kulus selliste skriptide arendamisele. Soovitan mitte siseneda sellesse l\u00f5ksu.<\/p>\n<p>Mida v\u00f5iks selle asemel kasutada?<\/p>\n<ul>\n<li>Mitsugust skriptimiskeelt. Kirjutamine <strong>Pythonis v\u00f5i Kotlin Scriptis<\/strong> on mugavam, sest see on programmeerimine, mitte skriptid.<\/li>\n<li>V\u00f5i kirjeldada kogu buildide loogikat <strong>Custom gradle \u00fclesannetena<\/strong> oma projekti jaoks.<\/li>\n<\/ul>\n<p>\nOtsustasime valida teise variandi ja praegu eemaldame j\u00e4rk-j\u00e4rgult k\u00f5ik bash-skriptid ning kirjutame palju kohandatud gradle-\u00fclesandeid.<\/p>\n<p><strong>N\u00f5uanne nr 2: hoidke infrastruktuuri koodis.<\/strong><\/p>\n<p>Mugav on, kui Continuous Integration seadistus ei hoiu UI-interf\u00e4\u00e4nis nagu Jenkins v\u00f5i TeamCity, vaid tekstifailidena otse projekti hoidlas. See tagab versioonikontrolli. Ei ole raske tagasi minna v\u00f5i koguda koodi teises harus.<\/p>\n<p>Skripte saab projektis hoida. Aga mis teha keskkonnaga?<\/p>\n<p><strong>N\u00f5uanne nr 3: Docker aitab keskkonna seadistamisel.<\/strong><\/p>\n<p>Android-arendajatele aitab see kindlasti, iOS-i puhul kahjuks mitte.<\/p>\n<p>See on lihtsa docker-faili n\u00e4ide, mis sisaldab jdk ja android-sdk:<\/p>\n<pre><code class=\"plaintext\">FROM openjdk:8\n\nENV SDK_URL=\"https:\/\/dl.google.com\/android\/repository\/sdk-tools-linux-3859397.zip\" \n    ANDROID_HOME=\"\/usr\/local\/android-sdk\" \n    ANDROID_VERSION=26 \n    ANDROID_BUILD_TOOLS_VERSION=26.0.2\n\n# Laadige alla Android SDK\nRUN mkdir \"$ANDROID_HOME\" .android \n    &amp;&amp; cd \"$ANDROID_HOME\" \n    &amp;&amp; curl -o sdk.zip $SDK_URL \n    &amp;&amp; unzip sdk.zip \n    &amp;&amp; rm sdk.zip \n    &amp;&amp; yes | $ANDROID_HOME\/tools\/bin\/sdkmanager --licenses\n\n# Installige Android Build Tool ja raamatukogud\nRUN $ANDROID_HOME\/tools\/bin\/sdkmanager --update\nRUN $ANDROID_HOME\/tools\/bin\/sdkmanager \"build-tools;${ANDROID_BUILD_TOOLS_VERSION}\" \n    \"platforms;android-${ANDROID_VERSION}\" \n    \"platform-tools\"\n\nRUN mkdir \/application\nWORKDIR \/application\n<\/code><\/pre>\n<p>\nKirjutasin selle docker-faili (\u00fctlen saladuseks, et seda ei pea kirjutama, vaid valmis versiooni saab GitHubist) ja pildi kokku panemisega saate virtuaalse masina, millel saate rakendust koguda ja Junit-testimist k\u00e4ivitada.<\/p>\n<p>Kaks peamist argumenti, miks see on m\u00f5ttekas: skaliseeritavus ja korduvus. Dockeriga saab kiiresti \u00fcles seada tosin ehitustegevust, millel on t\u00e4pselt sama keskkond nagu eelmisel. See lihtsustab CI-inseneride elu tohutult. Android-sdk't dockerisse t\u00f5ukamine on v\u00e4ga lihtne, emulaatoritega on veidi keerulisem: natuke peab pingutama (v\u00f5i j\u00e4lle GitHubist valmis versiooni alla laadima).<\/p>\n<p><strong>N\u00f5uanne nr 4: \u00e4rge unustage, et kontrollid ei toimu kontrollide p\u00e4rast, vaid inimeste jaoks.<\/strong><\/p>\n<p>Arendajatele on v\u00e4ga t\u00e4htis kiire ja, mis k\u00f5ige t\u00e4htsam, arusaadav tagasiside: mis neil katki l\u00e4ks, milline test kukkus l\u00e4bi, kus build-logi vaadata.<\/p>\n<p><strong>N\u00f5uanne nr 5: olge pragmaatilised Continuous Integration'i arendamisel.<\/strong><\/p>\n<p>Selgelt m\u00f5ista, milliseid vigade t\u00fc\u00fcpe soovite ennetada, kui palju olete valmis ressursse, aega ja masinasaega kulutama. Liiga pikad kontrollid saab n\u00e4iteks \u00f6\u00f6seks edasi l\u00fckata. Ja nendest, mis avastavad mitte v\u00e4ga olulisi vigu, saab \u00fcldse loobuda.<\/p>\n<p><strong>N\u00f5uanne nr 6: kasutage valmis t\u00f6\u00f6riistu.<\/strong><\/p>\n<p>Praegu on palju ettev\u00f5tteid, mis pakuvad pilvep\u00f5hist CI-d.<\/p>\n<p><img decoding=\"async\" alt=\"CI arengu evolutsioon mobiilide arendusmeeskonnas\" src=\"\/wp-content\/uploads\/2019\/04\/e57aabd49ec01d14e83b64331fd84ec5.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nV\u00e4ikestele meeskondadele on see hea lahendus. Ei ole vaja midagi hallata, lihtsalt maksate natuke raha, kogute oma rakenduse ja isegi k\u00e4igate instrumentatsioonitestide.<\/p>\n<p><strong>N\u00f5uanne #7: suurtes meeskondades on kasulikum in-house lahendused.<\/strong><\/p>\n<p>Kuid \u00fchel hetkel, koos meeskonna kasvuga, saavad in-house lahendused tasuvamaks. Nende lahendustega on \u00fcks aspekt. Majanduses on seadus kahanemisest: igas projektis muutub iga j\u00e4rgmine t\u00e4iustamine aina raskemaks ja n\u00f5uab \u00fcha rohkem investeeringuid.<\/p>\n<p>Majandus kirjeldab kogu meie elu, sealhulgas pidevat integreerimist. Olen koostanud graafiku t\u00f6\u00f6j\u00f5u kulude kohta igas etapis meie pideva integreerimise arendamisel.<\/p>\n<p><img decoding=\"async\" alt=\"CI arengu evolutsioon mobiilide arendusmeeskonnas\" src=\"\/wp-content\/uploads\/2019\/04\/97bf8bff64f3ac9ae587fae195251da2.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nOn n\u00e4ha, et iga t\u00e4iustamine muutub aina keerulisemaks ja keerulisemaks. Vaadates seda graafikut, v\u00f5ib m\u00f5ista, et pidevat integreerimist tuleb arendada koos meeskonna suuruse kasvuga. Kahe inimese meeskonnale kulutada 50 p\u00e4eva sisemise emulaatorite farmi arendamiseks \u2013 see pole k\u00f5ige parem idee. Kuid samas ei tegutseda suure meeskonnaga pideva integreerimise suunal \u2013 ka see on halb idee, kuna integreerimise probleemide lahendamiseks, suhtlemise parandamiseks jne kulub veelgi rohkem aega.<\/p>\n<p>Alustasime sellest, et automatiseerimine on vajalik, sest inimesed on kallid, nad teevad vigu ja on laisad. Kuid automatiseerivad ka inimesed. Seet\u00f5ttu kehtivad k\u00f5ik need probleemid ka automatiseerimise kohta.<\/p>\n<ul>\n<li>Automatiseerimine on kallis. Meenutage t\u00f6\u00f6j\u00f5u kulu graafikut.<\/li>\n<li>Automatiseerimise k\u00e4igus teevad inimesed vigu.<\/li>\n<li>Automatiseerimine on m\u00f5nikord \u00fcsna t\u00fclikas, sest nii v\u00f5i naa k\u00f5ik t\u00f6\u00f6tab. Miks veel midagi parandada, miks kogu see pidev integreerimine?<\/li>\n<\/ul>\n<p>\nAga mul on statistika: 20% kogumistest tuvastatakse vead. Ja see ei juhtu seet\u00f5ttu, et meie arendajad kirjutavad halba koodi. See toimub seet\u00f5ttu, et arendajad on kindlad, et kui nad teevad vea, ei j\u00f5ua see develop'i, vaid tuvastavad selle automatiseeritud kontrollid. Seega saavad arendajad kulutada rohkem aega koodi kirjutamisele ja huvitavatele asjadele, mitte mitte kohapeal midagi k\u00e4ima ja kontrollima.<\/p>\n<p><strong>Tegelege pideva integreerimisega. Kuid m\u00f5istlikult.<\/strong><\/p>\n<blockquote><p>Muide, Nikolai Nesterov mitte ainult ei tee lahedaid ettekandeid, vaid ta kuulub ka programmikomiteesse <noindex><a rel=\"nofollow\" href=\"https:\/\/appsconf.ru\/moscow\/2019\">AppsConf<\/a><\/noindex> ja aitab teistel valmistada teie jaoks sisukaid esitlusi. L\u00e4hima konverentsi programmi t\u00e4iuslikkust ja kasulikkust saab hinnata teemade kaudu <noindex><a rel=\"nofollow\" href=\"https:\/\/appsconf.ru\/moscow\/2019\/schedule\">ajakavast<\/a><\/noindex>. T\u00e4iendavate \u00fcksikasjade saamiseks tulge 22-23 aprillil Infoprostranstvo.<\/p><\/blockquote>\n<p>Allikas: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/oleg-bunin\/blog\/447608\/\">habr.com<\/a><\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u0421\u0435\u0433\u043e\u0434\u043d\u044f \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u043e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u044b\u0445 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u043e\u0432 \u0440\u0430\u0437\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u0430\u0445. \u0423\u0441\u043b\u043e\u0432\u0438\u044f \u0443\u0441\u043f\u0435\u0445\u0430 \u043a\u043e\u043c\u0430\u043d\u0434\u043d\u043e\u0439 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0432 \u0432\u0438\u0434\u0435 \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0441\u0445\u0435\u043c\u044b. \u041d\u0430\u043f\u0438\u0441\u0430\u0432 \u043a\u043e\u0434, \u0432\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0443\u0431\u0435\u0434\u0438\u0442\u044c\u0441\u044f, \u0447\u0442\u043e \u043e\u043d: \u0420\u0430\u0431\u043e\u0442\u0430\u0435\u0442. \u041d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043b\u043e\u043c\u0430\u0435\u0442, \u0432 \u0442\u043e\u043c \u0447\u0438\u0441\u043b\u0435 \u043a\u043e\u0434, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0430\u043f\u0438\u0441\u0430\u043b\u0438 \u0432\u0430\u0448\u0438 \u043a\u043e\u043b\u043b\u0435\u0433\u0438. \u0415\u0441\u043b\u0438 \u043e\u0431\u0430 \u0443\u0441\u043b\u043e\u0432\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u044e\u0442\u0441\u044f, \u0442\u043e \u0432\u044b \u043d\u0430 \u043f\u0443\u0442\u0438 \u043a \u0443\u0441\u043f\u0435\u0445\u0443. \u0427\u0442\u043e\u0431\u044b \u043b\u0435\u0433\u043a\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u044d\u0442\u0438 \u0443\u0441\u043b\u043e\u0432\u0438\u044f \u0438 \u043d\u0435 \u0441\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0442\u044c \u0441 [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":23270,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-31304","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.1.1 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u0421\u0435\u0433\u043e\u0434\u043d\u044f \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u043e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u044b\u0445 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u043e\u0432 \u0440\u0430\u0437\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u0430\u0445.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/et\/blog\/administrirovanie\/evolyutsiya-ci-v-komande-mobilnoj-razrabotki\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.1.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"et_EE\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47\u042d\u0432\u043e\u043b\u044e\u0446\u0438\u044f CI \u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u0435 \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0439 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u0421\u0435\u0433\u043e\u0434\u043d\u044f \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u043e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u044b\u0445 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u043e\u0432 \u0440\u0430\u0437\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u0430\u0445.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/et\/blog\/administrirovanie\/evolyutsiya-ci-v-komande-mobilnoj-razrabotki\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2019-10-31T18:40:34+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2019-10-31T18:40:34+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47CI evolutsioon mobiiliarenduse meeskonnas | ProHoster","description":"T\u00e4nap\u00e4eval arendavad enamik tarkvaratooteid meeskonnad.","canonical_url":"https:\/\/prohoster.info\/et\/blog\/administrirovanie\/evolyutsiya-ci-v-komande-mobilnoj-razrabotki","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"et_EE","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47\u042d\u0432\u043e\u043b\u044e\u0446\u0438\u044f CI \u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u0435 \u043c\u043e\u0431\u0438\u043b\u044c\u043d\u043e\u0439 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 | ProHoster","og:description":"\u0421\u0435\u0433\u043e\u0434\u043d\u044f \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u043e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u044b\u0445 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u043e\u0432 \u0440\u0430\u0437\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u0430\u0445.","og:url":"https:\/\/prohoster.info\/et\/blog\/administrirovanie\/evolyutsiya-ci-v-komande-mobilnoj-razrabotki","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2019-10-31T18:40:34+00:00","article:modified_time":"2019-10-31T18:40:34+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"31304","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":"2026-01-21 05:30:21","breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-03-01 03:19:49","updated":"2026-01-21 05:30:21","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/et\/wp-json\/wp\/v2\/posts\/31304","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/et\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/et\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/et\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/et\/wp-json\/wp\/v2\/comments?post=31304"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/et\/wp-json\/wp\/v2\/posts\/31304\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/et\/wp-json\/wp\/v2\/media\/23270"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/et\/wp-json\/wp\/v2\/media?parent=31304"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/et\/wp-json\/wp\/v2\/categories?post=31304"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/et\/wp-json\/wp\/v2\/tags?post=31304"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}