{"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\/cs\/blog\/administrirovanie\/evolyutsiya-ci-v-komande-mobilnoj-razrabotki","title":{"rendered":"Evoluce CI v t\u00fdmu mobiln\u00edho v\u00fdvoje","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Dnes v\u011bt\u0161ina softwarov\u00fdch produkt\u016f vznik\u00e1 v t\u00fdmech. Podm\u00ednky \u00fasp\u011b\u0161n\u00e9ho t\u00fdmov\u00e9ho v\u00fdvoje lze p\u0159edstavit jako jednoduch\u00e9 sch\u00e9ma.<\/p>\n<p><img decoding=\"async\" alt=\"Evoluce CI v t\u00fdmu mobiln\u00edho v\u00fdvoje\" src=\"\/wp-content\/uploads\/2019\/04\/b283cfd7772d8f479be0754ebbe51b19.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nPo naps\u00e1n\u00ed k\u00f3du se mus\u00edte ujistit, \u017ee:<\/p>\n<ol>\n<li>Funguje.<\/li>\n<li>Nic neporu\u0161uje, v\u010detn\u011b k\u00f3du, kter\u00fd napsali va\u0161i kolegov\u00e9.<\/li>\n<\/ol>\n<p>\nPokud ob\u011b podm\u00ednky plat\u00ed, jste na cest\u011b k \u00fasp\u011bchu. Aby bylo mo\u017en\u00e9 tyto podm\u00ednky snadno kontrolovat a nesklouznout z v\u00fdhodn\u00e9 cesty, vymysleli Continuous Integration.<\/p>\n<p>CI je pracovn\u00ed proces, p\u0159i kter\u00e9m co nej\u010dast\u011bji integrujete sv\u016fj k\u00f3d do celkov\u00e9ho k\u00f3du produktu. A nezapojujete jej jen tak, ale tak\u00e9 neust\u00e1le kontrolujete, \u017ee v\u0161e funguje. Proto\u017ee je t\u0159eba kontrolovat mnoho v\u011bc\u00ed a \u010dasto, stoj\u00ed za to p\u0159em\u00fd\u0161let o automatizaci. M\u016f\u017eete v\u0161e kontrolovat ru\u010dn\u011b, ale to se nevyplat\u00ed, a zde je pro\u010d.<br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<ul>\n<li><strong>Lid\u00e9 jsou cenn\u00ed<\/strong>. Hodina pr\u00e1ce jak\u00e9hokoliv program\u00e1tora stoj\u00ed v\u00edce ne\u017e hodina pr\u00e1ce jak\u00e9hokoliv serveru.<\/li>\n<li><strong>Lid\u00e9 d\u011blaj\u00ed chyby<\/strong>. Proto mohou nastat situace, kdy byly spu\u0161t\u011bny testy na nespr\u00e1vn\u00e9 v\u011btvi nebo byl sestaven nespr\u00e1vn\u00fd commit pro testery.<\/li>\n<li><strong>Lid\u00e9 jsou l\u00edn\u00ed<\/strong>. Ob\u010das, kdy\u017e dokon\u010duji n\u011bjak\u00fd \u00fakol, mi probleskne hlavou my\u0161lenka: \u201eCo tu kontrolovat? Napsal jsem dv\u011b \u0159\u00e1dky \u2014 ur\u010dit\u011b to funguje!\u201c Mysl\u00edm, \u017ee n\u011bkte\u0159\u00ed z v\u00e1s tyto my\u0161lenky tak\u00e9 ob\u010das maj\u00ed. Ale kontrolovat je t\u0159eba v\u017edy.<\/li>\n<\/ul>\n<p>\nJak implementovali a rozv\u00edjeli Continuous Integration v t\u00fdmu mobiln\u00edho v\u00fdvoje Avito, jak do\u0161li od 0 do 450 sestaven\u00ed denn\u011b, a co buildovac\u00ed stroje sestavuj\u00ed 200 hodin denn\u011b, popisuje Nikolaj Nesterov (<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/users\/nnesterov\/\" class=\"user_link\">nnesterov<\/a><\/noindex>) \u2014 \u00fa\u010dastn\u00edk v\u0161ech evolu\u010dn\u00edch zm\u011bn CI\/CD Android aplikace.<\/p>\n<p>P\u0159\u00edb\u011bh je postaven na p\u0159\u00edkladu Android t\u00fdmu, ale v\u011bt\u0161ina p\u0159\u00edstup\u016f je pou\u017eiteln\u00e1 i na 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=\"P\u0159ehr\u00e1t video\" loading=\"lazy\" width=\"480\" height=\"360\" style=\"width:100%;height:auto;\">\r\n        <div class=\"play-button\"><\/div>\r\n    <\/div><\/center><br \/>\nKdysi d\u00e1vno v Android t\u00fdmu Avito pracoval jeden \u010dlov\u011bk. Z definice nepot\u0159eboval nic z Continuous Integration: nem\u011bl s k\u00fdm se integrovat.<\/p>\n<p>Ale aplikace rostla, objevovalo se st\u00e1le v\u00edce a v\u00edce nov\u00fdch \u00fakol\u016f, a logicky rostl t\u00fdm. V ur\u010dit\u00e9m okam\u017eiku nastal \u010das formalizovat proces integrace k\u00f3du. Bylo rozhodnuto pou\u017e\u00edt Git flow.<\/p>\n<p><img decoding=\"async\" alt=\"Evoluce CI v t\u00fdmu mobiln\u00edho v\u00fdvoje\" src=\"\/wp-content\/uploads\/2019\/04\/f433effc5ab5a59de3d6bd20a7a87057.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nKoncept Git flow je zn\u00e1m\u00fd: v projektu existuje jedin\u00e9 spole\u010dn\u00e9 odv\u011btv\u00ed develop, a pro ka\u017edou novou funkci si v\u00fdvoj\u00e1\u0159i odejmou samostatnou v\u011btev, do kter\u00e9 provedou commit, push a kdy\u017e cht\u011bj\u00ed slou\u010dit sv\u016fj k\u00f3d do v\u011btve develop, otev\u0159ou pull request. Pro v\u00fdm\u011bnu znalost\u00ed a diskusi o p\u0159\u00edstupech jsme zavedli code review, tedy kolegov\u00e9 mus\u00ed zkontrolovat a potvrdit k\u00f3d jeden druh\u00e9ho.<\/p>\n<h2>Ov\u011b\u0159en\u00ed<\/h2>\n<p>\nD\u00edvat se na k\u00f3d o\u010dima je skv\u011bl\u00e9, ale nesta\u010d\u00ed to. Proto se zav\u00e1d\u011bj\u00ed automatizovan\u00e9 kontroly.<\/p>\n<ul>\n<li>Nejd\u0159\u00edve ov\u011b\u0159ujeme <strong>sestaven\u00ed ARC<\/strong>.<\/li>\n<li>Mnoho <strong>Junit test\u016f<\/strong>.<\/li>\n<li><strong>Vyhodnocujeme pokryt\u00ed k\u00f3du<\/strong>, kdy\u017e u\u017e spou\u0161t\u00edme testy.<\/li>\n<\/ul>\n<p>\nAbychom pochopili, jak by m\u011bly b\u00fdt tyto kontroly spu\u0161t\u011bny, pod\u00edv\u00e1me se na proces v\u00fdvoje v Avitu.<\/p>\n<p>Sch\u00e9maticky ho lze p\u0159edstavit takto:<\/p>\n<ul>\n<li>V\u00fdvoj\u00e1\u0159 p\u00ed\u0161e k\u00f3d na sv\u00e9m notebooku. Kontroly integrace lze spustit p\u0159\u00edmo zde \u2014 bu\u010f commit hookem, nebo jednodu\u0161e provozovat kontroly na pozad\u00ed.<\/li>\n<li>Pot\u00e9, co v\u00fdvoj\u00e1\u0159 pushnul k\u00f3d, otev\u0159e pull request. Aby se jeho k\u00f3d dostal do v\u011btve develop, je nutn\u00e9 proj\u00edt code review a z\u00edskat pot\u0159ebn\u00fd po\u010det potvrzen\u00ed. Zde lze tak\u00e9 zapnout kontroly a sestaven\u00ed: dokud nejsou v\u0161echna sestaven\u00ed \u00fasp\u011b\u0161n\u00e1, pull request nelze slou\u010dit.<\/li>\n<li>Jakmile je pull request slou\u010den a k\u00f3d se dostal do developu, m\u016f\u017eete si zvolit vhodn\u00fd \u010das: nap\u0159\u00edklad v noci, kdy jsou v\u0161echny servery voln\u00e9, a spustit kontroly tak, jak se to hod\u00ed.<\/li>\n<\/ul>\n<p>\nNikdo nem\u011bl r\u00e1d spou\u0161t\u011bt kontroly na sv\u00e9m notebooku. Kdy\u017e v\u00fdvoj\u00e1\u0159 dokon\u010dil funkci, chce ji co nejrychleji pushnout a otev\u0159\u00edt pull request. Pokud v tomto okam\u017eiku prob\u00edhaj\u00ed n\u011bjak\u00e9 dlouh\u00e9 kontroly, nen\u00ed to jen nep\u0159\u00edjemn\u00e9, ale tak\u00e9 to zpomaluje v\u00fdvoj: zat\u00edmco notebook n\u011bco kontroluje, nejde na n\u011bm norm\u00e1ln\u011b pracovat.<\/p>\n<p>No\u010dn\u00ed spou\u0161t\u011bn\u00ed kontrol n\u00e1m velmi vyhovovalo, proto\u017ee \u010dasu a server\u016f bylo dost, mohli jsme si u\u017e\u00edvat. Ale bohu\u017eel, kdy\u017e k\u00f3d funkce dos\u00e1hnul developu, m\u011bl v\u00fdvoj\u00e1\u0159 u\u017e mnohem m\u00e9n\u011b motivace opravovat chyby, kter\u00e9 na\u0161el CI. \u010casto jsem se chytil p\u0159em\u00fd\u0161len\u00ed, kdy\u017e jsem se d\u00edval do rann\u00edho reportu na v\u0161echny nalezen\u00e9 chyby, \u017ee je oprav\u00edm n\u011bkdy pozd\u011bji, proto\u017ee nyn\u00ed v Jira le\u017e\u00ed skv\u011bl\u00fd nov\u00fd \u00fakol, kter\u00fd se tak t\u011b\u0161\u00ed na realizaci.<\/p>\n<p>Pokud kontroly blokuj\u00ed pull request, je motivace dostate\u010dn\u00e1, proto\u017ee dokud nejsou v\u0161echna sestaven\u00ed \u00fasp\u011b\u0161n\u00e1, k\u00f3d se nedostane do developu, a tedy \u00fakol nebude dokon\u010den.<\/p>\n<p>Nakonec jsme zvolili takovou strategii: v noci b\u011b\u017e\u00edme co nejv\u00edce test\u016f, a ty nejkriti\u010dt\u011bj\u0161\u00ed, a co je nejd\u016fle\u017eit\u011bj\u0161\u00ed, rychl\u00e9, spou\u0161t\u00edme na pull requestu. Ale na tom nekon\u010d\u00edme \u2014 sou\u010dasn\u011b optimalizujeme rychlost spu\u0161t\u011bn\u00ed test\u016f tak, abychom je mohli p\u0159ev\u00e9st z no\u010dn\u00edho re\u017eimu na testy na pull request.<\/p>\n<p>V t\u00e9 dob\u011b v\u0161echny na\u0161e buildy prob\u00edhaly dost rychle, tak\u017ee jsme jednodu\u0161e p\u0159idali jako bloker k pull requestu build ARC, Junit testy a v\u00fdpo\u010det code coverage. P\u0159idali jsme, zamysleli se \u2014 a vzdali jsme se code coverage, proto\u017ee jsme usoudili, \u017ee ho nepot\u0159ebujeme.<\/p>\n<p><strong><em>Na celou konfiguraci z\u00e1kladn\u00edho CI jsme pot\u0159ebovali dva dny (odtud a d\u00e1le je \u010dasov\u00fd odhad p\u0159ibli\u017en\u00fd, pot\u0159ebn\u00fd pro m\u011b\u0159\u00edtko). <\/em><\/strong><\/p>\n<p>Pot\u00e9 jsme za\u010dali p\u0159em\u00fd\u0161let d\u00e1le \u2014 kontrolujeme spr\u00e1vn\u011b? Spou\u0161t\u00edme buildy na pull request spr\u00e1vn\u011b?<\/p>\n<p>Spou\u0161t\u011bli jsme build na posledn\u00edm commitu v\u011btve, ze kter\u00e9 byl otev\u0159en pull request. Ale kontroly tohoto commitu mohou uk\u00e1zat pouze to, \u017ee k\u00f3d, kter\u00fd napsal v\u00fdvoj\u00e1\u0159, funguje. Neprokazuj\u00ed v\u0161ak, \u017ee nic neporu\u0161il. Ve skute\u010dnosti je t\u0159eba zkontrolovat stav v\u011btve develop pot\u00e9, co byla funkce slou\u010dena.<\/p>\n<p><img decoding=\"async\" alt=\"Evoluce CI v t\u00fdmu mobiln\u00edho v\u00fdvoje\" src=\"\/wp-content\/uploads\/2019\/04\/c1a179b68b02c04031177f9bf51a81ed.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nK tomu jsme napsali jednoduch\u00fd bash skript <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>\nZde se jednodu\u0161e na\u010dtou v\u0161echny nejnov\u011bj\u0161\u00ed zm\u011bny z develop a slou\u010d\u00ed se do aktu\u00e1ln\u00ed v\u011btve. P\u0159idali jsme skript premerge.sh jako prvn\u00ed krok v\u0161ech build\u016f a za\u010dali jsme kontrolovat pr\u00e1v\u011b to, co chceme, tedy <strong>integraci<\/strong>.<\/p>\n<p><strong><em>Na lokalizaci probl\u00e9m\u016f, hled\u00e1n\u00ed \u0159e\u0161en\u00ed a naps\u00e1n\u00ed tohoto skriptu jsme pot\u0159ebovali t\u0159i dny.<\/em><\/strong><\/p>\n<p>Aplikace se vyv\u00edjela, p\u0159ib\u00fdvalo \u00faloh, t\u00fdm rostl a premerge.sh n\u00e1s ob\u010das za\u010dal zrazovat. Do develop pronikaly konfliktn\u00ed zm\u011bny, kter\u00e9 poru\u0161ovaly build.<\/p>\n<p>P\u0159\u00edklad toho, jak k tomu doch\u00e1z\u00ed:<\/p>\n<p><img decoding=\"async\" alt=\"Evoluce CI v t\u00fdmu mobiln\u00edho v\u00fdvoje\" src=\"\/wp-content\/uploads\/2019\/04\/6762d9ce4e431549455c49f2317a8f20.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nDva v\u00fdvoj\u00e1\u0159i sou\u010dasn\u011b za\u010dnou pracovat na funkc\u00edch A a B. V\u00fdvoj\u00e1\u0159 funkce A objev\u00ed v projektu nepou\u017e\u00edvanou funkci <code>answer()<\/code> a, jako dobr\u00fd skaut, ji odstran\u00ed. P\u0159itom v\u00fdvoj\u00e1\u0159 funkce B ve sv\u00e9 v\u011btvi p\u0159id\u00e1v\u00e1 nov\u00fd vol\u00e1n\u00ed t\u00e9to funkce.<\/p>\n<p>V\u00fdvoj\u00e1\u0159i dokon\u010d\u00ed pr\u00e1ci a sou\u010dasn\u011b otev\u0159ou pull request. Spou\u0161t\u00ed se buildy, premerge.sh kontroluje oba pull requesty vzhledem k aktu\u00e1ln\u00edmu stavu develop \u2014 v\u0161echny kontroly jsou zelen\u00e9. Pot\u00e9 se slou\u010d\u00ed pull request funkce A, slou\u010d\u00ed se pull request funkce B\u2026 Bum! Develop se rozbije, proto\u017ee v k\u00f3du develop je vol\u00e1n\u00ed neexistuj\u00edc\u00ed funkce.<\/p>\n<p><img decoding=\"async\" alt=\"Evoluce CI v t\u00fdmu mobiln\u00edho v\u00fdvoje\" src=\"\/wp-content\/uploads\/2019\/04\/0261dcc9f7f2e5e018ab136081b4679b.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nKdy\u017e se develop neslo\u017e\u00ed, tohle <strong>lok\u00e1ln\u00ed katastrofa<\/strong>. Cel\u00fd t\u00fdm nem\u016f\u017ee nic poskl\u00e1dat a odeslat k testov\u00e1n\u00ed.<\/p>\n<p>Jak to tak b\u00fdv\u00e1, v\u011bnoval jsem se p\u0159ev\u00e1\u017en\u011b infrastrukturn\u00edm \u00faloh\u00e1m: analytika, s\u00ed\u0165, datab\u00e1ze. To znamen\u00e1, \u017ee jsem psal funkce a t\u0159\u00eddy, kter\u00e9 pou\u017e\u00edvaj\u00ed ostatn\u00ed v\u00fdvoj\u00e1\u0159i. Z toho d\u016fvodu jsem se velmi \u010dasto ocital v podobn\u00fdch situac\u00edch. Dokonce jsem m\u011bl jedno obdob\u00ed, kdy jsem m\u011bl takov\u00fd obr\u00e1zek.<\/p>\n<p><img decoding=\"async\" alt=\"Evoluce CI v t\u00fdmu mobiln\u00edho v\u00fdvoje\" src=\"\/wp-content\/uploads\/2019\/04\/0eb1aadd05b33ce995027ab52d5c1e53.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nProto\u017ee n\u00e1s to nevyhovovalo, za\u010dali jsme pracovat na mo\u017enostech, jak tomu zabr\u00e1nit.<\/p>\n<h2>Jak nel\u00e1mat develop<\/h2>\n<p>\nPrvn\u00ed varianta: <strong>p\u0159epracov\u00e1vat v\u0161echny pull requesty p\u0159i aktualizaci develop. <\/strong>Pokud v na\u0161em p\u0159\u00edkladu bude pull request s funkcionalitou A prvn\u00ed, kter\u00fd se dostane do develop, pull request funkcionality B se p\u0159epracuje a odpov\u00eddaj\u00edc\u00edm zp\u016fsobem zkou\u0161ky neprojdou kv\u016fli chyb\u011b kompilace.<\/p>\n<p>Abychom pochopili, kolik \u010dasu to zabere, pod\u00edvejme se na p\u0159\u00edklad se dv\u011bma PR. Otev\u0159eme dva PR: dva buildy, dv\u011b spu\u0161t\u011bn\u00ed zkou\u0161ek. Pot\u00e9, co byl prvn\u00ed PR slou\u010den do develop, druh\u00fd se mus\u00ed p\u0159epracovat. Celkov\u011b to znamen\u00e1, \u017ee na dva PR prob\u011bhne t\u0159i spu\u0161t\u011bn\u00ed zkou\u0161ek: 2 + 1 = 3.<\/p>\n<p>V z\u00e1sad\u011b je to v po\u0159\u00e1dku. Ale pod\u00edvali jsme se na statistiky a typick\u00e1 situace v na\u0161em t\u00fdmu byla 10 otev\u0159en\u00fdch PR, a pak je po\u010det zkou\u0161ek sou\u010det postupnosti: 10 + 9 +\u2026 + 1 = 55. To znamen\u00e1, \u017ee abychom p\u0159ijali 10 PR, mus\u00edme to p\u0159epracovat 55kr\u00e1t. A to v ide\u00e1ln\u00ed situaci, kdy v\u0161echny zkou\u0161ky prob\u011bhnou na prvn\u00ed pokus, kdy nikdo neotev\u0159e dal\u0161\u00ed pull request, zat\u00edmco se zpracov\u00e1v\u00e1 tato des\u00edtka.<\/p>\n<p>P\u0159edstavte si, \u017ee jste v\u00fdvoj\u00e1\u0159, kter\u00fd se sna\u017e\u00ed zm\u00e1\u010dknout tla\u010d\u00edtko \"merge\" jako prvn\u00ed, proto\u017ee pokud to ud\u011bl\u00e1 soused, bude se muset \u010dekat, a\u017e prob\u011bhnou v\u0161echny nov\u00e9 buildy... Ne, to tak nejde, to v\u00e1\u017en\u011b zpomal\u00ed v\u00fdvoj.<\/p>\n<p>Druh\u00e1 mo\u017en\u00e1 varianta: <strong>sb\u00edrat pull requesty po code review. <\/strong>To znamen\u00e1, \u017ee otev\u0159ete pull request, z\u00edsk\u00e1te pot\u0159ebn\u00fd po\u010det schv\u00e1len\u00ed od koleg\u016f, oprav\u00edte, co je pot\u0159eba, a pot\u00e9 spust\u00edte buildy. Pokud jsou \u00fasp\u011b\u0161n\u00e9, pull request se slou\u010d\u00ed s develop. V tomto p\u0159\u00edpad\u011b nejsou \u017e\u00e1dn\u00e9 dodate\u010dn\u00e9 restartov\u00e1n\u00ed, ale v\u00fdrazn\u011b se zpomal\u00ed zp\u011btn\u00e1 vazba. J\u00e1, jako v\u00fdvoj\u00e1\u0159, chci hned vid\u011bt, zda se m\u016fj pull request sestavuje. Nap\u0159\u00edklad pokud n\u011bjak\u00fd test selhal, pot\u0159ebuji ho rychle opravit. V p\u0159\u00edpad\u011b odlo\u017een\u00e9ho buildov\u00e1n\u00ed se zpomaluje zp\u011btn\u00e1 vazba, a t\u00edm i cel\u00fd v\u00fdvoj. To n\u00e1s tak\u00e9 nevyhovovalo.<\/p>\n<p>Nakonec z\u016fstal pouze t\u0159et\u00ed variant \u2014 <strong>vym\u00fd\u0161let kolo<\/strong>V\u0161echna n\u00e1\u0161 k\u00f3d a v\u0161echny na\u0161e zdrojov\u00e9 k\u00f3dy jsou ulo\u017eeny v repozit\u00e1\u0159i na serveru Bitbucket. Proto jsme museli vyvinout plugin pro Bitbucket.<\/p>\n<p><img decoding=\"async\" alt=\"Evoluce CI v t\u00fdmu mobiln\u00edho v\u00fdvoje\" src=\"\/wp-content\/uploads\/2019\/04\/5e4b1f770ea1a3449b616c3101640294.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nTento plugin p\u0159episuje mechanismus slu\u010dov\u00e1n\u00ed pull request\u016f. Za\u010d\u00e1tek je standardn\u00ed: otev\u0159e se PR, spust\u00ed se v\u0161echny sestaven\u00ed, prob\u00edh\u00e1 code review. Ale pot\u00e9, co je code review \u00fasp\u011b\u0161n\u011b dokon\u010deno a v\u00fdvoj\u00e1\u0159 se rozhodne stisknout \u00abmerge\u00bb, plugin kontroluje, k jak\u00e9mu stavu develop byly prov\u00e1d\u011bny kontroly. Pokud se develop b\u011bhem sestaven\u00ed aktualizoval, plugin nedovol\u00ed slou\u010dit takov\u00fd pull request do hlavn\u00ed v\u011btve. Prost\u011b znovu spust\u00ed sestaven\u00ed na z\u00e1klad\u011b aktu\u00e1ln\u00edho developu.<\/p>\n<p><img decoding=\"async\" alt=\"Evoluce CI v t\u00fdmu mobiln\u00edho v\u00fdvoje\" src=\"\/wp-content\/uploads\/2019\/04\/0edee880e1f2d286a0c64033103ac136.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nV na\u0161em p\u0159\u00edkladu s konfliktuj\u00edc\u00edmi zm\u011bnami takov\u00e1 sestaven\u00ed neprojdou kv\u016fli chyb\u011b kompilace. V\u00fdvoj\u00e1\u0159 funkce B tak mus\u00ed opravit k\u00f3d a znovu spustit testy, co\u017e plugin automaticky aplikuje na pull request.<\/p>\n<p>P\u0159ed zaveden\u00edm tohoto pluginu jsme m\u011bli pr\u016fm\u011brn\u011b 2,7 spu\u0161t\u011bn\u00ed test\u016f na jeden pull request. S pluginem to vzrostlo na 3,6 spu\u0161t\u011bn\u00ed. To n\u00e1m vyhovovalo.<\/p>\n<p>Je t\u0159eba poznamenat, \u017ee tento plugin m\u00e1 nev\u00fdhodu: znovu spou\u0161t\u00ed sestaven\u00ed pouze jednou. Tak\u017ee st\u00e1le z\u016fst\u00e1v\u00e1 mal\u00e9 okno, skrze kter\u00e9 se do developu mohou dostat konfliktuj\u00edc\u00ed zm\u011bny. Ale pravd\u011bpodobnost toho je n\u00edzk\u00e1 a my jsme se rozhodli pro tento kompromis mezi po\u010dtem spu\u0161t\u011bn\u00ed a pravd\u011bpodobnost\u00ed chyby. Za posledn\u00ed dva roky se to stalo jen jednou, tak\u017ee asi to nebylo zbyte\u010dn\u00e9.<\/p>\n<p><strong><em>Na naps\u00e1n\u00ed prvn\u00ed verze pluginu pro Bitbucket n\u00e1m trvalo dva t\u00fddny. <\/em><\/strong><\/p>\n<h3>Nov\u00e9 kontroly<\/h3>\n<p>\nMezit\u00edm n\u00e1\u0161 t\u00fdm pokra\u010doval v r\u016fstu. P\u0159id\u00e1vali jsme nov\u00e9 kontroly.<\/p>\n<p>Pomysleli jsme si: pro\u010d opravit chyby, kdy\u017e je m\u016f\u017eeme p\u0159edch\u00e1zet? A proto jsme implementovali <strong>statick\u00fd anal\u00fdza k\u00f3du<\/strong>. Za\u010dali jsme s lint, kter\u00fd je sou\u010d\u00e1st\u00ed Android SDK. Ale ten tehdy v\u016fbec neum\u011bl pracovat s Kotlin k\u00f3dem, a my u\u017e m\u011bli 75 % aplikace napsan\u00e9 v Kotlinu. Tak\u017ee k lintu byly p\u0159id\u00e1ny vestav\u011bn\u00e9 <strong>kontroly Android Studio.<\/strong><\/p>\n<p>Za t\u00edmto \u00fa\u010delem jsme museli dost \u0161\u00edlen\u011b zkomplikovat situaci: vz\u00edt Android Studio, zabalit ji do Dockeru a spou\u0161t\u011bt na CI s virtu\u00e1ln\u00edm monitorem, aby si myslela, \u017ee je spu\u0161t\u011bna na skute\u010dn\u00e9m notebooku. Ale fungovalo to.<\/p>\n<p>Tak\u00e9 v t\u00e9 dob\u011b jsme za\u010dali ps\u00e1t hodn\u011b <strong>instrumenta\u010dn\u00edch test\u016f<\/strong> a implementovali jsme <strong>screenshotov\u00e9 testov\u00e1n\u00ed<\/strong>. To je moment, kdy je generov\u00e1n referen\u010dn\u00ed sn\u00edmek pro samostatn\u00fd mal\u00fd n\u00e1hled, a test spo\u010d\u00edv\u00e1 v tom, \u017ee se z n\u00e1hledu po\u0159\u00edd\u00ed sn\u00edmek a porovn\u00e1 se s referen\u010dn\u00edm obr\u00e1zkem pixel po pixelu. Pokud dojde k odchylce, znamen\u00e1 to, \u017ee do\u0161lo k chyb\u011b v rozvr\u017een\u00ed nebo ve stylu.<\/p>\n<p>Ale instrumenta\u010dn\u00ed testy a sn\u00edmkov\u00e9 testy je t\u0159eba spou\u0161t\u011bt na za\u0159\u00edzen\u00edch: na emul\u00e1torech nebo na skute\u010dn\u00fdch za\u0159\u00edzen\u00edch. Vzhledem k tomu, \u017ee test\u016f je mnoho a \u010dasto se prov\u00e1d\u011bj\u00ed, pot\u0159ebujeme celou farmu. Zalo\u017eit vlastn\u00ed farmu je p\u0159\u00edli\u0161 pracn\u00e9, proto jsme na\u0161li hotov\u00e9 \u0159e\u0161en\u00ed \u2014 Firebase Test Lab.<\/p>\n<h3>Firebase Test Lab<\/h3>\n<p>\nByl vybr\u00e1n, proto\u017ee Firebase je produkt Google, co\u017e znamen\u00e1, \u017ee by m\u011bl b\u00fdt spolehliv\u00fd a pravd\u011bpodobn\u011b nikdy nezmiz\u00ed. Ceny jsou p\u0159ijateln\u011b n\u00edzk\u00e9: 5 $ za hodinu provozu skute\u010dn\u00e9ho za\u0159\u00edzen\u00ed, 1 $ za hodinu provozu emul\u00e1toru.<\/p>\n<p><strong><em>Na implementaci Firebase Test Lab do na\u0161eho CI jsme pot\u0159ebovali p\u0159ibli\u017en\u011b t\u0159i t\u00fddny.<\/em><\/strong><\/p>\n<p>Ale t\u00fdm st\u00e1le rostl a Firebase n\u00e1s, bohu\u017eel, za\u010dal zklam\u00e1vat. V t\u00e9 dob\u011b nem\u011bl \u017e\u00e1dnou SLA. N\u011bkdy Firebase donutil \u010dekat, dokud se neuvoln\u00ed pot\u0159ebn\u00fd po\u010det za\u0159\u00edzen\u00ed pro testy, a neza\u010dal je prov\u00e1d\u011bt ihned, jak jsme cht\u011bli. \u010cek\u00e1n\u00ed ve front\u011b trvalo a\u017e p\u016fl hodiny, co\u017e je velmi dlouho. Instrumenta\u010dn\u00ed testy prob\u00edhaly na ka\u017ed\u00e9m PR, zpo\u017ed\u011bn\u00ed velmi zpomalovala v\u00fdvoj, a pot\u00e9 p\u0159i\u0161la faktura za m\u011bs\u00edc s kulatou \u010d\u00e1stkou. Celkov\u011b jsme se rozhodli vzd\u00e1t se Firebase a vyvinout vlastn\u00ed \u0159e\u0161en\u00ed, kdy\u017e u\u017e t\u00fdm dost vyrostl.<\/p>\n<h3>Docker + Python + bash<\/h3>\n<p>\nVzali jsme docker, nacpali do n\u011bj emul\u00e1tory, napsali jednoduch\u00fd program v Pythonu, kter\u00fd v pot\u0159ebnou chv\u00edli spust\u00ed pot\u0159ebn\u00fd po\u010det emul\u00e1tor\u016f v po\u017eadovan\u00e9 verzi a kdy\u017e je pot\u0159eba, je zastav\u00ed. A samoz\u0159ejm\u011b p\u00e1r bash skript\u016f \u2014 bez nich to nejde.<\/p>\n<p><strong><em>Na vytvo\u0159en\u00ed vlastn\u00ed testovac\u00ed prost\u0159ed\u00ed jsme pot\u0159ebovali p\u011bt t\u00fddn\u016f.<\/em><\/strong><\/p>\n<p>V d\u016fsledku toho m\u011bl ka\u017ed\u00fd pull request rozs\u00e1hl\u00fd, blockuj\u00edc\u00ed seznam kontrol:<\/p>\n<ul>\n<li>Sestaven\u00ed ARC;<\/li>\n<li>Junit testy;<\/li>\n<li>Lint;<\/li>\n<li>Kontroly Android Studio;<\/li>\n<li>Instrumentation testy;<\/li>\n<li>Screenshot testy.<\/li>\n<\/ul>\n<p>\nTo br\u00e1nilo mnoha mo\u017en\u00fdm selh\u00e1n\u00edm. Technicky v\u0161echno fungovalo, ale v\u00fdvoj\u00e1\u0159i si st\u011b\u017eovali, \u017ee \u010dek\u00e1n\u00ed na v\u00fdsledky trv\u00e1 p\u0159\u00edli\u0161 dlouho.<\/p>\n<p>Jak dlouho je \"p\u0159\u00edli\u0161 dlouho\"? Exportovali jsme data z Bitbucket a TeamCity do analytick\u00e9ho syst\u00e9mu a zjistili jsme, \u017ee <strong>pr\u016fm\u011brn\u00e1 doba \u010dek\u00e1n\u00ed je 45 minut<\/strong>. Tak\u017ee v\u00fdvoj\u00e1\u0159, kter\u00fd otv\u00edr\u00e1 pull request, v pr\u016fm\u011bru \u010dek\u00e1 na v\u00fdsledky sestaven\u00ed 45 minut. Podle m\u00e9ho n\u00e1zoru je to velmi mnoho a takto se pracovat ned\u00e1.<\/p>\n<p>Samoz\u0159ejm\u011b jsme se rozhodli urychlit v\u0161echny na\u0161e sestavy.<\/p>\n<h2>Zrychlujeme se<\/h2>\n<p>\nKdy\u017e jsme zjistili, \u017ee se \u010dasto bilduje fronta, nejd\u0159\u00edve jsme <strong>dokoupili hardware<\/strong> \u2014 extenzivn\u00ed rozvoj je nejjednodu\u0161\u0161\u00ed. Fronty pro buildy p\u0159estaly existovat, ale doba \u010dek\u00e1n\u00ed se sn\u00ed\u017eila jen o n\u011bco m\u00e1lo, proto\u017ee n\u011bkter\u00e9 kontroly se samy o sob\u011b prov\u00e1d\u011bly velmi dlouho.<\/p>\n<h3>Odstra\u0148ujeme p\u0159\u00edli\u0161 dlouh\u00e9 kontroly<\/h3>\n<p>\nN\u00e1\u0161 Continuous Integration mohl zachytit takov\u00e9 typy chyb a probl\u00e9m\u016f.<\/p>\n<ul>\n<li><strong>Nen\u00ed zapot\u0159eb\u00ed sestavit<\/strong>. CI m\u016f\u017ee zachytit chybu kompilace, kdy\u017e z d\u016fvodu konfliktn\u00edch zm\u011bn n\u011bco nelze sestavit. Jak jsem ji\u017e zm\u00ednil, v takov\u00e9m p\u0159\u00edpad\u011b nikdo nic nesestav\u00ed, v\u00fdvoj se zastav\u00ed a v\u0161ichni nerv\u00f3zn\u011b \u010dekaj\u00ed.<\/li>\n<li><strong>Chyba v chov\u00e1n\u00ed<\/strong>. Nap\u0159\u00edklad, kdy\u017e se aplikace sestav\u00ed, ale p\u0159i stisknut\u00ed tla\u010d\u00edtka se zhrout\u00ed, nebo tla\u010d\u00edtko v\u016fbec nezareaguje. To je \u0161patn\u011b, proto\u017ee takov\u00e1 chyba se m\u016f\u017ee dostat k u\u017eivateli.<\/li>\n<li><strong>Chyba v designu<\/strong>. Nap\u0159\u00edklad tla\u010d\u00edtko reaguje, ale posunulo se o 10 pixel\u016f vlevo.<\/li>\n<li><strong>Zvy\u0161ov\u00e1n\u00ed technick\u00e9ho dluhu<\/strong>.<\/li>\n<\/ul>\n<p>\nP\u0159i pohledu na tento seznam jsme pochopili, \u017ee kritick\u00fdmi jsou pouze prvn\u00ed dva body. Takov\u00e9 probl\u00e9my chceme odhalovat jako prvn\u00ed. Chyby v designu se odhaluj\u00ed v pr\u016fb\u011bhu design-review a lze je snadno opravit. Pr\u00e1ce s technick\u00fdm dluhem vy\u017eaduje samostatn\u00fd proces a pl\u00e1nov\u00e1n\u00ed, proto jsme se rozhodli ho neprov\u011b\u0159ovat p\u0159i pull requestu.<\/p>\n<p>Na z\u00e1klad\u011b t\u00e9to klasifikace jsme prohl\u00e9dli cel\u00fd seznam kontrol. <strong>Vy\u0161krtli jsme Lint<\/strong> a jeho spu\u0161t\u011bn\u00ed p\u0159esunuli na noc: prost\u011b aby vyd\u00e1val zpr\u00e1vu, kolik probl\u00e9m\u016f je v projektu. S technick\u00fdm dluhem jsme se dohodli pracovat odd\u011blen\u011b, a <strong>Android Studio kontrole jsme se \u00fapln\u011b vzdali<\/strong>. Android Studio na Dockeru pro spu\u0161t\u011bn\u00ed inspekc\u00ed zn\u00ed zaj\u00edmav\u011b, ale zp\u016fsobuje spoustu probl\u00e9m\u016f s \u00fadr\u017ebou. Ka\u017ed\u00e1 aktualizace verz\u00ed Android Studio je bojem s nejasn\u00fdmi chybami. Tak\u00e9 bylo slo\u017eit\u00e9 udr\u017eovat screenshotov\u00e9 testy, proto\u017ee knihovna nefungovala p\u0159\u00edli\u0161 stabiln\u011b, ob\u010das doch\u00e1zelo k fale\u0161n\u00fdm pozitiv\u016fm. <strong>Screenshotov\u00e9 testy jsme odstranili ze seznamu kontrol<\/strong>.<\/p>\n<p>Nakonec n\u00e1m z\u016fstaly:<\/p>\n<ul>\n<li>Sestaven\u00ed ARC;<\/li>\n<li>Junit testy;<\/li>\n<li>Testy instrumentace.<\/li>\n<\/ul>\n<h3>Gradle vzd\u00e1len\u00e1 cachov\u00e1n\u00ed<\/h3>\n<p>\nBez t\u011b\u017ek\u00fdch kontrol se v\u0161echno zlep\u0161ilo. Ale dokonalosti nen\u00ed nikdy konec!<\/p>\n<p>Na\u0161e aplikace byla ji\u017e rozd\u011blena na p\u0159ibli\u017en\u011b 150 gradle modul\u016f. Obvykle v takov\u00e9m p\u0159\u00edpad\u011b dob\u0159e funguje Gradle vzd\u00e1len\u00e1 cache, a rozhodli jsme se ji vyzkou\u0161et.<\/p>\n<p>Gradle remote cache je slu\u017eba, kter\u00e1 m\u016f\u017ee ukl\u00e1dat artefakty sestaven\u00ed pro jednotliv\u00e9 \u00fakoly v jednotliv\u00fdch modulech. Gradle, m\u00edsto skute\u010dn\u00e9 kompilace k\u00f3du, se p\u0159es HTTP dotazuje na remote cache, zda n\u011bkdo ji\u017e tento \u00fakol vykonal. Pokud ano, jednodu\u0161e st\u00e1hne v\u00fdsledek.<\/p>\n<p><strong><em>Spustit Gradle remote cache je snadn\u00e9, proto\u017ee Gradle poskytuje Docker obraz. Poda\u0159ilo se n\u00e1m to ud\u011blat za t\u0159i hodiny.<\/em><\/strong><\/p>\n<p>Sta\u010dilo jen spustit Docker a napsat jednu \u0159\u00e1dku do projektu. Ale a\u010dkoliv jej lze rychle spustit, pro to, aby v\u0161e dob\u0159e fungovalo, bude pot\u0159eba dost \u010dasu.<\/p>\n<p>N\u00ed\u017ee je graf cache misses.<\/p>\n<p><img decoding=\"async\" alt=\"Evoluce CI v t\u00fdmu mobiln\u00edho v\u00fdvoje\" src=\"\/wp-content\/uploads\/2019\/04\/458ef23b5506b04f3bd385be0c18607a.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nNa za\u010d\u00e1tku byl procento prom\u00e1chnut\u00ed mimo cache kolem 65. Po t\u0159ech t\u00fddnech jsme dok\u00e1zali tuto hodnotu sn\u00ed\u017eit na 20%. Uk\u00e1zalo se, \u017ee \u00fakoly, kter\u00e9 sestavuje Android aplikace, maj\u00ed podivn\u00e9 transitivn\u00ed z\u00e1vislosti, kv\u016fli kter\u00fdm Gradle prom\u00e1chnul mimo cache.<\/p>\n<p>P\u0159ipojen\u00edm cache jsme v\u00fdrazn\u011b zrychlili sestaven\u00ed. Ale krom\u011b sestaven\u00ed se tak\u00e9 spou\u0161t\u00ed instrumenta\u010dn\u00ed testy a ty b\u011b\u017e\u00ed dlouho. Mo\u017en\u00e1 nen\u00ed nutn\u00e9 spou\u0161t\u011bt v\u0161echny testy p\u0159i ka\u017ed\u00e9m pull requestu. Pro zji\u0161t\u011bn\u00ed tohoto pou\u017e\u00edv\u00e1me anal\u00fdzu dopadu.<\/p>\n<h3>Anal\u00fdza dopadu<\/h3>\n<p>\nP\u0159i pull requestu shroma\u017e\u010fujeme git diff a nach\u00e1z\u00edme zm\u011bn\u011bn\u00e9 Gradle moduly.<\/p>\n<p><img decoding=\"async\" alt=\"Evoluce CI v t\u00fdmu mobiln\u00edho v\u00fdvoje\" src=\"\/wp-content\/uploads\/2019\/04\/471ca37206a0da4d5741c8ee1dbb7f03.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nM\u00e1 smysl spou\u0161t\u011bt pouze ty instrumenta\u010dn\u00ed testy, kter\u00e9 kontroluj\u00ed zm\u011bn\u011bn\u00e9 moduly a v\u0161echny moduly, kter\u00e9 od nich z\u00e1vis\u00ed. Testy pro sousedn\u00ed moduly nemaj\u00ed smysl spou\u0161t\u011bt: tam se k\u00f3d nezm\u011bnil a nic se nem\u016f\u017ee rozb\u00edt.<\/p>\n<p>S instrumenta\u010dn\u00edmi testy to nen\u00ed tak jednoduch\u00e9, proto\u017ee mus\u00ed b\u00fdt um\u00edst\u011bny v nejvy\u0161\u0161\u00edm \u00farov\u0148ov\u00e9m modulu Aplikace. Pou\u017eili jsme heuristiku s anal\u00fdzou bytecode, abychom pochopili, k jak\u00e9mu modulu se ka\u017ed\u00fd test vztahuje.<\/p>\n<p><strong><em>Modernizace pr\u00e1ce instrumenta\u010dn\u00edch test\u016f, aby kontrolovaly pouze zapojen\u00e9 moduly, trvala asi osm t\u00fddn\u016f.<\/em><\/strong><\/p>\n<p>Opat\u0159en\u00ed ke zrychlen\u00ed kontrol se \u00fasp\u011b\u0161n\u011b vyda\u0159ila. Z 45 minut jsme se dostali na p\u0159ibli\u017en\u011b 15. \u010ctvrt hodiny \u010dek\u00e1n\u00ed na build je u\u017e v po\u0159\u00e1dku.<\/p>\n<p>Ale nyn\u00ed za\u010dali v\u00fdvoj\u00e1\u0159i st\u011b\u017eovat, \u017ee je pro n\u011b nejasn\u00e9, kter\u00e9 buildy se spou\u0161t\u011bj\u00ed, kde se pod\u00edvat na log, pro\u010d je build \u010derven\u00fd, kter\u00fd test selhal, atd.<\/p>\n<p><img decoding=\"async\" alt=\"Evoluce CI v t\u00fdmu mobiln\u00edho v\u00fdvoje\" src=\"\/wp-content\/uploads\/2019\/04\/aa76151174cd4e5a45ff281818e312f5.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nProbl\u00e9my s reakc\u00ed zpomaluj\u00ed v\u00fdvoj, proto jsme se sna\u017eili poskytnout co nejjasn\u011bj\u0161\u00ed a nejpodrobn\u011bj\u0161\u00ed informace o ka\u017ed\u00e9m PR a buildu. Za\u010dali jsme s koment\u00e1\u0159i v Bitbucketu k PR, kde jsme uvedli, kter\u00fd build selhal a pro\u010d, psali jsme adresn\u00e9 zpr\u00e1vy do Slacku. Nakonec jsme vytvo\u0159ili pro str\u00e1nku PR dashboard se seznamem v\u0161ech build\u016f, kter\u00e9 pr\u00e1v\u011b b\u011b\u017e\u00ed, a jejich stavem: \u010dek\u00e1, b\u011b\u017e\u00ed, selhal nebo skon\u010dil. M\u016f\u017eete kliknout na build a dostat se k jeho logu.<\/p>\n<p><img decoding=\"async\" alt=\"Evoluce CI v t\u00fdmu mobiln\u00edho v\u00fdvoje\" src=\"\/wp-content\/uploads\/2019\/04\/2d16b7a6b6d23e23903d291ef1526999.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\n<strong><em>Na podrobnou zp\u011btnou vazbu bylo vynalo\u017eeno \u0161est t\u00fddn\u016f.<\/em><\/strong><\/p>\n<h2>Pl\u00e1ny<\/h2>\n<p>\nP\u0159ejdeme k nejnov\u011bj\u0161\u00ed historii. Vy\u0159e\u0161en\u00edm ot\u00e1zky zp\u011btn\u00e9 vazby jsme se dostali na novou \u00farove\u0148 \u2014 rozhodli jsme se postavit na\u0161i farmu emul\u00e1tor\u016f. Kdy\u017e je test\u016f a emul\u00e1tor\u016f mnoho, je s nimi obt\u00ed\u017en\u00e9 manipulovat. Nakonec se v\u0161ichni na\u0161i emul\u00e1to\u0159i p\u0159est\u011bhovali do k8s clusteru s flexibiln\u00edm \u0159\u00edzen\u00edm zdroj\u016f.<\/p>\n<p>Krom\u011b toho m\u00e1me i dal\u0161\u00ed pl\u00e1ny.<\/p>\n<ul>\n<li><strong>Vr\u00e1tit Lint<\/strong> (a dal\u0161\u00ed statickou anal\u00fdzu). Ji\u017e na tom pracujeme.<\/li>\n<li>Spou\u0161t\u011bt na PR blokovan\u00e9m v\u0161echny <strong>end-to-end testy<\/strong> na v\u0161ech verz\u00edch SDK.<\/li>\n<\/ul>\n<p>\nTak\u017ee jsme sledovali historii v\u00fdvoje Continuous Integration v Avitu. Nyn\u00ed chci d\u00e1t n\u011bkolik rad z pohledu zku\u0161en\u00e9ho.<\/p>\n<h1>Tipy<\/h1>\n<p>\nKdybych mohl d\u00e1t pouze jednu radu, byla by to tato:<\/p>\n<blockquote><p>Pros\u00edm, bu\u010fte opatrn\u011bj\u0161\u00ed s shell skripty!<\/p><\/blockquote>\n<p>\nBash je velmi flexibiln\u00ed a mocn\u00fd n\u00e1stroj, na n\u011bm\u017e je velmi pohodln\u00e9 a rychl\u00e9 ps\u00e1t skripty. Ale m\u016f\u017eete se dostat do pasti, a my, bohu\u017eel, do n\u00ed padli.<\/p>\n<p>V\u0161echno to za\u010dalo jednoduch\u00fdmi skripty, kter\u00e9 se spou\u0161t\u011bly na na\u0161ich build stroj\u00edch:<\/p>\n<pre><code class=\"bash\">#!\/usr\/bin\/env bash\n.\/gradlew assembleDebug<\/code><\/pre>\n<p>\nAle jak v\u00edme, v\u0161e se \u010dasem vyv\u00edj\u00ed a komplikuje \u2014 poj\u010fme spustit jeden skript z druh\u00e9ho, poj\u010fme tam p\u0159edat n\u011bjak\u00e9 parametry \u2014 nakonec bylo nutn\u00e9 napsat funkci, kter\u00e1 ur\u010duje, na jak\u00e9 \u00farovni vno\u0159en\u00ed bash se pr\u00e1v\u011b nach\u00e1z\u00edme, abychom vlo\u017eili pot\u0159ebn\u00e9 uvozovky, aby se to v\u0161echno spustilo.<\/p>\n<p><img decoding=\"async\" alt=\"Evoluce CI v t\u00fdmu mobiln\u00edho v\u00fdvoje\" src=\"\/wp-content\/uploads\/2019\/04\/244a574f7b5d3b4fc77cfc4ccda2db69.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nDok\u00e1\u017eete si p\u0159edstavit n\u00e1klady na v\u00fdvoj takov\u00fdch skript\u016f. Doporu\u010duji se nedost\u00e1vat do t\u00e9to pasti.<\/p>\n<p>\u010c\u00edm lze nahradit?<\/p>\n<ul>\n<li>Jak\u00fdmkoliv skriptovac\u00edm jazykem. Ps\u00e1t v <strong>Pythonu nebo Kotlin Script<\/strong> je pohodln\u011bj\u0161\u00ed, proto\u017ee to je programov\u00e1n\u00ed, ne skripty.<\/li>\n<li>Nebo popsat celou logiku build\u016f ve form\u011b <strong>Custom gradle tasks<\/strong> pro v\u00e1\u0161 projekt.<\/li>\n<\/ul>\n<p>\nRozhodli jsme se zvolit druhou mo\u017enost a nyn\u00ed systematicky odstra\u0148ujeme v\u0161echny bash skripty a p\u00ed\u0161eme mnoho custom gradle \u00faloh.<\/p>\n<p><strong>Rada \u010d. 2: uchov\u00e1vejte infrastrukturu v k\u00f3du.<\/strong><\/p>\n<p>Je pohodln\u00e9, kdy\u017e je nastaven\u00ed Continuous Integration ulo\u017eeno ne v UI rozhran\u00ed Jenkins nebo TeamCity atd., ale ve form\u011b textov\u00fdch soubor\u016f p\u0159\u00edmo v repozit\u00e1\u0159i projektu. To poskytuje verzov\u00e1n\u00ed. Nebude obt\u00ed\u017en\u00e9 se vr\u00e1tit zp\u011bt nebo sestavit k\u00f3d na jin\u00e9 v\u011btvi.<\/p>\n<p>Skripty lze uchov\u00e1vat v projektu. A co d\u011blat s prost\u0159ed\u00edm?<\/p>\n<p><strong>Tip \u010d. 3: s prost\u0159ed\u00edm m\u016f\u017ee pomoci Docker.<\/strong><\/p>\n<p>Android v\u00fdvoj\u00e1\u0159\u016fm ur\u010dit\u011b pom\u016f\u017ee, iOS zat\u00edm ne, bohu\u017eel.<\/p>\n<p>Toto je p\u0159\u00edklad jednoduch\u00e9ho dockerfile, kter\u00fd obsahuje jdk a 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# St\u00e1hnout 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# Nainstalovat Android Build Tool a knihovny\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>\nNapsal jsem tento dockerfile (\u0159eknu v\u00e1m potaj\u00ed, \u017ee ho nemus\u00edte ps\u00e1t, ale m\u016f\u017eete st\u00e1hnout hotov\u00fd z GitHubu) a po sestaven\u00ed obrazu z\u00edsk\u00e1te virtu\u00e1ln\u00ed stroj, na kter\u00e9m m\u016f\u017eete sestavovat aplikaci a spou\u0161t\u011bt Junit testy.<\/p>\n<p>Dv\u011b hlavn\u00ed argumenty, pro\u010d to m\u00e1 smysl: \u0161k\u00e1lovatelnost a opakovatelnost. S pou\u017eit\u00edm dockeru m\u016f\u017eete rychle spustit des\u00edtku build agent\u016f, kte\u0159\u00ed budou m\u00edt p\u0159esn\u011b stejn\u00e9 prost\u0159ed\u00ed jako ten p\u0159edchoz\u00ed. To velmi usnad\u0148uje \u017eivot CI in\u017een\u00fdr\u016fm. Nacpat android-sdk do dockeru je docela jednoduch\u00e9, s emul\u00e1tory je to o n\u011bco slo\u017eit\u011bj\u0161\u00ed: budete muset trochu zap\u0159em\u00fd\u0161let (nebo zase st\u00e1hnout hotov\u00e9 z GitHubu).<\/p>\n<p><strong>Tip \u010d. 4: nezapom\u00ednejte, \u017ee kontroly se d\u011blaj\u00ed ne pro kontrolu, ale pro lidi.<\/strong><\/p>\n<p>V\u00fdvoj\u00e1\u0159\u016fm je velmi d\u016fle\u017eit\u00e1 rychl\u00e1 a p\u0159edev\u0161\u00edm srozumiteln\u00e1 zp\u011btn\u00e1 vazba: co se jim rozbilo, kter\u00fd test selhal, kde se pod\u00edvat na build log.<\/p>\n<p><strong>Tip \u010d. 5: bu\u010fte pragmati\u010dt\u00ed p\u0159i v\u00fdvoji Continuous Integration.<\/strong><\/p>\n<p>Jasn\u011b pochopte, jak\u00e9 typy chyb chcete zabr\u00e1nit, kolik jste ochotni vynalo\u017eit zdroj\u016f, \u010dasu a v\u00fdpo\u010detn\u00edho \u010dasu. P\u0159\u00edli\u0161 dlouh\u00e9 kontroly m\u016f\u017eete nap\u0159\u00edklad p\u0159esunout na noc. A od t\u011bch, kter\u00e9 odhaluj\u00ed m\u00e9n\u011b d\u016fle\u017eit\u00e9 chyby, se zcela vzd\u00e1t.<\/p>\n<p><strong>Tip \u010d. 6: pou\u017e\u00edvejte hotov\u00e9 n\u00e1stroje.<\/strong><\/p>\n<p>V sou\u010dasnosti existuje mnoho spole\u010dnost\u00ed, kter\u00e9 poskytuj\u00ed cloudov\u00fd CI.<\/p>\n<p><img decoding=\"async\" alt=\"Evoluce CI v t\u00fdmu mobiln\u00edho v\u00fdvoje\" src=\"\/wp-content\/uploads\/2019\/04\/e57aabd49ec01d14e83b64331fd84ec5.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nPro mal\u00e9 t\u00fdmy je to dobr\u00e9 \u0159e\u0161en\u00ed. Nen\u00ed pot\u0159eba nic udr\u017eovat, jen zaplat\u00edte trochu pen\u011bz, shrom\u00e1\u017ed\u00edte svou aplikaci a dokonce spust\u00edte instrumenta\u010dn\u00ed testy.<\/p>\n<p><strong>Tip \u010d. 7: ve velk\u00e9m t\u00fdmu jsou v\u00fdhodn\u011bj\u0161\u00ed in-house \u0159e\u0161en\u00ed.<\/strong><\/p>\n<p>Ale d\u0159\u00edve nebo pozd\u011bji, s r\u016fstem t\u00fdmu, se st\u00e1vaj\u00ed v\u00fdhodn\u011bj\u0161\u00ed in-house \u0159e\u0161en\u00ed. S t\u011bmito \u0159e\u0161en\u00edmi je jeden aspekt. V ekonomice existuje z\u00e1kon klesaj\u00edc\u00edho v\u00fdnosu: v ka\u017ed\u00e9m projektu ka\u017ed\u00e9 dal\u0161\u00ed zlep\u0161en\u00ed je st\u00e1le obt\u00ed\u017en\u011bj\u0161\u00ed a vy\u017eaduje st\u00e1le v\u00edce investic.<\/p>\n<p>Ekonomika popisuje cel\u00fd n\u00e1\u0161 \u017eivot, v\u010detn\u011b Continuous Integration. Vytvo\u0159il jsem graf pracovn\u00edch n\u00e1klad\u016f na ka\u017edou f\u00e1zi v\u00fdvoje na\u0161eho Continuous Integration.<\/p>\n<p><img decoding=\"async\" alt=\"Evoluce CI v t\u00fdmu mobiln\u00edho v\u00fdvoje\" src=\"\/wp-content\/uploads\/2019\/04\/97bf8bff64f3ac9ae587fae195251da2.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nJe z\u0159ejm\u00e9, \u017ee jak\u00e9koliv zlep\u0161en\u00ed je st\u00e1le obt\u00ed\u017en\u011bj\u0161\u00ed a obt\u00ed\u017en\u011bj\u0161\u00ed. Pohledem na tento graf lze pochopit, \u017ee rozv\u00edjet Continuous Integration je pot\u0159eba v souladu s r\u016fstem velikosti t\u00fdmu. Pro t\u00fdm se dv\u011bma lidmi je pl\u00fdtvat 50 dn\u00ed na v\u00fdvoj intern\u00ed farmy emul\u00e1tor\u016f dost \u0161patn\u00fd n\u00e1pad. Ale na druhou stranu, pro velk\u00fd t\u00fdm se \u00fapln\u011b nev\u011bnovat Continuous Integration \u2014 tak\u00e9 nen\u00ed dobr\u00fd n\u00e1pad, proto\u017ee na probl\u00e9my s integrac\u00ed, opravu komunikace atd. by bylo pot\u0159eba je\u0161t\u011b v\u00edce \u010dasu.<\/p>\n<p>Za\u010dali jsme t\u00edm, \u017ee automatizace je pot\u0159eba, proto\u017ee lid\u00e9 jsou drah\u00ed, d\u011blaj\u00ed chyby a leno\u0161i. Ale automatizaci d\u011blaj\u00ed tak\u00e9 lid\u00e9. Proto se v\u0161echny tyto probl\u00e9my vztahuj\u00ed i na automatizaci.<\/p>\n<ul>\n<li>Automatizace je drah\u00e1. Vzpome\u0148te si na graf pracovn\u00edch n\u00e1klad\u016f.<\/li>\n<li>P\u0159i automatizaci lid\u00e9 d\u011blaj\u00ed chyby.<\/li>\n<li>N\u011bkdy je velmi lenost automatizovat, proto\u017ee to funguje i tak. Pro\u010d je\u0161t\u011b n\u011bco zlep\u0161ovat, pro\u010d cel\u00fd ten Continuous Integration?<\/li>\n<\/ul>\n<p>\nAle m\u00e1m statistiky: u 20 % build\u016f se odhaluj\u00ed chyby. A to se ned\u011bje proto, \u017ee by na\u0161i v\u00fdvoj\u00e1\u0159i psali \u0161patn\u00fd k\u00f3d. D\u011bje se to, proto\u017ee v\u00fdvoj\u00e1\u0159i jsou p\u0159esv\u011bd\u010deni, \u017ee pokud ud\u011blaj\u00ed n\u011bjakou chybu, nedostane se do developu, odhal\u00ed ji automatizovan\u00e9 kontroly. Odpov\u00eddaj\u00edc\u00edm zp\u016fsobem mohou v\u00fdvoj\u00e1\u0159i str\u00e1vit v\u00edce \u010dasu psan\u00edm k\u00f3du a zaj\u00edmav\u00fdmi v\u011bcmi, m\u00edsto aby lok\u00e1ln\u011b n\u011bco spou\u0161t\u011bli a kontrolovali.<\/p>\n<p><strong>V\u011bnujte se Continuous Integration. Ale s m\u00edrou.<\/strong><\/p>\n<blockquote><p>Mimochodem, Nikolaj Nesterov nejen d\u011bl\u00e1 \u00fa\u017easn\u00e9 prezentace, ale tak\u00e9 je sou\u010d\u00e1st\u00ed programov\u00e9ho v\u00fdboru <noindex><a rel=\"nofollow\" href=\"https:\/\/appsconf.ru\/moscow\/2019\">AppsConf<\/a><\/noindex> a pom\u00e1h\u00e1 ostatn\u00edm p\u0159ipravit pro v\u00e1s obsahov\u00e9 vystoupen\u00ed. \u00daplnost a u\u017eite\u010dnost programu nadch\u00e1zej\u00edc\u00ed konference lze posoudit podle t\u00e9mat v <noindex><a rel=\"nofollow\" href=\"https:\/\/appsconf.ru\/moscow\/2019\/schedule\">rozpisu<\/a><\/noindex>. A pro podrobnosti p\u0159ij\u010fte 22-23. dubna do Infoprostranstv\u00ed.<\/p><\/blockquote>\n<p>Zdroj: <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.3 - 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\/cs\/blog\/administrirovanie\/evolyutsiya-ci-v-komande-mobilnoj-razrabotki\" \/>\n\t\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.3\" \/>\n\t\t<meta property=\"og:locale\" content=\"cs_CZ\" \/>\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\/cs\/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\udd47Evoluce CI v t\u00fdmu mobiln\u00edho v\u00fdvoje | ProHoster","description":"Dnes v\u011bt\u0161ina softwarov\u00fdch produkt\u016f vznik\u00e1 v t\u00fdmech.","canonical_url":"https:\/\/prohoster.info\/cs\/blog\/administrirovanie\/evolyutsiya-ci-v-komande-mobilnoj-razrabotki","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"cs_CZ","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\/cs\/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\/cs\/wp-json\/wp\/v2\/posts\/31304","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/cs\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/cs\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/cs\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/cs\/wp-json\/wp\/v2\/comments?post=31304"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/cs\/wp-json\/wp\/v2\/posts\/31304\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/cs\/wp-json\/wp\/v2\/media\/23270"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/cs\/wp-json\/wp\/v2\/media?parent=31304"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/cs\/wp-json\/wp\/v2\/categories?post=31304"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/cs\/wp-json\/wp\/v2\/tags?post=31304"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}