{"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\/ro\/blog\/administrirovanie\/evolyutsiya-ci-v-komande-mobilnoj-razrabotki","title":{"rendered":"Evolu\u021bia CI \u00een echipa de dezvoltare mobil\u0103","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Ast\u0103zi, majoritatea produselor software sunt dezvoltate \u00een echipe. Condi\u021biile de succes ale dezvolt\u0103rii de echip\u0103 pot fi reprezentate sub forma unei scheme simple.<\/p>\n<p><img decoding=\"async\" alt=\"Evolu\u021bia CI \u00een echipa de dezvoltare mobil\u0103\" src=\"\/wp-content\/uploads\/2019\/04\/b283cfd7772d8f479be0754ebbe51b19.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nDup\u0103 ce a\u021bi scris codul, trebuie s\u0103 v\u0103 asigura\u021bi c\u0103 acesta:<\/p>\n<ol>\n<li>Func\u021bioneaz\u0103.<\/li>\n<li>Nu rupe nimic, inclusiv codul scris de colegii dvs.<\/li>\n<\/ol>\n<p>\nDac\u0103 ambele condi\u021bii sunt \u00eendeplinite, atunci sunte\u021bi pe drumul cel bun. Pentru a verifica cu u\u0219urin\u021b\u0103 aceste condi\u021bii \u0219i a nu devia de la calea profitabil\u0103, a fost inventat Continuous Integration.<\/p>\n<p>CI este un flux de lucru \u00een care integra\u021bi codul dvs. \u00een codul produsului c\u00e2t mai des posibil. \u0218i nu doar l integra\u021bi, ci verifica\u021bi constant c\u0103 totul func\u021bioneaz\u0103. Deoarece trebuie s\u0103 face\u021bi multe teste frecvent, ar trebui s\u0103 lua\u021bi \u00een considerare automatizarea. Pute\u021bi verifica totul manual, dar nu ar trebui, \u0219i iat\u0103 de ce.<br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<ul>\n<li><strong>Oamenii sunt costisitori<\/strong>. O or\u0103 de munc\u0103 a oric\u0103rui programator cost\u0103 mai mult dec\u00e2t o or\u0103 de func\u021bionare a oric\u0103rui server.<\/li>\n<li><strong>Oamenii gre\u0219esc<\/strong>. De aceea pot ap\u0103rea situa\u021bii \u00een care a\u021bi rulat testele pe o ramur\u0103 gre\u0219it\u0103 sau a\u021bi construit un commit gre\u0219it pentru testerii de software.<\/li>\n<li><strong>Oamenii sunt lene\u0219i<\/strong>. Perioadele \u00een care termin o sarcin\u0103, \u00eemi apar \u00een minte g\u00e2nduri de genul: \u201eCe s\u0103 verific aici? Am scris doar dou\u0103 linii \u2013 cu siguran\u021b\u0103 func\u021bioneaz\u0103!\u201d Cred c\u0103 \u0219i unora dintre voi le trec astfel de g\u00e2nduri prin minte. Dar trebuie s\u0103 verifica\u021bi \u00eentotdeauna.<\/li>\n<\/ul>\n<p>\nCum a fost implementat \u0219i dezvoltat Continuous Integration \u00een echipa de dezvoltare mobil\u0103 Avito, cum au ajuns de la 0 la 450 de build-uri pe zi \u0219i ce ma\u0219ini de build realizeaz\u0103 200 de ore de munc\u0103 pe zi, ne poveste\u0219te Nikolai Nesterov (<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/users\/nnesterov\/\" class=\"user_link\">nnesterov<\/a><\/noindex>) \u2013 participant la toate schimb\u0103rile evolutive CI\/CD ale aplica\u021biei Android.<\/p>\n<p>Povestea este construit\u0103 pe exemplul echipei Android, dar majoritatea abord\u0103rilor sunt aplicabile \u0219i pe iOS.<\/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=\"Reda\u021bi video\" loading=\"lazy\" width=\"480\" height=\"360\" style=\"width:100%;height:auto;\">\r\n        <div class=\"play-button\"><\/div>\r\n    <\/div><\/center><br \/>\nCu mult timp \u00een urm\u0103, \u00een echipa Android de la Avito lucra o singur\u0103 persoan\u0103. Prin defini\u021bie, nu avea nevoie de nimic din Continuous Integration: nu se integra cu nimeni.<\/p>\n<p>Dar aplica\u021bia cre\u0219tea, ap\u0103rea tot mai multe sarcini noi, astfel c\u0103 echipa se m\u0103rea. La un moment dat, a venit vremea s\u0103 se organizeze mai formal procesul de integrare a codului. S-a decis utilizarea Git flow.<\/p>\n<p><img decoding=\"async\" alt=\"Evolu\u021bia CI \u00een echipa de dezvoltare mobil\u0103\" src=\"\/wp-content\/uploads\/2019\/04\/f433effc5ab5a59de3d6bd20a7a87057.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nConceptul Git flow este cunoscut: \u00een proiect exist\u0103 un singur branch comun develop, iar pentru fiecare nou\u0103 func\u021bionalitate, dezvoltatorii creeaz\u0103 un branch separat, commit-\u0103 \u00een el, \u00eemping codul \u0219i, c\u00e2nd doresc s\u0103 integreze codul \u00een branch-ul develop, deschid un pull request. Pentru schimbul de cuno\u0219tin\u021be \u0219i discutarea abord\u0103rilor, am introdus code review, adic\u0103 colegii trebuie s\u0103 verifice \u0219i s\u0103 confirme codul unii altora.<\/p>\n<h2>Verific\u0103ri<\/h2>\n<p>\nA te uita la cod este grozav, dar nu este suficient. De aceea, se introduc verific\u0103ri automate.<\/p>\n<ul>\n<li>\u00cen primul r\u00e2nd, verific\u0103m <strong>build-ul ARC<\/strong>.<\/li>\n<li>Multe <strong>teste Junit<\/strong>.<\/li>\n<li><strong>Calcul\u0103m code coverage<\/strong>, av\u00e2nd \u00een vedere c\u0103 pornim testele.<\/li>\n<\/ul>\n<p>\nPentru a \u00een\u021belege cum ar trebui s\u0103 ruleze aceste verific\u0103ri, s\u0103 ne uit\u0103m la procesul de dezvoltare de la Avito.<\/p>\n<p>Schematic, acesta poate fi reprezentat astfel:<\/p>\n<ul>\n<li>Dezvoltatorul scrie cod pe laptopul s\u0103u. Poate rula verific\u0103rile de integrare chiar aici \u2014 fie prin commit hook, fie pur \u0219i simplu rul\u00e2nd verific\u0103rile \u00een fundal.<\/li>\n<li>Dup\u0103 ce dezvoltatorul a \u00eempins codul, deschide un pull request. Pentru ca codul s\u0103 fie integrat \u00een branch-ul develop, trebuie s\u0103 treac\u0103 prin code review \u0219i s\u0103 ob\u021bin\u0103 suficiente aprob\u0103ri. Verific\u0103rile \u0219i build-urile pot fi activate aici: at\u00e2ta timp c\u00e2t nu toate build-urile sunt de succes, pull request-ul nu poate fi fuzionat.<\/li>\n<li>Dup\u0103 ce pull request-ul a fost fuzionat \u0219i codul a intrat \u00een develop, se poate alege un moment convenabil: de exemplu, noaptea, c\u00e2nd toate serverele sunt libere, \u0219i s\u0103 rul\u0103m verific\u0103rile c\u00e2t mai mult posibil.<\/li>\n<\/ul>\n<p>\nA rula verific\u0103rile pe laptopul s\u0103u nu a pl\u0103cut nim\u0103nui. C\u00e2nd dezvoltatorul a terminat func\u021bionalitatea, vrea s\u0103 o \u00eemping\u0103 c\u00e2t mai repede \u0219i s\u0103 deschid\u0103 un pull request. Dac\u0103 \u00een acel moment se desf\u0103\u0219oar\u0103 ni\u0219te verific\u0103ri de lung\u0103 durat\u0103, nu este doar incomod, ci \u0219i \u00eencetine\u0219te dezvoltarea: at\u00e2ta timp c\u00e2t laptopul face verific\u0103ri, nu este posibil s\u0103 lucreze normal.<\/p>\n<p>A rula verific\u0103rile noaptea ne-a pl\u0103cut foarte mult, deoarece sunt multe timp \u0219i servere, putem s\u0103 ne desf\u0103\u0219ur\u0103m. Dar, din p\u0103cate, c\u00e2nd codul func\u021bionalit\u0103\u021bii a intrat \u00een develop, dezvoltatorul are mult mai pu\u021bin\u0103 motiva\u021bie s\u0103 repare erorile g\u0103site de CI. Periodic m\u0103 surprindeam privind raportul matinal la toate erorile g\u0103site, g\u00e2ndindu-m\u0103 c\u0103 le voi repara mai t\u00e2rziu, pentru c\u0103 acum \u00een Jira este o sarcin\u0103 nou\u0103 grozav\u0103 pe care abia a\u0219tept s\u0103 o \u00eencep.<\/p>\n<p>Dac\u0103 verific\u0103rile blocheaz\u0103 pull request-ul, atunci motiva\u021bia este suficient\u0103, deoarece at\u00e2ta timp c\u00e2t build-urile nu sunt verzi, codul nu va ajunge \u00een develop, iar, prin urmare, sarcina nu va fi finalizat\u0103.<\/p>\n<p>\u00cen final, am ales aceast\u0103 strategie: noaptea execut\u0103m c\u00e2t mai multe teste posibil, iar cele mai critice dintre acestea, \u0219i cel mai important, cele rapide, le lans\u0103m la pull request. Dar nu ne oprim aici \u2014 \u00een paralel optimiz\u0103m viteza trecerii testelor pentru a le transforma din mod nocturn \u00een teste pentru pull request.<\/p>\n<p>La acel moment, toate construc\u021biile noastre treceau destul de repede, a\u0219a c\u0103 am inclus pur \u0219i simplu construc\u021bia ARC, testele Junit \u0219i calculul acoperirii codului ca blocator la pull request. Am inclus, am reflectat \u2014 \u0219i ne-am r\u0103zg\u00e2ndit \u00een leg\u0103tur\u0103 cu acoperirea codului, deoarece am considerat c\u0103 nu avem nevoie de ea.<\/p>\n<p><strong><em>Configura\u021bia ini\u021bial\u0103 a CI-ului ne-a luat dou\u0103 zile (aceasta \u0219i urm\u0103toarele estim\u0103ri de timp sunt aproximative, necesare pentru context). <\/em><\/strong><\/p>\n<p>Dup\u0103 aceea, ne-am g\u00e2ndit mai departe \u2014 verific\u0103m corect? Rul\u0103m corect construc\u021biile la pull request?<\/p>\n<p>Noi rulam construc\u021bia pe ultimul commit al ramurii din care a fost deschis pull request-ul. Dar testele acestui commit pot ar\u0103ta doar c\u0103 codul scris de dezvoltator func\u021bioneaz\u0103. Dar ele nu demonstreaz\u0103 c\u0103 n-a stricat nimic. De fapt, trebuie s\u0103 verific\u0103m starea ramurii develop dup\u0103 ce func\u021bionalitatea a fost integrat\u0103.<\/p>\n<p><img decoding=\"async\" alt=\"Evolu\u021bia CI \u00een echipa de dezvoltare mobil\u0103\" src=\"\/wp-content\/uploads\/2019\/04\/c1a179b68b02c04031177f9bf51a81ed.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nPentru aceasta, am scris un script bash simplu <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>\nAici sunt preluate cele mai recente modific\u0103ri din develop \u0219i integrate \u00een ramura curent\u0103. Am ad\u0103ugat scriptul premerge.sh ca primul pas \u00een toate construc\u021biile \u0219i am \u00eenceput s\u0103 verific\u0103m exact ceea ce dorim, adic\u0103 <strong>integrarea<\/strong>.<\/p>\n<p><strong><em>Pentru localizarea problemelor, g\u0103sirea solu\u021biilor \u0219i scrierea acestui script ne-au luat trei zile.<\/em><\/strong><\/p>\n<p>Aplica\u021bia evolua, ap\u0103r\u00e2nd din ce \u00een ce mai multe sarcini \u0219i echipa cre\u0219tea, iar premerge.sh a \u00eenceput s\u0103 ne fac\u0103 uneori probleme. Modific\u0103rile conflicting p\u0103trundeau \u00een develop, ceea ce rupea construc\u021biile.<\/p>\n<p>Un exemplu despre cum se \u00eent\u00e2mpl\u0103 acest lucru:<\/p>\n<p><img decoding=\"async\" alt=\"Evolu\u021bia CI \u00een echipa de dezvoltare mobil\u0103\" src=\"\/wp-content\/uploads\/2019\/04\/6762d9ce4e431549455c49f2317a8f20.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nDoi dezvoltatori \u00eencep simultan s\u0103 lucreze la func\u021bionalit\u0103\u021bile A \u0219i B. Dezvoltatorul func\u021bionalit\u0103\u021bii A descoper\u0103 \u00eentr-o func\u021bie nefolosit\u0103 \u00een proiect <code>answer()<\/code> \u0219i, ca un bun scout, o elimin\u0103. \u00cen acela\u0219i timp, dezvoltatorul func\u021bionalit\u0103\u021bii B adaug\u0103 un nou apel al acestei func\u021bii \u00een ramura sa.<\/p>\n<p>Dezvoltatorii termin\u0103 lucrul \u0219i deschid simultan pull request-uri. Se lanseaz\u0103 construc\u021biile, premerge.sh verific\u0103 ambele pull request-uri \u00een raport cu starea recent\u0103 a lui develop \u2014 toate verific\u0103rile sunt verzi. Dup\u0103 aceea, se face merge pe pull request-ul func\u021bionalit\u0103\u021bii A, se face merge pe pull request-ul func\u021bionalit\u0103\u021bii B\u2026 Bam! Develop se stric\u0103, deoarece \u00een codul develop exist\u0103 un apel la o func\u021bie care nu mai exist\u0103.<\/p>\n<p><img decoding=\"async\" alt=\"Evolu\u021bia CI \u00een echipa de dezvoltare mobil\u0103\" src=\"\/wp-content\/uploads\/2019\/04\/0261dcc9f7f2e5e018ab136081b4679b.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nC\u00e2nd develop nu se compileaz\u0103, aceasta <strong>catastrof\u0103 local\u0103<\/strong>. \u00centreaga echip\u0103 nu poate aduna nimic \u0219i trimite la testare.<\/p>\n<p>A\u0219a s-a \u00eent\u00e2mplat c\u0103 m-am ocupat cel mai des de sarcini de infrastructur\u0103: analiz\u0103, re\u021bea, baze de date. Adic\u0103 eu am scris acele func\u021bii \u0219i clase pe care le folosesc al\u021bi dezvoltatori. Din acest motiv, m-am aflat foarte des \u00een astfel de situa\u021bii. Chiar aveam o imagine care era afi\u0219at\u0103 o perioad\u0103 de timp.<\/p>\n<p><img decoding=\"async\" alt=\"Evolu\u021bia CI \u00een echipa de dezvoltare mobil\u0103\" src=\"\/wp-content\/uploads\/2019\/04\/0eb1aadd05b33ce995027ab52d5c1e53.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nDeoarece asta nu ne mul\u021bumea, am \u00eenceput s\u0103 analiz\u0103m op\u021biuni pentru a preveni asta.<\/p>\n<h2>Cum s\u0103 nu stric\u0103m develop<\/h2>\n<p>\nPrima op\u021biune: <strong>reconstruirea tuturor pull request-urilor la actualizarea develop. <\/strong>Dac\u0103 \u00een exemplul nostru pull request-ul cu caracteristica A ajunge primul \u00een develop, pull request-ul caracteristicii B va trebui reconstruit \u0219i, din acest motiv, verific\u0103rile nu vor trece din cauza unei erori de compilare.<\/p>\n<p>Pentru a \u00een\u021belege c\u00e2t timp va dura, s\u0103 lu\u0103m un exemplu cu dou\u0103 PR-uri. Deschidem dou\u0103 PR-uri: dou\u0103 build-uri, dou\u0103 runde de verific\u0103ri. Dup\u0103 ce primul PR este fuzionat \u00een develop, al doilea trebuie reconstruit. A\u0219adar, pentru dou\u0103 PR-uri, avem trei runde de verific\u0103ri: 2 + 1 = 3.<\/p>\n<p>\u00cen principiu, este acceptabil. Dar ne-am uitat la statistic\u0103 \u0219i situa\u021bia tipic\u0103 \u00een echipa noastr\u0103 era de 10 PR-uri deschise, iar atunci num\u0103rul de verific\u0103ri ar fi suma progresiei: 10 + 9 +\u2026 + 1 = 55. Deci, pentru a accepta 10 PR-uri, trebuie reconstruit de 55 de ori. \u0218i asta \u00een situa\u021bia ideal\u0103, c\u00e2nd toate verific\u0103rile trec din prima, iar nimeni nu deschide un alt pull request \u00een timp ce se proceseaz\u0103 acest grup de zece.<\/p>\n<p>Imagina\u021bi-v\u0103 c\u0103 sunte\u021bi dezvoltator \u0219i trebuie s\u0103 reu\u0219i\u021bi s\u0103 ap\u0103sa\u021bi butonul \u201emerge\u201d primul, pentru c\u0103 dac\u0103 o face vecinul, va trebui s\u0103 a\u0219tepta\u021bi p\u00e2n\u0103 c\u00e2nd toate build-urile trec din nou\u2026 Nu, a\u0219a nu merge, asta va \u00eencetini serios dezvoltarea.<\/p>\n<p>A doua op\u021biune posibil\u0103: <strong>s\u0103 construim pull request-ul dup\u0103 revizuirea codului. <\/strong>Adic\u0103 deschide\u021bi pull request-ul, ob\u021bine\u021bi num\u0103rul necesar de aprobat de la colegi, corecta\u021bi ce trebuie, dup\u0103 care lansa\u021bi build-urile. Dac\u0103 acestea sunt de succes, pull request-ul este fuzionat cu develop. \u00cen acest caz, nu sunt necesare reconstruc\u021bii suplimentare, dar feedback-ul este \u00eencetinit semnificativ. Eu, ca dezvoltator, deschiz\u00e2nd un pull request, vreau s\u0103 v\u0103d imediat dac\u0103 se construie\u0219te. De exemplu, dac\u0103 un test a picat, trebuie reparat rapid. \u00cen cazul unei construc\u021bii am\u00e2nate, feedback-ul este \u00eencetinit, ceea ce \u00eenseamn\u0103 c\u0103 \u00eentreaga dezvoltare este afectat\u0103. Asta nu ne mul\u021bumea nici pe noi.<\/p>\n<p>\u00cen cele din urm\u0103, a r\u0103mas doar a treia op\u021biune \u2014 <strong>a bicicleta<\/strong>. Tot codul nostru, toate sursele noastre sunt stocate \u00eentr-un repository pe serverul Bitbucket. Prin urmare, a trebuit s\u0103 dezvolt\u0103m un plugin pentru Bitbucket.<\/p>\n<p><img decoding=\"async\" alt=\"Evolu\u021bia CI \u00een echipa de dezvoltare mobil\u0103\" src=\"\/wp-content\/uploads\/2019\/04\/5e4b1f770ea1a3449b616c3101640294.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nAcest plugin redefine\u0219te mecanismul de fuziune a cererilor de pull. \u00cenceperea este standard: se deschide un PR, se ruleaz\u0103 toate construc\u021biile, se face revizuirea codului. Dar dup\u0103 ce revizuirea codului este finalizat\u0103 \u0219i dezvoltatorul decide s\u0103 apese pe \"merge\", pluginul verific\u0103 \u00een raport cu ce stare a dezvolt\u0103rii au fost efectuate testele. Dac\u0103 dup\u0103 construc\u021bii, dezvoltarea s-a actualizat, pluginul nu va permite fuzionarea acelei cereri de pull \u00een ramura principal\u0103. Pur \u0219i simplu va reporni construc\u021biile \u00een raport cu o dezvoltare proasp\u0103t\u0103.<\/p>\n<p><img decoding=\"async\" alt=\"Evolu\u021bia CI \u00een echipa de dezvoltare mobil\u0103\" src=\"\/wp-content\/uploads\/2019\/04\/0edee880e1f2d286a0c64033103ac136.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\n\u00cen exemplul nostru cu modific\u0103ri conflictuale, aceste build-uri nu vor trece din cauza unei erori de compilare. Prin urmare, dezvoltatorul func\u021biei B va trebui s\u0103 corecteze codul, s\u0103 reporneasc\u0103 verific\u0103rile, iar pluginul va aplica automat pull request-ul.<\/p>\n<p>\u00cenainte de implementarea acestui plugin, aveam \u00een medie 2.7 lans\u0103ri de verific\u0103ri pentru un pull request. Cu pluginul, am ajuns la 3.6 lans\u0103ri. Ne-am mul\u021bumit cu aceasta.<\/p>\n<p>Este de men\u021bionat c\u0103 acest plugin are un dezavantaj: reporne\u0219te build-ul doar o singur\u0103 dat\u0103. Deci, tot r\u0103m\u00e2ne o mic\u0103 fereastr\u0103 prin care modific\u0103ri conflictuale pot ajunge \u00een develop. Dar probabilitatea acestuia nu este mare, \u0219i am acceptat acest compromis \u00eentre num\u0103rul de lans\u0103ri \u0219i probabilitatea de a provoca o defec\u021biune. \u00cen dou\u0103 ani, a avut loc doar o singur\u0103 dat\u0103, deci, probabil, nu degeaba.<\/p>\n<p><strong><em>Scrierea primei versiuni a pluginului pentru Bitbucket ne-a luat dou\u0103 s\u0103pt\u0103m\u00e2ni. <\/em><\/strong><\/p>\n<h3>Verific\u0103ri noi<\/h3>\n<p>\n\u00centre timp, echipa noastr\u0103 a continuat s\u0103 creasc\u0103. S-au ad\u0103ugat noi verific\u0103ri.<\/p>\n<p>Ne-am g\u00e2ndit: de ce s\u0103 repar\u0103m erorile, c\u00e2nd le putem preveni? A\u0219a c\u0103 am implementat <strong>static\u0103 a codului<\/strong>. Am \u00eenceput cu lint, care face parte din Android SDK. Dar \u00een acea vreme nu func\u021biona deloc cu codul Kotlin, iar 75% din aplica\u021bie era deja scris\u0103 \u00een Kotlin. A\u0219a c\u0103 am ad\u0103ugat la lint verific\u0103rile \u00eencorporate din <strong>Android Studio.<\/strong><\/p>\n<p>Pentru aceasta, a fost necesar s\u0103 ne \u00eengrijim pu\u021bin: s\u0103 lu\u0103m Android Studio, s\u0103 o ambal\u0103m \u00een Docker \u0219i s\u0103 o rul\u0103m pe CI cu un monitor virtual, pentru ca aceasta s\u0103 cread\u0103 c\u0103 este rulat\u0103 pe un laptop real. Dar a func\u021bionat.<\/p>\n<p>De asemenea, \u00een aceast\u0103 perioad\u0103 am \u00eenceput s\u0103 scriem multe <strong>teste de instrumenta\u021bie<\/strong> \u0219i am implementat <strong>testarea prin capturi de ecran.<\/strong>. Acesta este momentul \u00een care se genereaz\u0103 o captur\u0103 de ecran de referin\u021b\u0103 pentru o mic\u0103 vizualizare separat\u0103, iar testul const\u0103 \u00een a face o captur\u0103 de ecran de pe vizualizare \u0219i a o compara pixel cu pixel cu referin\u021ba. Dac\u0103 exist\u0103 abateri, \u00eenseamn\u0103 c\u0103 designul s-a stricat \u00een vreun fel sau c\u0103 stilurile nu sunt corecte.<\/p>\n<p>Dar testele de instrumentare \u0219i testele de captur\u0103 de ecran trebuie s\u0103 fie rulate pe dispozitive: pe emulatori sau pe dispozitive reale. Av\u00e2nd \u00een vedere c\u0103 sunt multe teste \u0219i sunt rulate frecvent, este nevoie de o \u00eentreag\u0103 ferm\u0103. A \u00eenfiin\u021ba propria ferm\u0103 este prea costisitor, a\u0219a c\u0103 am g\u0103sit o op\u021biune gata f\u0103cut\u0103 \u2014 Firebase Test Lab.<\/p>\n<h3>Firebase Test Lab<\/h3>\n<p>\nA fost ales pentru c\u0103 Firebase este un produs Google, ceea ce \u00eenseamn\u0103 c\u0103 ar trebui s\u0103 fie de \u00eencredere \u0219i c\u0103 este pu\u021bin probabil s\u0103 dispar\u0103 vreodat\u0103. Pre\u021burile sunt accesibile: 5$ pe or\u0103 pentru utilizarea unui dispozitiv real, 1$ pe or\u0103 pentru utilizarea unui emulator.<\/p>\n<p><strong><em>Implementarea Firebase Test Lab \u00een CI-ul nostru a durat aproximativ trei s\u0103pt\u0103m\u00e2ni.<\/em><\/strong><\/p>\n<p>Dar echipa a continuat s\u0103 creasc\u0103, iar Firebase, din p\u0103cate, a \u00eenceput s\u0103 ne dezam\u0103geasc\u0103. La acel moment nu avea niciun SLA. Uneori Firebase ne f\u0103cea s\u0103 a\u0219tept\u0103m p\u00e2n\u0103 c\u00e2nd erau disponibile suficiente dispozitive pentru teste, \u0219i nu \u00eencepea s\u0103 le execute imediat, a\u0219a cum ne doream. A\u0219teptarea \u00een coad\u0103 dura p\u00e2n\u0103 la jum\u0103tate de or\u0103, iar asta este foarte mult. Testele de instrumentare erau rulate la fiecare PR, \u00eent\u00e2rzierile \u00eencetineau foarte mult dezvoltarea, iar apoi a venit factura pe luna cu o sum\u0103 rotund\u0103. \u00cen general, s-a decis s\u0103 renun\u021b\u0103m la Firebase \u0219i s\u0103 dezvolt\u0103m intern, av\u00e2nd \u00een vedere c\u0103 echipa a crescut destul de mult.<\/p>\n<h3>Docker + Python + bash<\/h3>\n<p>\nAm folosit docker, am bagat emulatorii \u00een el, am scris un program simplu \u00een Python care la momentul potrivit ridic\u0103 num\u0103rul necesar de emulatori \u00een versiunea dorit\u0103 \u0219i c\u00e2nd trebuie, \u00eei opre\u0219te. \u0218i, bine\u00een\u021beles, c\u00e2teva scripturi bash \u2014 cum altfel?<\/p>\n<p><strong><em>Crearea propriului mediu de testare a durat cinci s\u0103pt\u0103m\u00e2ni.<\/em><\/strong><\/p>\n<p>Ca urmare, pentru fiecare pull request existau o serie extins\u0103, blocant\u0103 a fuzion\u0103rii, de verific\u0103ri:<\/p>\n<ul>\n<li>Construirea ARC;<\/li>\n<li>Testele Junit;<\/li>\n<li>Lint;<\/li>\n<li>Verific\u0103rile Android Studio;<\/li>\n<li>Teste de instrumentare;<\/li>\n<li>Teste de captur\u0103 de ecran.<\/li>\n<\/ul>\n<p>\nAcest lucru prevenea multe posibile defecte. Tehnic, totul func\u021biona, dar dezvoltatorii se pl\u00e2ngeau c\u0103 a\u0219teptarea rezultatelor dureaz\u0103 prea mult.<\/p>\n<p>Prea mult \u2014 c\u00e2t de mult? Am extras datele din Bitbucket \u0219i TeamCity \u00een sistemul de analiz\u0103 \u0219i am realizat c\u0103 <strong>timpul mediu de a\u0219teptare este de 45 de minute<\/strong>. Deci, dezvoltatorul, atunci c\u00e2nd deschide un pull request, a\u0219teapt\u0103 \u00een medie 45 de minute pentru rezultatele construc\u021biilor. Din punctul meu de vedere, acesta este foarte mult, \u0219i nu se poate lucra astfel.<\/p>\n<p>Desigur, am decis s\u0103 acceler\u0103m toate construc\u021biile noastre.<\/p>\n<h2>Acceler\u0103m<\/h2>\n<p>\nObserv\u00e2nd c\u0103 adesea build-urile a\u0219teptau, am decis mai \u00eent\u00e2i <strong>s\u0103 ad\u0103ug\u0103m hardware<\/strong> \u2014 dezvoltarea extensiv\u0103 este cea mai simpl\u0103. Build-urile nu mai a\u0219teapt\u0103, dar timpul de a\u0219teptare a sc\u0103zut doar pu\u021bin, deoarece unele verific\u0103ri dureaz\u0103 foarte mult.<\/p>\n<h3>Elimin\u0103m verific\u0103rile prea lungi<\/h3>\n<p>\nCI-ul nostru poate s\u0103 identifice astfel de tipuri de erori \u0219i probleme.<\/p>\n<ul>\n<li><strong>Nu se compileaz\u0103<\/strong>. CI poate prinde o eroare de compilare c\u00e2nd din cauza modific\u0103rilor conflictuale ceva nu se compileaz\u0103. A\u0219a cum am spus deja, atunci nimeni nu poate s\u0103 compileze nimic, dezvoltarea se opre\u0219te \u0219i toat\u0103 lumea devine nelini\u0219tit\u0103.<\/li>\n<li><strong>Bug \u00een comportament<\/strong>. De exemplu, c\u00e2nd aplica\u021bia se compileaz\u0103, dar la ap\u0103sarea butonului se blocheaz\u0103, sau butonul nu r\u0103spunde deloc. Aceasta este o problem\u0103 serioas\u0103, deoarece un astfel de bug poate ajunge la utilizator.<\/li>\n<li><strong>Bug \u00een design<\/strong>. De exemplu, butonul r\u0103spunde, dar s-a mutat cu 10 pixeli spre st\u00e2nga.<\/li>\n<li><strong>Cre\u0219terea datoriei tehnice<\/strong>.<\/li>\n<\/ul>\n<p>\nPrivind aceast\u0103 list\u0103, ne-am dat seama c\u0103 doar primele dou\u0103 puncte sunt critice. Astfel de probleme dorim s\u0103 le identific\u0103m prioritare. Bug-urile \u00een design sunt descoperite \u00een etapa de revizuire a designului \u0219i atunci pot fi corectate u\u0219or. Lucrul cu datoriile tehnice necesit\u0103 un proces \u0219i o planificare separate, de aceea am decis s\u0103 nu le verific\u0103m \u00een pull request.<\/p>\n<p>Pe baza acestei clasific\u0103ri, am revizuit \u00eentreaga list\u0103 de verific\u0103ri. <strong>Am exclus Lint<\/strong> \u0219i am mutat rularea lui pe timpul nop\u021bii: doar pentru a emite un raport despre c\u00e2te probleme sunt \u00een proiect. Am decis s\u0103 lucr\u0103m separat cu datoria tehnic\u0103, iar <strong>ne-am abandonat complet verific\u0103rile Android Studio<\/strong>. Android Studio \u00een Docker pentru a rula inspec\u021biile sun\u0103 interesant, dar ofer\u0103 multe nepl\u0103ceri \u00een mentenan\u021b\u0103. Orice actualizare a versiunilor Android Studio este o lupt\u0103 cu bug-uri neclare. De asemenea, a fost dificil s\u0103 men\u021binem teste de screenshot, deoarece biblioteca nu func\u021biona foarte stabil, existau false alarme. <strong>Testele de screenshot au fost eliminate din lista de verific\u0103ri<\/strong>.<\/p>\n<p>\u00cen final, ne-au r\u0103mas:<\/p>\n<ul>\n<li>Construirea ARC;<\/li>\n<li>Testele Junit;<\/li>\n<li>Teste de instrumenta\u021bie.<\/li>\n<\/ul>\n<h3>Cache-ul remote Gradle<\/h3>\n<p>\nF\u0103r\u0103 verific\u0103ri grele, totul a devenit mai bine. Dar nu exist\u0103 limit\u0103 pentru perfec\u021biune!<\/p>\n<p>Aplica\u021bia noastr\u0103 era deja \u00eemp\u0103r\u021bit\u0103 \u00een aproximativ 150 de module gradle. De obicei, \u00een astfel de cazuri, cache-ul remote Gradle func\u021bioneaz\u0103 bine, a\u0219a c\u0103 am decis s\u0103-l \u00eencerc\u0103m.<\/p>\n<p>Gradle remote cache este un serviciu care poate stoca artefacte de construc\u021bie pentru sarcini specifice \u00een module separate. Gradle, \u00een loc s\u0103 compileze efectiv codul, face o cerere HTTP la remote cache \u0219i \u00eentreab\u0103 dac\u0103 cineva a mai executat deja aceast\u0103 sarcin\u0103. Dac\u0103 da, pur \u0219i simplu descarc\u0103 rezultatul.<\/p>\n<p><strong><em>Pornirea Gradle remote cache este u\u0219oar\u0103, deoarece Gradle ofer\u0103 o imagine Docker. Am reu\u0219it s\u0103 facem asta \u00een trei ore.<\/em><\/strong><\/p>\n<p>A fost suficient s\u0103 pornim Docker \u0219i s\u0103 ad\u0103ug\u0103m o linie \u00een proiect. Dar, de\u0219i poate fi lansat rapid, pentru ca totul s\u0103 func\u021bioneze bine, va fi nevoie de ceva timp.<\/p>\n<p>Mai jos este grafica privind ratele de rat\u0103 a cache-ului.<\/p>\n<p><img decoding=\"async\" alt=\"Evolu\u021bia CI \u00een echipa de dezvoltare mobil\u0103\" src=\"\/wp-content\/uploads\/2019\/04\/458ef23b5506b04f3bd385be0c18607a.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nLa \u00eenceput, procentul ratelor de rat\u0103 a cache-ului era de aproximativ 65%. Dup\u0103 trei s\u0103pt\u0103m\u00e2ni, am reu\u0219it s\u0103 reducem aceast\u0103 valoare la 20%. S-a dovedit c\u0103 sarcinile pe care le compileaz\u0103 aplica\u021bia Android au dependen\u021be tranzitive ciudate, din cauza c\u0103rora Gradle a ratat cache-ul.<\/p>\n<p>Prin activarea cache-ului, am accelerat semnificativ construc\u021bia. Dar, \u00een afar\u0103 de construc\u021bie, se ruleaz\u0103 \u0219i teste de instrumentare, care dureaz\u0103 mult. Poate c\u0103 nu toate testele trebuie rulate pentru fiecare pull request. Pentru a determina acest lucru, folosim analiza impactului.<\/p>\n<h3>Analiza impactului<\/h3>\n<p>\nPe pull request, colect\u0103m git diff \u0219i g\u0103sim modulele Gradle modificate.<\/p>\n<p><img decoding=\"async\" alt=\"Evolu\u021bia CI \u00een echipa de dezvoltare mobil\u0103\" src=\"\/wp-content\/uploads\/2019\/04\/471ca37206a0da4d5741c8ee1dbb7f03.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nAre sens s\u0103 rul\u0103m doar testele de instrumentare care verific\u0103 modulele modificate \u0219i toate modulele care depind de acestea. Nu are sens s\u0103 rul\u0103m teste pentru modulele vecine: acolo codul nu s-a schimbat \u0219i nimic nu se poate defecta.<\/p>\n<p>Cu testele de instrumentare lucrurile nu sunt at\u00e2t de simple, deoarece ele trebuie s\u0103 fie \u00een modulul cel mai de sus Application. Am aplicat o euristic\u0103 cu analiza bytecode-ului pentru a \u00een\u021belege la ce modul se refer\u0103 fiecare test.<\/p>\n<p><strong><em>Modernizarea func\u021bion\u0103rii testelor de instrumentare, astfel \u00eenc\u00e2t s\u0103 verifice doar modulele implicate, a durat aproximativ opt s\u0103pt\u0103m\u00e2ni.<\/em><\/strong><\/p>\n<p>M\u0103surile de accelerare a verific\u0103rilor au fost eficiente. De la 45 de minute am ajuns la aproximativ 15. A\u0219teptarea unui build timp de un sfert de or\u0103 este deja acceptabil\u0103.<\/p>\n<p>Dar acum dezvoltatorii au \u00eenceput s\u0103 se pl\u00e2ng\u0103 c\u0103 nu este clar ce builds sunt lansate, unde pot vedea logurile, de ce build-ul este ro\u0219u, care test a picat etc.<\/p>\n<p><img decoding=\"async\" alt=\"Evolu\u021bia CI \u00een echipa de dezvoltare mobil\u0103\" src=\"\/wp-content\/uploads\/2019\/04\/aa76151174cd4e5a45ff281818e312f5.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nProblemele cu feedback-ul \u00eencetinesc dezvoltarea, a\u0219a c\u0103 ne-am str\u0103duit s\u0103 oferim cele mai clare \u0219i detaliate informa\u021bii despre fiecare PR \u0219i build. Am \u00eenceput cu comentarii \u00een Bitbucket pentru PR, specific\u00e2nd care build a picat \u0219i de ce, \u0219i am trimis mesaje pe Slack. \u00cen cele din urm\u0103, am creat o pagin\u0103 dashboard pentru PR cu o list\u0103 a tuturor build-urilor care sunt \u00een curs de rulare \u0219i starea lor: \u00een a\u0219teptare, \u00een rulare, picat sau finalizat. Pute\u021bi face clic pe build pentru a accesa log-ul acestuia.<\/p>\n<p><img decoding=\"async\" alt=\"Evolu\u021bia CI \u00een echipa de dezvoltare mobil\u0103\" src=\"\/wp-content\/uploads\/2019\/04\/2d16b7a6b6d23e23903d291ef1526999.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\n<strong><em>A fost necesar\u0103 o perioad\u0103 de \u0219ase s\u0103pt\u0103m\u00e2ni pentru feedback detaliat.<\/em><\/strong><\/p>\n<h2>Planuri<\/h2>\n<p>\nS\u0103 trecem la cele mai recente evenimente. Odat\u0103 ce am rezolvat problema feedback-ului, am ajuns la un nou nivel \u2014 am decis s\u0103 construim propria ferm\u0103 de emulatori. C\u00e2nd sunt multe teste \u0219i emulatori, devine greu de gestionat. \u00cen cele din urm\u0103, toate emulatorii no\u0219tri s-au mutat \u00eentr-un cluster k8s cu gestionare flexibil\u0103 a resurselor.<\/p>\n<p>\u00cen plus, avem \u0219i alte planuri.<\/p>\n<ul>\n<li><strong>Restorarea Lint<\/strong> (\u0219i alte analize statice). Lucr\u0103m deja \u00een aceast\u0103 direc\u021bie.<\/li>\n<li>S\u0103 rul\u0103m toate <strong>testele end-to-end<\/strong> pe toate versiunile SDK.<\/li>\n<\/ul>\n<p>\nAstfel, am urm\u0103rit dezvoltarea Continuous Integration \u00een Avito. Acum vreau s\u0103 ofer c\u00e2teva sfaturi din perspectiva unui veteran.<\/p>\n<h1>Sfaturi<\/h1>\n<p>\nDac\u0103 ar fi s\u0103 dau doar un singur sfat, acesta ar fi:<\/p>\n<blockquote><p>V\u0103 rog, fi\u021bi mai aten\u021bi cu scripturile shell!<\/p><\/blockquote>\n<p>\nBash este un instrument foarte flexibil \u0219i puternic, este foarte convenabil \u0219i rapid pentru a scrie scripturi. Dar poate fi o capcan\u0103, \u0219i, din p\u0103cate, am c\u0103zut \u00een ea.<\/p>\n<p>Totul a \u00eenceput cu scripturi simple care erau rulate pe ma\u0219inile noastre de build:<\/p>\n<pre><code class=\"bash\">#!\/usr\/bin\/env bash\n.\/gradlew assembleDebug<\/code><\/pre>\n<p>\nDar, dup\u0103 cum se \u0219tie, totul se dezvolt\u0103 \u0219i devine mai complex \u2014 hai s\u0103 rul\u0103m un script din altul, hai s\u0103-i transmitem c\u00e2teva parametrii \u2014 \u00een cele din urm\u0103, a trebuit s\u0103 scriu o func\u021bie care determin\u0103 nivelul de imbricare bash \u00een care ne afl\u0103m, pentru a pune ghilimelele corecte ca s\u0103 func\u021bioneze totul.<\/p>\n<p><img decoding=\"async\" alt=\"Evolu\u021bia CI \u00een echipa de dezvoltare mobil\u0103\" src=\"\/wp-content\/uploads\/2019\/04\/244a574f7b5d3b4fc77cfc4ccda2db69.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\n\u00ce\u021bi po\u021bi imagina eforturile necesare pentru dezvoltarea unor astfel de scripturi. \u00ce\u021bi recomand s\u0103 nu cazi \u00een aceast\u0103 capcan\u0103.<\/p>\n<p>Cu ce poate fi \u00eenlocuit?<\/p>\n<ul>\n<li>Orice limbaj de scriptare. Este mai convenabil s\u0103 scrii \u00een <strong>Python sau Kotlin Script<\/strong> pentru c\u0103 este programare, nu scripturi.<\/li>\n<li>Sau po\u021bi descrie \u00eentreaga logic\u0103 a build-urilor sub form\u0103 de <strong>sarci gradle personalizate<\/strong> pentru proiectul t\u0103u.<\/li>\n<\/ul>\n<p>\nAm decis s\u0103 alegem a doua op\u021biune \u0219i acum elimin\u0103m sistematic toate scripturile bash \u0219i scriem multe sarcini gradle personalizate.<\/p>\n<p><strong>Sfat nr. 2: p\u0103stra\u021bi infrastructura \u00een cod.<\/strong><\/p>\n<p>Este este convenabil c\u00e2nd configura\u021bia Continuous Integration nu este stocat\u0103 \u00een interfa\u021ba UI a Jenkins sau TeamCity, ci sub form\u0103 de fi\u0219iere text direct \u00een depozitul proiectului. Acest lucru ofer\u0103 versiune. Nu va fi greu s\u0103 reveni\u021bi sau s\u0103 compila\u021bi codul pe o alt\u0103 ramur\u0103.<\/p>\n<p>Scripturile pot fi stocate \u00een proiect. Dar ce facem cu mediu?<\/p>\n<p><strong>Sfatul nr. 3: Docker poate ajuta cu mediu.<\/strong><\/p>\n<p>Cu siguran\u021b\u0103 le va fi de ajutor dezvoltatorilor Android, din p\u0103cate nu \u0219i celor de la iOS.<\/p>\n<p>Acesta este un exemplu de fi\u0219ier Docker simplu, care con\u021bine jdk \u0219i 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# Descarc\u0103 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# Instaleaz\u0103 Android Build Tool \u0219i Biblioteci\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>\nAm scris acest fi\u0219ier docker (\u00ee\u021bi spun \u00een secret, nu e nevoie s\u0103-l scrii, ci po\u021bi desc\u0103rca unul gata de pe GitHub) \u0219i, construind imaginea, ob\u021bii o ma\u0219in\u0103 virtual\u0103, pe care po\u021bi construi aplica\u021bia \u0219i rula teste Junit.<\/p>\n<p>Cele dou\u0103 argumente principale pentru care are sens: scalabilitate \u0219i repetabilitate. Folosind Docker, po\u021bi ridica rapid o duzin\u0103 de agen\u021bi de build, care vor avea exact acela\u0219i mediu ca \u0219i anteriorul. Acest lucru faciliteaz\u0103 mult via\u021ba inginerilor CI. A integra android-sdk \u00een Docker este foarte simplu, cu emulatorii este pu\u021bin mai complicat: va trebui s\u0103 te str\u0103duie\u0219ti pu\u021bin (sau, din nou, s\u0103 descarci un exemplu de pe GitHub).<\/p>\n<p><strong>Sfatul nr. 4: nu uita\u021bi c\u0103 verific\u0103rile nu se fac pentru c\u0103 sunt verific\u0103ri, ci pentru oameni.<\/strong><\/p>\n<p>Dezvoltatorilor le este foarte important un feedback rapid \u0219i, mai ales, clar: ce s-a stricat, ce test a picat, unde s\u0103 se uite \u00een buildlog.<\/p>\n<p><strong>Sfatul nr. 5: fi\u021bi pragmatici \u00een dezvoltarea Continuous Integration.<\/strong><\/p>\n<p>\u00cen\u021belege\u021bi clar ce tipuri de erori dori\u021bi s\u0103 preveni\u021bi, c\u00e2t sunte\u021bi dispu\u0219i s\u0103 cheltui\u021bi resurse, timp, timp de calcul. Verific\u0103rile care dureaz\u0103 prea mult pot fi, de exemplu, mutate pe timpul nop\u021bii. Iar de la cele care depisteaz\u0103 erori mai pu\u021bin importante, pute\u021bi renun\u021ba complet.<\/p>\n<p><strong>Sfatul nr. 6: folosi\u021bi instrumente gata f\u0103cute.<\/strong><\/p>\n<p>Acum sunt multe companii care ofer\u0103 CI \u00een cloud.<\/p>\n<p><img decoding=\"async\" alt=\"Evolu\u021bia CI \u00een echipa de dezvoltare mobil\u0103\" src=\"\/wp-content\/uploads\/2019\/04\/e57aabd49ec01d14e83b64331fd84ec5.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nPentru echipe mici, acesta este un bun compromis. Nu trebuie s\u0103 sus\u021bine\u021bi nimic, doar pl\u0103ti\u021bi pu\u021bini bani, aduna\u021bi-v\u0103 aplica\u021bia \u0219i chiar rula\u021bi teste de instrumentare.<\/p>\n<p><strong>Sfaturi \u21167: \u00eentr-o echip\u0103 mare, solu\u021biile in-house sunt mai avantajoase.<\/strong><\/p>\n<p>Dar mai devreme sau mai t\u00e2rziu, pe m\u0103sur\u0103 ce echipa cre\u0219te, solu\u021biile in-house vor fi mai profitabile. Cu aceste solu\u021bii exist\u0103 un aspect. \u00cen economie exist\u0103 legea randamentelor \u00een sc\u0103dere: \u00een orice proiect, fiecare \u00eembun\u0103t\u0103\u021bire ulterioar\u0103 devine din ce \u00een ce mai dificil\u0103 \u0219i necesit\u0103 tot mai multe investi\u021bii.<\/p>\n<p>Economia descrie \u00eentreaga noastr\u0103 via\u021b\u0103, inclusiv Continuous Integration. Am construit un grafic al eforturilor necesare pentru fiecare etap\u0103 a dezvolt\u0103rii Continuous Integration-ului nostru.<\/p>\n<p><img decoding=\"async\" alt=\"Evolu\u021bia CI \u00een echipa de dezvoltare mobil\u0103\" src=\"\/wp-content\/uploads\/2019\/04\/97bf8bff64f3ac9ae587fae195251da2.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nSe observ\u0103 c\u0103 orice \u00eembun\u0103t\u0103\u021bire devine din ce \u00een ce mai dificil de realizat. Privind acest grafic, putem \u00een\u021belege c\u0103 dezvoltarea Continuous Integration-ului trebuie s\u0103 fie \u00een conformitate cu cre\u0219terea dimensiunii echipei. Pentru o echip\u0103 de dou\u0103 persoane, a cheltui 50 de zile pentru a dezvolta o ferm\u0103 intern\u0103 de emulatoare este o idee proast\u0103. Dar, pe de alt\u0103 parte, pentru o echip\u0103 mare s\u0103 nu se ocupe deloc de Continuous Integration este, de asemenea, o idee proast\u0103, deoarece va dura \u0219i mai mult timp pentru a rezolva problemele de integrare, repararea comunic\u0103rii etc.<\/p>\n<p>Am \u00eenceput cu ideea c\u0103 automatizarea este necesar\u0103, deoarece oamenii sunt costisitori, fac gre\u0219eli \u0219i sunt lene\u0219i. Dar \u0219i automatizarea este realizat\u0103 de oameni. Prin urmare, toate aceste probleme se aplic\u0103 \u0219i automatiz\u0103rii.<\/p>\n<ul>\n<li>Automatizarea este costisitoare. Aminti\u021bi-v\u0103 graficul eforturilor.<\/li>\n<li>C\u00e2nd automatiz\u0103m, oamenii fac gre\u0219eli.<\/li>\n<li>Uneori, este foarte greu s\u0103 automatizezi, deoarece totul func\u021bioneaz\u0103 deja. De ce s\u0103 \u00eembun\u0103t\u0103\u021bim ceva? De ce toat\u0103 aceast\u0103 Continuous Integration?<\/li>\n<\/ul>\n<p>\nDar am statistici: \u00een 20% din construc\u021bii sunt descoperite erori. \u0218i asta nu se \u00eent\u00e2mpl\u0103 pentru c\u0103 dezvoltatorii no\u0219tri scriu cod prost. Se \u00eent\u00e2mpl\u0103 pentru c\u0103 dezvoltatorii sunt siguri c\u0103, dac\u0103 comit o gre\u0219eal\u0103, aceasta nu va ajunge \u00een develop, va fi prins\u0103 de verific\u0103rile automatizate. Prin urmare, dezvoltatorii pot petrece mai mult timp scriind cod \u0219i lucruri interesante, \u00een loc s\u0103 verifice local.<\/p>\n<p><strong>\u00cencerca\u021bi Continuous Integration. Dar cu m\u0103sur\u0103.<\/strong><\/p>\n<blockquote><p>Apropo, Nikolai Nesterov nu doar c\u0103 face prezent\u0103ri grozave, ci face parte \u0219i din comitetul de program <noindex><a rel=\"nofollow\" href=\"https:\/\/appsconf.ru\/moscow\/2019\">AppsConf<\/a><\/noindex> \u0219i ajut\u0103 pe al\u021bii s\u0103 preg\u0103teasc\u0103 pentru voi prezent\u0103ri de con\u021binut. Completa \u0219i utilitatea programului urm\u0103toarei conferin\u021be pot fi evaluate dup\u0103 temele din <noindex><a rel=\"nofollow\" href=\"https:\/\/appsconf.ru\/moscow\/2019\/schedule\">program<\/a><\/noindex>. Iar pentru detalii veni\u021bi pe 22-23 aprilie \u00een Infoprosp\u0103ria.<\/p><\/blockquote>\n<p>Sursa: <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.2.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\/ro\/blog\/administrirovanie\/evolyutsiya-ci-v-komande-mobilnoj-razrabotki\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.2.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"ro_RO\" \/>\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\/ro\/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\udd47Evolu\u021bia CI \u00een echipa de dezvoltare mobil\u0103 | ProHoster","description":"Ast\u0103zi, majoritatea produselor software sunt dezvoltate \u00een echipe.","canonical_url":"https:\/\/prohoster.info\/ro\/blog\/administrirovanie\/evolyutsiya-ci-v-komande-mobilnoj-razrabotki","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"ro_RO","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\/ro\/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\/ro\/wp-json\/wp\/v2\/posts\/31304","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/comments?post=31304"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/posts\/31304\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/media\/23270"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/media?parent=31304"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/categories?post=31304"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/ro\/wp-json\/wp\/v2\/tags?post=31304"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}