{"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 areng seente meeskonnas","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>T\u00e4nap\u00e4eval arendatakse enamik tarkvaratooteid meeskondades. Meeskonna arendamise edu tingimusi v\u00f5ib esitada lihtsa skeemina.<\/p>\n<p><img decoding=\"async\" alt=\"CI areng seente meeskonnas\" src=\"\/wp-content\/uploads\/2019\/04\/b283cfd7772d8f479be0754ebbe51b19.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nKoodi kirjutades peaksite veenduma, et see:<\/p>\n<ol>\n<li>T\u00f6\u00f6tab.<\/li>\n<li>Ei rikku midagi, sealhulgas teie kolleegide kirjutatud koodi.<\/li>\n<\/ol>\n<p>\nKui m\u00f5lemad tingimused on t\u00e4idetud, siis olete edusamme tegemas. Nende tingimuste lihtsaks kontrollimiseks ja kasulikult edasiviivale teele j\u00e4\u00e4miseks on v\u00e4lja t\u00f6\u00f6tatud pidev integreerimine.<\/p>\n<p>CI on t\u00f6\u00f6voog, kus integreerite oma koodi v\u00f5imalikult sageli tootekoodi. Ja mitte lihtsalt integreerite, vaid kontrollite pidevalt, et k\u00f5ik toimib. Kuna tuleb sageli ja palju kontrollida, on m\u00f5istlik m\u00f5elda automatiseerimise peale. K\u00f5ike saab k\u00e4sitsi kontrollida, kuid seda ei tohiks teha, ja siin on p\u00f5hjus.<br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<ul>\n<li><strong>Inimesed on kallid<\/strong>. Iga programmeerija t\u00f6\u00f6tunni hind \u00fcletab igasuguste serverite t\u00f6\u00f6tunni hinda.<\/li>\n<li><strong>Inimesed teevad vigu<\/strong>. Seet\u00f5ttu v\u00f5ivad tekkida olukorrad, kus testid k\u00e4ivitatakse vale haru peal v\u00f5i koostatakse vale kommit testeritele.<\/li>\n<li><strong>Inimesed on laisad<\/strong>. Aeg-ajalt, kui mul on m\u00f5ni \u00fclesanne l\u00f5petatud, tekib mul m\u00f5te: \u201eMilleks siin \u00fcldse midagi kontrollida? Kirjutasin kaks rida \u2014 kindlasti k\u00f5ik t\u00f6\u00f6tab!\u201d M\u00f5tlen, et ka m\u00f5nel teist v\u00f5ivad sellised m\u00f5tted aeg-ajalt p\u00e4he tulla. Kuid kontrollida tuleb alati.<\/li>\n<\/ul>\n<p>\nKuidas juurutati ja arendati j\u00e4tkuvat integreerimist Avito mobiiliviimiste meeskonnas, kuidas j\u00f5uti 0-lt 450 kogumiseni p\u00e4evas, ja milline on build-masinate 200 tunni kogumine p\u00e4evas, 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 evolutsiooniliste muutuste juures.<\/p>\n<p>Jutustus p\u00f5hineb Android-meeskonna n\u00e4itel, kuid enamik l\u00e4henemisviise on rakendatavad ka iOS-i puhul.<\/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=\"Vaata 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 vaid \u00fcks inimene. Talle ei olnud mingil juhul vajalik mitte midagi j\u00e4tkuva integreerimise kohta: kellegagi ei olnud vaja integreeruda.<\/p>\n<p>Kuid rakendus kasvas, uusi \u00fclesandeid tuli j\u00e4rjest juurde ja seega kasvas ka meeskond. \u00dchel hetkel tuli aeg, mil oli vajalik koodi integreerimise protsess formaalsemaks muuta. Otsustati kasutada Git flow'd.<\/p>\n<p><img decoding=\"async\" alt=\"CI areng seente meeskonnas\" 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 haru develop, ja iga uue funktsiooni jaoks loovad arendajad eraldi haru, commitivad sinna, pushivad, ja kui nad soovivad oma koodi haru develop'iga \u00fchendada, avavad pull request'i. Teadmiste vahetamiseks ja l\u00e4henemiste aruteluks oleme sisse viinud koodivaatluse, mis t\u00e4hendab, et kolleegid peavad \u00fcksteise koodi kontrollima ja kinnitama.<\/p>\n<h2>Kontrollid<\/h2>\n<p>\nKoodi vaatamine on suurep\u00e4rane, kuid mitte piisav. Seet\u00f5ttu on sisse viidud automaatsed kontrollid.<\/p>\n<ul>\n<li>Esmalt kontrollime <strong>ARK komponentide koostamist<\/strong>.<\/li>\n<li>Palju <strong>Junit teste<\/strong>.<\/li>\n<li><strong>Arvutame koodikatvuse<\/strong>, kuna k\u00e4ivitame teste.<\/li>\n<\/ul>\n<p>\nEt m\u00f5ista, kuidas neid kontrolle k\u00e4ivitada, vaatame Avito arendusprotsessi.<\/p>\n<p>Skeemiliselt v\u00f5ib selle esitada j\u00e4rgmiselt:<\/p>\n<ul>\n<li>Arendaja kirjutab koodi oma s\u00fclearvutis. Integreerimiskontrolle saab k\u00e4ivitada otse siin \u2014 kas commit hook'i abil v\u00f5i lihtsalt taustal kontrolle k\u00e4ivitades.<\/li>\n<li>P\u00e4rast seda, kui arendaja on koodi pushinud, avab ta pull request'i. Selleks, et tema kood j\u00f5uaks haru develop, peab ta l\u00e4bima koodivaatluse ja koguma vajaliku arvu kinnitusi. Siin saab sisse l\u00fclitada kontrollid ja build'id: seni, kuni k\u00f5ik build'id ei ole edukad, ei saa pull request'i \u00fchendada.<\/li>\n<li>P\u00e4rast seda, kui pull request on kokku sulandatud ja kood on arenduses, 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>\nTestide k\u00e4itamine oma s\u00fclearvutis ei olnud kellelegi meeltm\u00f6\u00f6da. Kui arendaja on funktsionaalsuse l\u00f5petanud, soovib ta selle kiiresti osutada ja avada pull requesti. Kui sel hetkel k\u00e4ivitatakse mingeid pikki teste, on see mitte ainult ebameeldiv, vaid pidurdab ka arendust: seni kuni s\u00fclearvuti midagi testib, ei ole seal normaalselt t\u00f6\u00f6d teha.<\/p>\n<p>\u00d6\u00f6sel testide k\u00e4itamine oli meile v\u00e4ga meeltm\u00f6\u00f6da, sest aega ja servereid on palju, v\u00f5ib vabalt testida. Kuid kahjuks, kui funktsionaalsuse kood on arenduses, on arendajal t\u00f5en\u00e4oliselt palju v\u00e4hem motivatsiooni parandada CI leidnud vigu. Aeg-ajalt tabasin end m\u00f5ttelt, kui vaatasin hommikusest raportist \u00fcles leitud vigu, et parandan need kunagi hiljem, sest praegu on Jira's \u00e4ge uus \u00fclesanne, mida tahaks kohe tegema hakata.<\/p>\n<p>Kui teste takistavad pull requesti, on motivatsioon piisav, sest seni kuni ehitused ei ole roheline, ei p\u00e4\u00e4se kood arendusse ja seega ei saa \u00fclesanne l\u00f5puleviidud.<\/p>\n<p>L\u00f5puks valisime sellise strateegia: \u00f6\u00f6sel teeme v\u00f5imalikult palju kontrollerite teste ja k\u00f5ige kriitilisemad ning, mis k\u00f5ige t\u00e4htsam, kiireimad, \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c pull request'is. Kuid me ei peatu sellega \u2014 samal ajal optimeerime kontrollide l\u00e4bimise kiirus, et viia need \u00f6\u00f6re\u017eiimist \u00fcle pull request'i kontrollimiseks.<\/p>\n<p>Sel ajal l\u00e4bisid k\u00f5ik meie kogumid piisavalt kiiresti, seega l\u00fclitasime pull request'i blokkerisse A\u0420\u041a kogumise, Junit-testid ja koodi katvuse kalkulatsiooni. L\u00fclitasime sisse, m\u00f5tlesime ja loobusime koodi katvusest, kuna arvasime, et see pole meile vajalik.<\/p>\n<p><strong><em>Kogu p\u00f5hisi CI seadistamiseks kulus meil kaks p\u00e4eva (siin ja edaspidi on ajakava umbkaudne, vajalik ulatuse m\u00f5istmiseks). <\/em><\/strong><\/p>\n<p>P\u00e4rast seda hakkasime m\u00f5tlema edasi \u2014 kas me kontrollime \u00fcldse \u00f5igesti? Kas me k\u00e4ivitame kogumid pull request'is \u00f5igesti?<\/p>\n<p>K\u00e4ivitame kogumise viimase haru commit'i peal, mille alusel pull request avati. Kuid selle commit'i kontrollid v\u00f5ivad n\u00e4idata vaid, et kood, mille arendaja kirjutas, t\u00f6\u00f6tab. Kuid need ei t\u00f5enda, et ta midagi rikub. Tegelikult tuleks kontrollida haru develop seisundit p\u00e4rast seda, kui funktsioon on sinna viidud.<\/p>\n<p><img decoding=\"async\" alt=\"CI areng seente meeskonnas\" src=\"\/wp-content\/uploads\/2019\/04\/c1a179b68b02c04031177f9bf51a81ed.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nSelle jaoks kirjutasime me 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 k\u00f5ige v\u00e4rskemad muudatused develop'ist ja liidetakse praegusesse haru. Me lisasime skripti premerge.sh k\u00f5igi ehituste esimeseks sammuks ning hakkasime kontrollima t\u00e4pselt seda, mida me tahame, nimelt <strong>integratsiooni<\/strong>.<\/p>\n<p><strong><em>Probleemide lokaliseerimiseks, lahenduse leidmiseks ja selle skripti kirjutamiseks kulus kolm p\u00e4eva.<\/em><\/strong><\/p>\n<p>Rakendus arenes, \u00fclesandeid tuli \u00fcha rohkem, meeskond kasvas ja premerge.sh hakkas meid m\u00f5nikord alt vedama. Develop'isse tungisid vastuolulised muudatused, mis l\u00f5hkusid ehituse.<\/p>\n<p>N\u00e4ide sellest, kuidas see juhtub:<\/p>\n<p><img decoding=\"async\" alt=\"CI areng seente meeskonnas\" src=\"\/wp-content\/uploads\/2019\/04\/6762d9ce4e431549455c49f2317a8f20.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nKaks arendajat hakkavad samal ajal t\u00f6\u00f6tama funktsioonide A ja B kallal. Funktsiooni A arendaja leiab projektist kasutamata funktsiooni <code>answer()<\/code> ja, nagu hea skaut, eemaldab selle. Samal ajal lisab funktsiooni B arendaja oma harusse uue v\u00e4ljakutse sellele funktsioonile.<\/p>\n<p>Arendajad l\u00f5petavad t\u00f6\u00f6 ja avavad samal ajal pull request'i. K\u00e4ivitatakse ehitused, premerge.sh kontrollib m\u00f5lemat pull request'i arvestades v\u00e4rsket olukorda develop'is \u2014 k\u00f5ik kontrollid on rohelised. P\u00e4rast seda liidetakse funktsiooni A pull request, liidetakse funktsiooni B pull request\u2026 Bum! Develop l\u00e4heb katki, sest koodis on kutsumine mitteeksisteerivale funktsioonile.<\/p>\n<p><img decoding=\"async\" alt=\"CI areng seente meeskonnas\" src=\"\/wp-content\/uploads\/2019\/04\/0261dcc9f7f2e5e018ab136081b4679b.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nKui develop ei suuda t\u00f6\u00f6tada, on see <strong>kohalik katastroof<\/strong>. Kogu meeskond ei suuda midagi koguda ega testimiseks anda.<\/p>\n<p>Nii juhtus, et olen sagedamini tegelenud infrastruktuuri \u00fclesannetega: anal\u00fc\u00fcs, v\u00f5rk, andmebaasid. See t\u00e4hendab, et just mina kirjutasin need funktsioonid ja klassid, mida teised arendajad kasutavad. Seet\u00f5ttu sattusin tihti sarnastesse olukordadesse. Mul oli isegi \u00fchel perioodil selline pilt.<\/p>\n<p><img decoding=\"async\" alt=\"CI areng seente meeskonnas\" src=\"\/wp-content\/uploads\/2019\/04\/0eb1aadd05b33ce995027ab52d5c1e53.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nKuna see ei rahuldanud meid, hakkasime uurima v\u00f5imalusi, kuidas seda ennetada.<\/p>\n<h2>Kuidas mitte katkestada develop<\/h2>\n<p>\nEsimene variant: <strong>k\u00f5ik pull requestid (PR) \u00fcmber kompileerida, kui develop'i v\u00e4rskendatakse. <\/strong>Kui meie n\u00e4ites PR funktsiooni A l\u00e4heb esimesena develop'i, siis kompileeritakse PR funktsiooni B \u00fcmber ning seega ei l\u00e4he testimised l\u00e4bi, kuna tekib kompileerimisviga.<\/p>\n<p>Et m\u00f5ista, kui kaua selleks aega kulub, vaatame kahte PR-i n\u00e4idet. Avame kaks PR-i: kaks ehitust, kaks testide k\u00e4ivitamist. P\u00e4rast seda, kui esimene PR on devlapi sisse voolu l\u00e4inud, tuleb teine \u00fcmber kompileerida. Kokku kulub kahe PR-i jaoks kolm testimise k\u00e4ivitamist: 2 + 1 = 3.<\/p>\n<p>P\u00f5him\u00f5tteliselt on k\u00f5ik normaalne. Kuid me vaatasime statistikat ja meie meeskonnas oli tavaline olukord, kus oli 10 avatud PR-i, ning seega kontrollide arv on summa: 10 + 9 +\u2026 + 1 = 55. See t\u00e4hendab, et 10 PR-i aktsepteerimiseks tuleb kokku 55 korda uuesti \u00fcles ehitada. Ja see on ideaalne olukord, kus k\u00f5ik kontrollid l\u00e4bivad esimese korraga, ja keegi ei ava lisanduvaid pull request'e, samal ajal kui seda k\u00fcmmet t\u00f6\u00f6deldakse.<\/p>\n<p>Kujutage ette end arendajana, kes peab esimesena nuppu 'merge' vajutama, sest kui seda teeb naaber, tuleb oodata, kuni k\u00f5ik kogumised uuesti l\u00e4bi k\u00e4ivad... Ei, nii ei saa, see takistab t\u00f5siselt arendust.<\/p>\n<p>Teine v\u00f5imalus: <strong>koguda pull request p\u00e4rast koodide \u00fclevaatust. <\/strong>See, you open a pull request, gather the necessary approvals from colleagues, fix what needs to be fixed, and then run the builds. If they are successful, the pull request is merged with develop. In this case, there are no additional restarts, but feedback slows down significantly. As a developer, when I open a pull request, I want to see immediately if it builds. For example, if a test fails, it needs to be fixed quickly. If the build is delayed, feedback slows down, which affects the entire development process. This was also unsatisfactory for us.<\/p>\n<p>In the end, only the third option remained \u2014 <strong>bicycle<\/strong>. All our code, all our source files are stored in a repository on the Bitbucket server. Accordingly, we had to develop a plugin for Bitbucket.<\/p>\n<p><img decoding=\"async\" alt=\"CI areng seente meeskonnas\" src=\"\/wp-content\/uploads\/2019\/04\/5e4b1f770ea1a3449b616c3101640294.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nSee plugin \u00fcletab pull request&#8217;ide sulandumismehhanismi. Algus on standardne: avatakse PR, k\u00f5ik kogumised k\u00e4ivitatakse, toimub koodide \u00fclevaatus. Kuid p\u00e4rast seda, kui koodide \u00fclevaatus on l\u00e4bitud ja arendaja otsustab vajutada \"sulanda\", kontrollib plugin, millise oleku osas toimusid testimised. Kui p\u00e4rast kogumisi on develop v\u00e4rskendatud, ei luba plugin sellist pull requesti p\u00f5hiharusse sulandada. See lihtsalt k\u00e4ivitab kogumised uuemate developi osas.<\/p>\n<p><img decoding=\"async\" alt=\"CI areng seente meeskonnas\" src=\"\/wp-content\/uploads\/2019\/04\/0edee880e1f2d286a0c64033103ac136.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nMeie konfliktsete muudatuste n\u00e4ites ei l\u00e4he sellised kogumised l\u00e4bi kompileerimise veateate t\u00f5ttu. Vastavalt on arendaja B funktsionaalsuse koodi parandamise kohustuses, et uuesti testida, siis plugina rakendab automaatselt pull requesti.<\/p>\n<p>Enne selle plugina rakendamist oli meil keskmiselt 2.7 testimist \u00fche pull requesti kohta. Plugina rakendamisega t\u00f5usis see 3.6 testimiseni. See meid rahuldas.<\/p>\n<p>Tuleb m\u00e4rkida, et sellel pluginal on puudus: see k\u00e4ivitab ehituse uuesti ainult \u00fcks kord. See t\u00e4hendab, et j\u00e4\u00e4b v\u00e4ike aken, mille kaudu v\u00f5ivad arengusse sattuda konfliktivad muudatused. Kuid selle t\u00f5en\u00e4osus on madal, ja me n\u00e4gime raskust koguste ja purunemise t\u00f5en\u00e4osuse vahel. Kahte aastat jooksul on see toimunud vaid \u00fcks kord, seega ilmselt mitte asjata.<\/p>\n<p><strong><em>Esimese versiooni kirjutamine Bitbucketile v\u00f5ttis meil kaks n\u00e4dalat. <\/em><\/strong><\/p>\n<h3>Uued kontrollid<\/h3>\n<p>\nSamal ajal j\u00e4tkas meie meeskond kasvu. Meile lisandusid uued kontrollid.<\/p>\n<p>Me m\u00f5tlesime: miks parandada vigu, kui neid saab ennetada? Ja seet\u00f5ttu rakendasime <strong>koodi staatiline anal\u00fc\u00fcs<\/strong>. Alustasime lintiga, mis on osa Android SDK-st. Kuid sel ajal ei osanud see \u00fcldse t\u00f6\u00f6tada Kotlin-koodiga, samas kui meie rakendusest oli juba 75% kirjutatud Kotlinis. Seet\u00f5ttu lisasime lintile integreeritud <strong>Android Studio kontrolle.<\/strong><\/p>\n<p>Selleks tuli meeleheitlikult tegutseda: v\u00f5tta Android Studio, pakkida see Dockerisse ja k\u00e4ivitada CI-s virtuaalse monitoriga, et ta arvestaks, et see t\u00f6\u00f6tab reaalses s\u00fclearvutis. Kuid see t\u00f6\u00f6tas.<\/p>\n<p>Samuti alustasime sel ajal palju <strong>instrumentatsiooniteste<\/strong> ja rakendasime <strong>screenshot-teste.<\/strong>. See on hetk, mil genereeritakse referentskuva eraldi v\u00e4ikesele vaatele, ja test seisneb selles, et vaate ekraanipilt v\u00f5etakse ja v\u00f5rreldakse referentsiga piksel-piksel. Kui on erinevus, t\u00e4hendab see, et kuskil on paigutus vale v\u00f5i stiilides on midagi valesti.<\/p>\n<p>Kuid instrumentatsioonitestid ja ekraanipiltide testid tuleb k\u00e4ivitada seadmetes: emulaatoritel v\u00f5i reaalsetel seadmetel. Arvestades, et teste on palju ja need k\u00e4ivitatakse tihti, on vajalik terve farm. Oma farmi rajamine on liiga t\u00f6\u00f6mahukas, seega leidsime valmis lahenduse \u2014 Firebase Test Lab.<\/p>\n<h3>Firebase Test Lab<\/h3>\n<p>\nValiti, kuna Firebase on Google'i toode, mis t\u00e4hendab, et see peaks olema usaldusv\u00e4\u00e4rne ja t\u00f5en\u00e4oliselt mitte kunagi surema. Hinnad on taskukohased: 5 $ tunni eest reaalset seadet, 1 $ tunni eest emulaatorit.<\/p>\n<p><strong><em>Firebase Test Labi integreerimiseks meie CI-s kulus umbes kolm n\u00e4dalat.<\/em><\/strong><\/p>\n<p>Kuid meeskond j\u00e4tkas kasvu ja kahjuks alustas Firebase meid alt vedama. Sel ajal ei olnud tal mingit SLA-d. M\u00f5nikord pidi Firebase ootama, kuni vajalik arv seadmeid testimiseks vabastatakse, mitte ei alustanud nende t\u00e4itmist kohe, nagu me soovisime. Ooteaeg j\u00e4rjekorras kestis kuni poole tunni, mis oli v\u00e4ga pikk. Instrumentatsiooni testid k\u00e4idi l\u00e4bi iga PR-i juures, viivitused pidurdasid arendust ja seej\u00e4rel tuli veel kuu arve suure summa eest. Kokkuv\u00f5ttes otsustati Firebase'ist loobuda ja hakata in-house arendama, kuna meeskond oli piisavalt suur.<\/p>\n<h3>Docker + Python + bash<\/h3>\n<p>\nV\u00f5tsime Dockeri, panime sinna emulaatorid, kirjutasime lihtsa programmi Pythonis, mis vajalikul hetkel k\u00e4ivitab vajaliku arvu emulaatoreid soovitud versioonis ja kui vaja, peatab need. Ja loomulikult paar bash-skripti \u2014 kuidas siis ilma nendeta?<\/p>\n<p><strong><em>Oma testimisruumi loomiseks kulus viis n\u00e4dalat.<\/em><\/strong><\/p>\n<p>Tulemuseks oli igale pull request'ile laialdane, sulgemise blokeeriv kontrollide loetelu:<\/p>\n<ul>\n<li>ARK kogumine;<\/li>\n<li>Junit-testid;<\/li>\n<li>Lint;<\/li>\n<li>Android Studio kontrollid;<\/li>\n<li>Instrumentatsiooni testid;<\/li>\n<li>Kuvapilditesti.<\/li>\n<\/ul>\n<p>\nSee v\u00e4ltis palju v\u00f5imalikke rikkeid. Tehniliselt k\u00f5ik t\u00f6\u00f6tas, kuid arendajad kaebasid, et tulemuste ootamine on liiga pikk.<\/p>\n<p>Liiga pikk \u2014 kui kaua see kestab? Laadime Bitbucketist ja TeamCityst andmed anal\u00fc\u00fcsi s\u00fcsteemi ja saame aru, et <strong>keskmine ootamisaeg on 45 minutit<\/strong>. See t\u00e4hendab, et arendaja ootab keskmiselt oma pull request'i tulemusi 45 minutit. Minu arvates on see v\u00e4ga palju ja nii ei saa t\u00f6\u00f6tada.<\/p>\n<p>Muidugi otsustasime kiirendada k\u00f5iki meie build'e.<\/p>\n<h2>Aktiivselt parandame<\/h2>\n<p>\nN\u00e4gin, et buildid j\u00e4\u00e4vad sageli j\u00e4rjekorda, seega ostsime k\u00f5igepealt <strong>rohkem riistvara<\/strong> \u2014 ekstensiivne areng on k\u00f5ige lihtsam. Buildid lakkasid j\u00e4rjekorras seismast, kuid ootamisaeg v\u00e4henes vaid veidi, sest m\u00f5ned kontrollid olid iseenesest v\u00e4ga pikad.<\/p>\n<h3>Eemaldame liiga pikad kontrollid<\/h3>\n<p>\nMeie pidev integreerimine suudab selliseid vigu ja probleeme tuvastada.<\/p>\n<ul>\n<li><strong>Ei koondata<\/strong>. CI suudab tabada kompileerimviga, kui konfliktsete muudatuste t\u00f5ttu midagi ei koo. Nagu ma juba \u00fctlesin, siis tollal ei suuda keegi midagi kokku panna, areng seiskub ja k\u00f5ik on \u00e4revuses.<\/li>\n<li><strong>K\u00e4itumise viga<\/strong>. N\u00e4iteks, kui rakendus k\u00e4ivitatakse, kuid nuppu vajutades see kukub v\u00f5i nupp ei reageeri \u00fcldse. See on halb, sest selline viga v\u00f5ib j\u00f5uda kasutajani.<\/li>\n<li><strong>Disaini viga<\/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>\nSeda nimekirja vaadates m\u00f5istsime, et kriitilised on vaid esimesed kaks punkti. Selliseid probleeme soovime esmaj\u00e4rjekorras tuvastada. Disaini vigade avastamine toimub disaini \u00fclevaate etapis ja seal on neid lihtne parandada. Tehnilise v\u00f5la haldamine n\u00f5uab eraldi protsessi ja planeerimist, seet\u00f5ttu otsustasime seda pull request'il mitte kontrollida.<\/p>\n<p>Selle klassifitseerimise p\u00f5hjal l\u00e4bisime kogu kontrollide nimekirja. <strong>V\u00e4listasime Linti<\/strong> ja l\u00fckkasime selle k\u00e4ivitamise \u00f6\u00f6se: lihtsalt et saada aruandlust, kui palju probleeme projektis on. Tehnilise v\u00f5laga oleme kokku leppinud t\u00f6\u00f6tama eraldi, ja <strong>Android Studio kontrolle oleme t\u00e4ielikult loobunud<\/strong>. Android Studio Dockeris vigade kontrollimiseks k\u00f5lab huvitavalt, kuid toob palju ebamugavusi \u00fchilduvuses. Iga Android Studio versiooni uuendus on v\u00f5itlus arusaamatute t\u00f5rgetega. Samuti oli ekraanipildi testide toetamine keeruline, sest teek toimis ebastabiilselt ja esines valeh\u00e4iresid. <strong>Ekraanipildi teste eemaldati kontrollide nimekirjast<\/strong>.<\/p>\n<p>L\u00f5puks j\u00e4id meil alles:<\/p>\n<ul>\n<li>ARK kogumine;<\/li>\n<li>Junit-testid;<\/li>\n<li>Instrumentatsioonitestid.<\/li>\n<\/ul>\n<h3>Gradle kaugv\u00e4lim\u00e4lu<\/h3>\n<p>\nIlma keeruliste kontrollideta on k\u00f5ik parem. Kuid t\u00e4iustamisel ei ole piire!<\/p>\n<p>Meie rakendus oli juba jagatud umbes 150 gradle mooduliks. Tavaliselt t\u00f6\u00f6tab sellisel juhul Gradle kaugv\u00e4lim\u00e4lu h\u00e4sti, ja otsustasime seda proovida.<\/p>\n<p>Gradle kaugv\u00e4lim\u00e4lu on teenus, mis suudab kahetseda kogumise artefakte eraldi \u00fclesannete jaoks erinevates moodulites. Gradle, selle asemel et reaalselt koodi kompileerida, k\u00fcsib HTTP kaudu kaugv\u00e4lim\u00e4lult, kas keegi on selle \u00fclesande juba t\u00e4itnud. Kui jah, siis laadib lihtsalt tulemuse alla.<\/p>\n<p><strong><em>Gradle kaugv\u00e4lim\u00e4lu k\u00e4ivitamine on lihtne, sest Gradle pakub Docker'i pilti. Meil \u00f5nnestus seda teha kolme tunni jooksul.<\/em><\/strong><\/p>\n<p>Kogu, mida oli vaja, oli Docker k\u00e4ivitamine ja projekti \u00fchele reale kirjutamine. Kuigi selle kiirelt t\u00f6\u00f6le saamine on v\u00f5imalik, kulub selle t\u00f5rgeteta toimimiseks siiski rohkelt aega.<\/p>\n<p>Allpool on cache misside graafik.<\/p>\n<p><img decoding=\"async\" alt=\"CI areng seente meeskonnas\" src=\"\/wp-content\/uploads\/2019\/04\/458ef23b5506b04f3bd385be0c18607a.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nAlguses oli cache'i m\u00f6\u00f6dalaskmise protsent umbes 65. Kolme n\u00e4dala p\u00e4rast suudeti see v\u00e4\u00e4rtus viia 20%ni. Selgus, et Android rakenduse kogutavad \u00fclesanded omavad kummalisi transitiivseid s\u00f5ltuvusi, mist\u00f5ttu Gradle j\u00e4ttis cache'i k\u00f5rvale.<\/p>\n<p>Cache'i \u00fchendamine kiirendas meie ehitust m\u00e4rgatavalt. Kuid lisaks ehitusele k\u00e4ivad ka instrumentation testid, ja need v\u00f5tavad kaua aega. V\u00f5ib-olla ei pea k\u00f5iki teste jooksutama iga pull request'i puhul. Selle selgitamiseks kasutame impaktilahendust.<\/p>\n<h3>Impaktilahendus<\/h3>\n<p>\nPull request'i puhul kogume git diff'i ja leiame muudetud Gradle moodulid.<\/p>\n<p><img decoding=\"async\" alt=\"CI areng seente meeskonnas\" src=\"\/wp-content\/uploads\/2019\/04\/471ca37206a0da4d5741c8ee1dbb7f03.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nOn m\u00f5ttekas k\u00e4ivitada ainult need instrumentation testid, mis kontrollivad muudetud mooduleid ja k\u00f5iki mooduleid, mis neist s\u00f5ltuvad. K\u00f5rvalloodud moodulite teste pole m\u00f5tet k\u00e4ivitada: seal ei ole kood muutunud, seega ei saa midagi puruneda.<\/p>\n<p>Instrumentation testidega ei ole k\u00f5ik nii lihtne, kuna need peavad olema k\u00f5ige k\u00f5rgema taseme rakenduse moodulis. Kasutasime heuristikast bytecode'i anal\u00fc\u00fcsi, et m\u00f5ista, millisele moodulile iga test kuulub.<\/p>\n<p><strong><em>Instrumentation testide t\u00e4iendamine, et nad kontrolliksid ainult aktiivseid mooduleid, kestis umbes kaheksa n\u00e4dalat.<\/em><\/strong><\/p>\n<p>Kontrollide kiirusmeetmed t\u00f6\u00f6tasid edukalt. 45 minutist oleme j\u00f5udnud umbes 15 minutini. Veerand tundi oodata buildi on n\u00fc\u00fcd normaalne.<\/p>\n<p>Aga n\u00fc\u00fcd hakkasid arendajad kurtma, et nad ei saa aru, millised buildid k\u00e4ivitatakse, kus vaadata logi, miks build on punane, milline test on kukkunud jne.<\/p>\n<p><img decoding=\"async\" alt=\"CI areng seente meeskonnas\" src=\"\/wp-content\/uploads\/2019\/04\/aa76151174cd4e5a45ff281818e312f5.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nTagasiside probleemid aeglustavad arendust, seet\u00f5ttu p\u00fc\u00fcdsime pakkuda maksimaalselt arusaadavat ja detailset teavet iga PR ja buildi kohta. Alustasime Bitbucketis PR-i kommentaaridega, m\u00e4rkides, milline build kukkus ja miks, saatsime adresseeritud s\u00f5numeid Slackis. L\u00f5puks tegime PR-i jaoks armatuurlaud, kus on loetelu k\u00f5ikidest buildidest, mis praegu k\u00e4ivitatakse, ja nende olekust: ootel, k\u00e4ivitatakse, kukkus v\u00f5i l\u00f5ppes. Buildile saab klikkida ja vaadata selle logi.<\/p>\n<p><img decoding=\"async\" alt=\"CI areng seente meeskonnas\" src=\"\/wp-content\/uploads\/2019\/04\/2d16b7a6b6d23e23903d291ef1526999.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\n<strong><em>Detailide tagasisidele kulus kuus n\u00e4dalat.<\/em><\/strong><\/p>\n<h2>Tunniplaanid<\/h2>\n<p>\nLiigume edasi uusima loo juurde. Tagasiside probleemi lahendamisega j\u00f5udsime uuele tasemele \u2014 otsustasime luua oma emulaatorite farmi. Kui teste ja emulaatoreid on palju, on nende haldamine keeruline. L\u00f5ppkokkuv\u00f5ttes on k\u00f5ik meie emulaatorid kolitud k8s-klastrisse paindliku ressursihaldusega.<\/p>\n<p>Lisaks on veel teisi plaane.<\/p>\n<ul>\n<li><strong>Tagasta Lint<\/strong> (ja muud staatilised anal\u00fc\u00fcsid). Me juba t\u00f6\u00f6tame selle suunas.<\/li>\n<li>K\u00e4ivitada PR-blokeerijana k\u00f5ik <strong>end-to-end testid<\/strong> k\u00f5ikide SDK versioonide jaoks.<\/li>\n<\/ul>\n<p>\nNii oleme j\u00e4lginud Continuous Integration'i 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 ettevaatlik shell-skriptide suhtes!<\/p><\/blockquote>\n<p>\nBash on v\u00e4ga paindlik ja v\u00f5imas t\u00f6\u00f6riist, sellel on mugav ja kiire skriptide kirjutamine. Kuid selle sisse v\u00f5ib langeda l\u00f5ksu, ja me, kahjuks, langesime sellesse.<\/p>\n<p>K\u00f5ik algas lihtsatest skriptidest, mida k\u00e4itati meie ehitusmasinatel:<\/p>\n<pre><code class=\"bash\">#!\/usr\/bin\/env bash\n.\/gradlew assembleDebug<\/code><\/pre>\n<p>\nKuid nagu teada, areng ja keerukus on ajaga laiemaks l\u00e4inud \u2014 laseme \u00fche skripti teise seest v\u00e4lja, edastame sinna mingid parameetrid \u2014 l\u00f5ppkokkuv\u00f5ttes pidime kirjutama funktsiooni, mis m\u00e4\u00e4rab, millisel bash-i s\u00fcgaval tasemel me parasjagu oleme, et sisestada \u00f5ige jutum\u00e4rk, et k\u00f5ik toimiks.<\/p>\n<p><img decoding=\"async\" alt=\"CI areng seente meeskonnas\" src=\"\/wp-content\/uploads\/2019\/04\/244a574f7b5d3b4fc77cfc4ccda2db69.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nKas saate ette kujutada nende skriptide arendamiseks vajalikke ressursse? Soovin mitte sattuda sellesse l\u00f5ksu.<\/p>\n<p>Mida saab kasutada asendajana?<\/p>\n<ul>\n<li>Iga skripti keelt. Kirjutamine <strong>Pythonis v\u00f5i Kotlin Scriptis<\/strong> on mugavam, sest see on programmeerimine, mitte skriptid.<\/li>\n<li>V\u00f5i kirjeldada kogu build-loogikat <strong>Custom gradle tasks<\/strong> teie projekti jaoks.<\/li>\n<\/ul>\n<p>\nMeie otsustasime valida teise v\u00f5imaluse ja praegu eemaldame j\u00e4rk-j\u00e4rgult k\u00f5ik bash-skriptid ning kirjutame palju kohandatud gradle-tasks.<\/p>\n<p><strong>n\u00f5uanne nr 2: hoida infrastruktuur koodina.<\/strong><\/p>\n<p>On mugav, kui pideva integreerimise seadistamine ei ole Jenkins, TeamCity jm kasutajaliidese kaudu, vaid tekstifailide kujul otse projekti hoidlas. See tagab versioonihalduse. Tagasiviimine v\u00f5i koodi kogumine teisel harul ei ole keeruline.<\/p>\n<p>Skripte saab hoida projektis. Aga mis teha keskkonnaga?<\/p>\n<p><strong>n\u00f5uanne nr 3: keskkonnas v\u00f5ib aidata Docker.<\/strong><\/p>\n<p>Androidi arendajatele on see kindlasti kasuks, iOSi jaoks kahjuks ei ole.<\/p>\n<p>See on n\u00e4ide lihtsast docker-failist, 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# Laadi 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# Paigalda Android Build Tool ja teegid\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 (salaja \u00fctlen, et seda ei ole vaja ise kirjutada, saab valmis v\u00f5tta GitHubist) ja pildi kokku panemisega saate virtuaalse masina, millel saate rakendust koostada ja Junit teste k\u00e4ivitada.<\/p>\n<p>Kaks peamist argumenti, miks see m\u00f5istlik on: skaleeritavus ja korduvus. Dockerit kasutades saab kiiresti \u00fcles t\u00f5sta k\u00fcmneid ehitusagensid, millel on t\u00e4pselt sama keskkond nagu eelneval. See teeb CI-inseneride elu oluliselt kergemaks. Android-sdk pakkimine Dockerisse on \u00fcsna lihtne, emulaatoritega on natuke keerulisem: tuleb veidi vaeva n\u00e4ha (v\u00f5i laadida GitHubist valmis lahendus).<\/p>\n<p><strong>N\u00f5uanne nr 4: \u00e4rge unustage, et kontrolle tehakse mitte kontrollide p\u00e4rast, vaid inimeste jaoks.<\/strong><\/p>\n<p>Arendajatele on v\u00e4ga oluline kiire ja mis k\u00f5ige t\u00e4htsam, arusaadav tagasiside: mis neil katki l\u00e4ks, milline test eba\u00f5nnestus, kus build-logi vaadata.<\/p>\n<p><strong>N\u00f5uanne nr 5: olge pragmaatilised pideva integratsiooni arendamisel.<\/strong><\/p>\n<p>Selgelt m\u00f5istke, milliseid t\u00f5rkeid soovite \u00e4ra hoida, kui palju olete valmis ressursse, aega ja masina aega kulutama. Liiga pikaajalised kontrollid v\u00f5ib n\u00e4iteks \u00f6\u00f6seks \u00fcle kanda. Ja nende seast, mis p\u00fc\u00fcavad mitte eriti olulisi vigu, tasub t\u00e4ielikult 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.<\/p>\n<p><img decoding=\"async\" alt=\"CI areng seente meeskonnas\" src=\"\/wp-content\/uploads\/2019\/04\/e57aabd49ec01d14e83b64331fd84ec5.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nV\u00e4ikeste meeskondade jaoks on see head lahendus. Pole vaja midagi toetada, lihtsalt maksate veidi raha, koguge oma rakendus ja isegi jooksutage instrumentatsiooni teste.<\/p>\n<p><strong>N\u00f5uanne nr 7: suure meeskonna jaoks on kasulikumad in-house lahendused.<\/strong><\/p>\n<p>Kuid varem v\u00f5i hiljem, meeskonna suurenedes, hakkavad in-house lahendused olema kasumlikumad. Nende lahendustega on \u00fcks aspekt. Majanduses on kahanemise seadus: igas projektis saab iga j\u00e4rgmise t\u00e4iustuse tegemine j\u00e4rjest keerulisemaks, n\u00f5udes \u00fcha rohkem investeeringuid.<\/p>\n<p>Majandus kirjeldab kogu meie elu, sealhulgas pidevat integreerimist. Olen koostanud diagrammi t\u00f6\u00f6j\u00f5ukulu kohta iga etapi kohta meie pideva integreerimise arendamisel.<\/p>\n<p><img decoding=\"async\" alt=\"CI areng seente meeskonnas\" src=\"\/wp-content\/uploads\/2019\/04\/97bf8bff64f3ac9ae587fae195251da2.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nOn n\u00e4ha, et iga t\u00e4iustuse saavutamine muutub j\u00e4rjest keerulisemaks. Seda diagrammi vaadates on v\u00f5imalik m\u00f5ista, et pidevat integreerimist tuleb arendada koos meeskonna suuruse kasvuga. Kahe inimese meeskond on 50 p\u00e4eva investeerimine sisemiste emulaatorite talu arendamisse \u00fcsna tobe idee. Kuid sama halb idee on ka suurte meeskondade puhul pideva integreerimisega mitte tegeleda, kuna integraatsiooni probleemide, suhtlemise parandamise jne tegelemine v\u00f5tab veel rohkem aega.<\/p>\n<p>Alustasime t\u00f5demusega, et automatiseerimine on vajalik, kuna inimesed on kallid, nad teevad vigu ja kipuvad olema laisikud. Kuid automatiseerimise teevad samuti inimesed. Seet\u00f5ttu kehtivad k\u00f5ik need probleemid ka automatiseerimise kohta.<\/p>\n<ul>\n<li>Automatiseerimine on kallis. Pidage meeles t\u00f6\u00f6j\u00f5u kulude graafikut.<\/li>\n<li>Automatiseerimise k\u00e4igus teevad inimesed vigu.<\/li>\n<li>M\u00f5nikord on automatiseerimine v\u00e4ga t\u00fc\u00fctu, kuna k\u00f5ik t\u00f6\u00f6tab niigi. Miks veel midagi parandada, miks kogu see pidev integreerimine?<\/li>\n<\/ul>\n<p>\nAga mul on statistika: 20%-l ehitustest tuvastatakse vigu. See ei juhtu mitte seet\u00f5ttu, et meie arendajad kirjutavad halba koodi. See toimub seet\u00f5ttu, et arendajad usuvad, et kui nad teevad m\u00f5ne vea, siis see ei p\u00e4\u00e4se develop'i. Automaatkontrollid p\u00fc\u00fcavad selle kinni. Seega saavad arendajad kulutada rohkem aega koodi kirjutamisele ja huvitavatele asjadele ning mitte midagi kohalikult jooksutada ja kontrollida.<\/p>\n<p><strong>Tegelege pideva integreerimisega. Kuid m\u00f5\u00f5dukalt.<\/strong><\/p>\n<blockquote><p>Muide, Nikolai Nesterov mitte ainult ei tee lahedaid ettekandeid, vaid kuulub ka programmikomiteesse, <noindex><a rel=\"nofollow\" href=\"https:\/\/appsconf.ru\/moscow\/2019\">AppsConf<\/a><\/noindex> ja aitab teistel valmistada teile sisukaid esinemisi. Tulevase konverentsi programmi t\u00e4ielikkust ja kasulikkust saab hinnata teemade j\u00e4rgi <noindex><a rel=\"nofollow\" href=\"https:\/\/appsconf.ru\/moscow\/2019\/schedule\">kava<\/a><\/noindex>. Ja \u00fcksikasjade saamiseks tulge 22.-23. aprillil Infopinda.<\/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.0.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. \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\" \/>\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.0.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. \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\" \/>\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\udd47 CI evolutsioon mobiilarenduse meeskonnas | ProHoster","description":"T\u00e4na arendatakse enamikku tarkvaratooteid meeskondades. Meeskonna arendamise edu tingimusi saab esitada lihtsa skeemina. Kood kirjutades peate veenduma, et see: t\u00f6\u00f6tab. Ei rikku midagi, sealhulgas kolleegide kirjutatud koodi. Kui m\u00f5lemad tingimused on t\u00e4idetud, olete edule l\u00e4hemal. Nende tingimuste lihtne kontrollimine ei h\u00e4iri teid.","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. \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","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}]}}