{"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\/pl\/blog\/administrirovanie\/evolyutsiya-ci-v-komande-mobilnoj-razrabotki","title":{"rendered":"Ewolucja CI w zespole rozwoju mobilnego","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Dzi\u015b wi\u0119kszo\u015b\u0107 produkt\u00f3w programistycznych jest tworzonych w zespo\u0142ach. Warunki sukcesu w pracy zespo\u0142owej mo\u017cna przedstawi\u0107 w postaci prostej schemy.<\/p>\n<p><img decoding=\"async\" alt=\"Ewolucja CI w zespole rozwoju mobilnego\" src=\"\/wp-content\/uploads\/2019\/04\/b283cfd7772d8f479be0754ebbe51b19.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nPisz\u0105c kod, musisz upewni\u0107 si\u0119, \u017ce on:<\/p>\n<ol>\n<li>Dzia\u0142a.<\/li>\n<li>Nie psuje niczego, w tym kodu napisanego przez twoich koleg\u00f3w.<\/li>\n<\/ol>\n<p>\nJe\u015bli oba warunki s\u0105 spe\u0142nione, jeste\u015b na drodze do sukcesu. Aby \u0142atwo sprawdza\u0107 te warunki i nie zbacza\u0107 z korzystnej \u015bcie\u017cki, wymy\u015blono Continuous Integration.<\/p>\n<p>CI to proces roboczy, w kt\u00f3rym mo\u017cliwie jak najcz\u0119\u015bciej integrujesz sw\u00f3j kod z og\u00f3lnym kodem produktu. I nie tylko integrujesz, ale tak\u017ce nieustannie sprawdzasz, czy wszystko dzia\u0142a. Poniewa\u017c trzeba cz\u0119sto i dok\u0142adnie sprawdza\u0107, warto pomy\u015ble\u0107 o automatyzacji. Mo\u017cna wszystko sprawdza\u0107 r\u0119cznie, ale nie warto, oto dlaczego.<br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<ul>\n<li><strong>Ludzie s\u0105 kosztowni<\/strong>. Godzina pracy dowolnego programisty jest dro\u017csza ni\u017c godzina pracy dowolnego serwera.<\/li>\n<li><strong>Ludzie si\u0119 myl\u0105<\/strong>. Dlatego mog\u0105 wyst\u0105pi\u0107 sytuacje, w kt\u00f3rych uruchamiasz testy na niew\u0142a\u015bciwej ga\u0142\u0119zi lub kompilujesz nie ten commit do tester\u00f3w.<\/li>\n<li><strong>Ludzie s\u0105 leniwi<\/strong>. Czasami, gdy ko\u0144cz\u0119 jakiekolwiek zadanie, pojawia si\u0119 my\u015bl: \"Co tu sprawdza\u0107? Napisa\u0142em dwie linijki \u2014 na pewno wszystko dzia\u0142a!\" S\u0105dz\u0119, \u017ce niekt\u00f3rzy z was te\u017c czasami maj\u0105 takie my\u015bli. Ale sprawdza\u0107 trzeba zawsze.<\/li>\n<\/ul>\n<p>\nJak wdro\u017cono i rozwijano Continuous Integration w zespole mobilnym Avito, jak przeszli od 0 do 450 kompilacji dziennie i co maszyny buduj\u0105ce zbieraj\u0105 200 godzin dziennie, opowiada Nikolaj Nesterow (<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/users\/nnesterov\/\" class=\"user_link\">nnesterov<\/a><\/noindex>) \u2014 uczestnik wszystkich ewolucyjnych zmian CI\/CD aplikacji na Androida.<\/p>\n<p>Opowie\u015b\u0107 zbudowana jest na przyk\u0142adzie zespo\u0142u Android, ale wi\u0119kszo\u015b\u0107 podej\u015b\u0107 jest r\u00f3wnie\u017c zastosowalna w 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=\"Odtwarzaj wideo\" loading=\"lazy\" width=\"480\" height=\"360\" style=\"width:100%;height:auto;\">\r\n        <div class=\"play-button\"><\/div>\r\n    <\/div><\/center><br \/>\nDawno temu w zespole Android Avito pracowa\u0142a tylko jedna osoba. Z definicji nie potrzebowa\u0142 on niczego z Continuous Integration: nie mia\u0142 z kim si\u0119 integrowa\u0107.<\/p>\n<p>Jednak aplikacja ros\u0142a, pojawia\u0142o si\u0119 coraz wi\u0119cej zada\u0144, a w zwi\u0105zku z tym powi\u0119ksza\u0142 si\u0119 zesp\u00f3\u0142. W pewnym momencie nasta\u0142 czas, aby bardziej formalnie uregulowa\u0107 proces integracji kodu. Zdecydowano si\u0119 na wykorzystanie Git flow.<\/p>\n<p><img decoding=\"async\" alt=\"Ewolucja CI w zespole rozwoju mobilnego\" src=\"\/wp-content\/uploads\/2019\/04\/f433effc5ab5a59de3d6bd20a7a87057.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nKoncepcja Git flow jest znana: w projekcie istnieje jedna wsp\u00f3lna ga\u0142\u0105\u017a develop, a dla ka\u017cdej nowej funkcji programi\u015bci tworz\u0105 osobn\u0105 ga\u0142\u0105\u017a, commituj\u0105 do niej, puszczaj\u0105, a gdy chc\u0105 w\u0142\u0105czy\u0107 sw\u00f3j kod do ga\u0142\u0119zi develop, otwieraj\u0105 pull request. W celu wymiany wiedzy i om\u00f3wienia podej\u015b\u0107 wprowadzili\u015bmy code review, co oznacza, \u017ce koledzy musz\u0105 sprawdzi\u0107 i zatwierdzi\u0107 kod nawzajem.<\/p>\n<h2>Sprawdzenia<\/h2>\n<p>\nPatrzenie na kod oczami \u2014 to \u015bwietna sprawa, ale niewystarczaj\u0105ca. Dlatego wprowadzone s\u0105 automatyczne kontrole.<\/p>\n<ul>\n<li>Na pocz\u0105tek sprawdzamy <strong>kompilacj\u0119 ARC<\/strong>.<\/li>\n<li>Wiele <strong>test\u00f3w Junit<\/strong>.<\/li>\n<li><strong>Obliczamy zasi\u0119g kodu<\/strong>, skoro uruchamiamy testy.<\/li>\n<\/ul>\n<p>\nAby zrozumie\u0107, jak nale\u017cy uruchamia\u0107 te kontrole, przyjrzymy si\u0119 procesowi rozwoju w Avito.<\/p>\n<p>Schematycznie mo\u017cna go przedstawi\u0107 w ten spos\u00f3b:<\/p>\n<ul>\n<li>Programista pisze kod na swoim laptopie. Mo\u017cna uruchomi\u0107 kontrole integracji tutaj \u2014 albo za pomoc\u0105 hooka commit, albo po prostu uruchamiaj\u0105c kontrole w tle.<\/li>\n<li>Po tym, jak programista wypchnie kod, otwiera pull request. Aby jego kod trafi\u0142 do ga\u0142\u0119zi develop, musi przej\u015b\u0107 code review i uzyska\u0107 wymagan\u0105 liczb\u0119 zatwierdze\u0144. Mo\u017cna w\u0142\u0105czy\u0107 kontrole i kompilacje tutaj: dop\u00f3ki nie wszystkie kompilacje s\u0105 udane, pull request nie mo\u017ce zosta\u0107 scalony.<\/li>\n<li>Po tym, jak pull request zostanie scalony i kod trafi do develop, mo\u017cna wybra\u0107 odpowiedni czas: na przyk\u0142ad w nocy, kiedy wszystkie serwery s\u0105 wolne, i uruchomi\u0107 kontrole na maxa.<\/li>\n<\/ul>\n<p>\nUruchamianie kontroli na swoim laptopie nikomu si\u0119 nie podoba\u0142o. Kiedy programista sko\u0144czy\u0142 funkcj\u0119, chcia\u0142 jak najszybciej j\u0105 wypchn\u0105\u0107 i otworzy\u0107 pull request. Je\u015bli w tym momencie uruchamiane s\u0105 d\u0142ugie kontrole, to nie tylko jest to ma\u0142o przyjemne, ale tak\u017ce spowalnia rozw\u00f3j: dop\u00f3ki laptop co\u015b sprawdza, nie mo\u017cna na nim normalnie pracowa\u0107.<\/p>\n<p>Bardzo spodoba\u0142o nam si\u0119 uruchamianie kontroli w nocy, poniewa\u017c jest du\u017co czasu i serwer\u00f3w, mo\u017cna sobie poszale\u0107. Niestety, gdy kod funkcji trafi\u0142 do develop, programista mia\u0142 znacznie mniej motywacji, aby naprawi\u0107 b\u0142\u0119dy, kt\u00f3re znalaz\u0142o CI. Czasami \u0142apa\u0142em si\u0119 na tym, \u017ce patrz\u0105c na poranny raport o wszystkich znalezionych b\u0142\u0119dach my\u015bla\u0142em, \u017ce naprawi\u0119 je kiedy\u015b p\u00f3\u017aniej, poniewa\u017c teraz w Jira czeka niesamowite nowe zadanie, kt\u00f3re chcia\u0142bym jak najszybciej zacz\u0105\u0107 realizowa\u0107.<\/p>\n<p>Je\u015bli kontrole blokuj\u0105 pull request, to motywacja jest wystarczaj\u0105ca, bo dop\u00f3ki kompilacje nie b\u0119d\u0105 zielone, kod nie trafi do develop, a to oznacza, \u017ce zadanie nie b\u0119dzie zako\u0144czone.<\/p>\n<p>Ostatecznie wybrali\u015bmy tak\u0105 strategi\u0119: w nocy uruchamiamy mo\u017cliwie najwi\u0119kszy zestaw test\u00f3w, a najwa\u017cniejsze i przede wszystkim najszybsze uruchamiamy przy \u017c\u0105daniu pull request. Ale na tym nie przestajemy \u2014 r\u00f3wnolegle optymalizujemy czas przechodzenia test\u00f3w, aby przenie\u015b\u0107 je z trybu nocnego do test\u00f3w przy \u017c\u0105daniu pull request.<\/p>\n<p>W tamtym czasie wszystkie nasze kompilacje odbywa\u0142y si\u0119 wystarczaj\u0105co szybko, dlatego po prostu w\u0142\u0105czyli\u015bmy blokad\u0119 dla \u017c\u0105dania pull request dla kompilacji ARC, test\u00f3w Junit i oblicze\u0144 pokrycia kodu. W\u0142\u0105czyli\u015bmy, pomy\u015bleli\u015bmy \u2014 i zrezygnowali\u015bmy z pokrycia kodu, poniewa\u017c uznali\u015bmy, \u017ce jest nam niepotrzebne.<\/p>\n<p><strong><em>Ca\u0142a konfiguracja podstawowego CI zaj\u0119\u0142a nam dwa dni (tutaj i dalej oszacowanie czasowe jest przybli\u017cone, potrzebne dla kontekstu). <\/em><\/strong><\/p>\n<p>Po tym zacz\u0119li\u015bmy si\u0119 zastanawia\u0107 \u2014 czy w\u0142a\u015bciwie w og\u00f3le testujemy? Czy w\u0142a\u015bciwie uruchamiamy budowy przy \u017c\u0105daniu pull request?<\/p>\n<p>Uruchamiali\u015bmy kompilacj\u0119 na ostatnim commicie ga\u0142\u0119zi, na kt\u00f3rej otwarto pull request. Ale testy tego commita mog\u0105 pokaza\u0107 tylko, \u017ce kod napisany przez programist\u0119 dzia\u0142a. Nie dowodz\u0105 one, \u017ce nic nie zepsu\u0142. W rzeczywisto\u015bci trzeba sprawdzi\u0107 stan ga\u0142\u0119zi develop po tym, jak funkcjonalno\u015b\u0107 zostanie w ni\u0105 w\u0142\u0105czona.<\/p>\n<p><img decoding=\"async\" alt=\"Ewolucja CI w zespole rozwoju mobilnego\" src=\"\/wp-content\/uploads\/2019\/04\/c1a179b68b02c04031177f9bf51a81ed.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nW tym celu napisali\u015bmy prosty skrypt bash <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>\nTutaj pobierane s\u0105 wszystkie najnowsze zmiany z develop i w\u0142\u0105czane do bie\u017c\u0105cej ga\u0142\u0119zi. Dodali\u015bmy skrypt premerge.sh jako pierwszy krok wszystkich kompilacji i zacz\u0119li\u015bmy sprawdza\u0107 dok\u0142adnie to, co chcemy, czyli <strong>integracj\u0119<\/strong>.<\/p>\n<p><strong><em>Na lokalizacj\u0119 problemu, znalezienie rozwi\u0105zania i napisanie tego skryptu zaj\u0119\u0142o nam trzy dni.<\/em><\/strong><\/p>\n<p>Aplikacja rozwija\u0142a si\u0119, pojawia\u0142o si\u0119 coraz wi\u0119cej zada\u0144, zesp\u00f3\u0142 si\u0119 powi\u0119ksza\u0142, a premerge.sh czasami zaczyna\u0142 nas zawodzi\u0107. W develop przenika\u0142y konflikty zmian, kt\u00f3re \u0142ama\u0142y kompilacj\u0119.<\/p>\n<p>Przyk\u0142ad tego, jak to si\u0119 dzieje:<\/p>\n<p><img decoding=\"async\" alt=\"Ewolucja CI w zespole rozwoju mobilnego\" src=\"\/wp-content\/uploads\/2019\/04\/6762d9ce4e431549455c49f2317a8f20.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nDwaj programi\u015bci jednocze\u015bnie zaczynaj\u0105 pracowa\u0107 nad funkcjami A i B. Programista funkcji A odkrywa w projekcie nieu\u017cywan\u0105 funkcj\u0119 <code>answer()<\/code> i, jak dobry skaut, j\u0105 usuwa. W tym samym czasie programista funkcji B w swojej ga\u0142\u0119zi dodaje nowe wywo\u0142anie tej funkcji.<\/p>\n<p>Programi\u015bci ko\u0144cz\u0105 prac\u0119 i w tym samym czasie otwieraj\u0105 pull request. Uruchamiane s\u0105 kompilacje, premerge.sh sprawdza oba \u017c\u0105dania pull request w odniesieniu do \u015bwie\u017cego stanu develop \u2014 wszystkie testy s\u0105 zielone. Po tym \u0142\u0105czony jest pull request funkcji A, a nast\u0119pnie pull request funkcji B\u2026 Bum! Develop si\u0119 \u0142amie, poniewa\u017c w kodzie develop jest wywo\u0142anie nieistniej\u0105cej funkcji.<\/p>\n<p><img decoding=\"async\" alt=\"Ewolucja CI w zespole rozwoju mobilnego\" src=\"\/wp-content\/uploads\/2019\/04\/0261dcc9f7f2e5e018ab136081b4679b.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nKiedy develop si\u0119 nie kompiluje, to <strong>lokalna katastrofa<\/strong>. Ca\u0142y zesp\u00f3\u0142 nie mo\u017ce nic zebra\u0107 i odda\u0107 do test\u00f3w.<\/p>\n<p>Zdarzy\u0142o si\u0119, \u017ce zajmowa\u0142em si\u0119 g\u0142\u00f3wnie zadaniami infrastrukturalnymi: analityka, sie\u0107, bazy danych. To znaczy, \u017ce to ja pisa\u0142em funkcje i klasy, kt\u00f3re wykorzystuj\u0105 inni programi\u015bci. Z tego powodu cz\u0119sto stawa\u0142em w podobnych sytuacjach. Mia\u0142em nawet jaki\u015b czas tak\u0105 grafik\u0119.<\/p>\n<p><img decoding=\"async\" alt=\"Ewolucja CI w zespole rozwoju mobilnego\" src=\"\/wp-content\/uploads\/2019\/04\/0eb1aadd05b33ce995027ab52d5c1e53.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nPoniewa\u017c to nam nie odpowiada\u0142o, zacz\u0119li\u015bmy opracowywa\u0107 opcje, jak to zapobiec.<\/p>\n<h2>Jak nie \u0142ama\u0107 develop<\/h2>\n<p>\nPierwsza opcja: <strong>przebudowa\u0107 wszystkie pull requesty przy aktualizacji develop. <\/strong>Je\u015bli w naszym przyk\u0142adzie pull request z funkcjonalno\u015bci\u0105 A jako pierwszy trafi do develop, pull request funkcjonalno\u015bci B zostanie przebudowany i odpowiednio, kontrole nie przejd\u0105 z powodu b\u0142\u0119du kompilacji.<\/p>\n<p>Aby zrozumie\u0107, ile czasu to zajmie, rozwa\u017cmy przyk\u0142ad z dwoma PR. Otwieramy dwa PR: dwa buildy, dwa uruchomienia kontrolek. Po tym, jak pierwszy PR zostanie scalony z develop, drugi musi zosta\u0107 przebudowany. W sumie, na dwa PR potrzeba trzech uruchomie\u0144 kontrolek: 2 + 1 = 3.<\/p>\n<p>W zasadzie to w porz\u0105dku. Ale przyjrzeli\u015bmy si\u0119 statystykom, a typow\u0105 sytuacj\u0105 w naszym zespole by\u0142o 10 otwartych PR, a wtedy liczba kontrolek to suma post\u0119puj\u0105cej: 10 + 9 +\u2026 + 1 = 55. To znaczy, \u017ceby zaakceptowa\u0107 10 PR, trzeba przebudowa\u0107 55 razy. I to w idealnej sytuacji, gdy wszystkie kontrole przechodz\u0105 za pierwszym razem, kiedy nikt nie otwiera dodatkowego pull requestu, podczas gdy ten dziesi\u0105tek jest przetwarzany.<\/p>\n<p>Pomy\u015bl sobie, \u017ce jeste\u015b programist\u0105, kt\u00f3ry musi zd\u0105\u017cy\u0107 nacisn\u0105\u0107 przycisk \u201emerge\u201d jako pierwszy, poniewa\u017c je\u015bli to zrobi s\u0105siad, to trzeba b\u0119dzie czeka\u0107, a\u017c wszystkie buildy przejd\u0105 od nowa... Nie, tak nie mo\u017ce by\u0107, to powa\u017cnie spowolni rozw\u00f3j.<\/p>\n<p>Drugi mo\u017cliwy spos\u00f3b: <strong>zbiera\u0107 pull request po code review. <\/strong>To znaczy, otwierasz pull request, zbierasz potrzebn\u0105 liczb\u0119 akceptacji od koleg\u00f3w, poprawiasz to, co trzeba, a nast\u0119pnie uruchamiasz buildy. Je\u015bli s\u0105 udane, pull request scalany jest z develop. W tym przypadku nie ma dodatkowych ponownych uruchomie\u0144, ale znacznie zwalnia to informacj\u0119 zwrotn\u0105. Jako programista, otwieraj\u0105c pull request, od razu chc\u0119 widzie\u0107, czy on si\u0119 skompiluje. Na przyk\u0142ad, je\u015bli jaki\u015b test nie przeszed\u0142, musz\u0119 go szybko naprawi\u0107. W przypadku op\u00f3\u017anionego builda zwalnia to informacj\u0119 zwrotn\u0105, a tym samym ca\u0142o\u015bciowy rozw\u00f3j. To tak\u017ce nam nie odpowiada\u0142o.<\/p>\n<p>W rezultacie zosta\u0142a tylko trzecia opcja \u2014 <strong>wymys\u0142a\u0107<\/strong>. Wszystkie nasze kody, wszystkie nasze \u017ar\u00f3d\u0142a s\u0105 przechowywane w repozytorium na serwerze Bitbucket. W zwi\u0105zku z tym musieli\u015bmy opracowa\u0107 wtyczk\u0119 do Bitbucket.<\/p>\n<p><img decoding=\"async\" alt=\"Ewolucja CI w zespole rozwoju mobilnego\" src=\"\/wp-content\/uploads\/2019\/04\/5e4b1f770ea1a3449b616c3101640294.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nTen wtyczka redefiniuje mechanizm \u0142\u0105czenia pull request\u00f3w. Rozpocz\u0119cie przebiega standardowo: otwierany jest PR, uruchamiane s\u0105 wszystkie kompilacje, przeprowadzana jest recenzja kodu. Jednak po pomy\u015blnym przej\u015bciu recenzji kodu, gdy deweloper zdecyduje si\u0119 nacisn\u0105\u0107 \u201emerge\u201d, wtyczka sprawdza, w odniesieniu do jakiego stanu develop uruchamiano kontrole. Je\u015bli po kompilacjach develop zd\u0105\u017cy\u0142 si\u0119 zaktualizowa\u0107, wtyczka nie pozwoli na po\u0142\u0105czenie takiego pull requestu z g\u0142\u00f3wn\u0105 ga\u0142\u0119zi\u0105. Po prostu ponownie uruchomi kompilacje w odniesieniu do \u015bwie\u017cego develop.<\/p>\n<p><img decoding=\"async\" alt=\"Ewolucja CI w zespole rozwoju mobilnego\" src=\"\/wp-content\/uploads\/2019\/04\/0edee880e1f2d286a0c64033103ac136.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nW naszym przyk\u0142adzie z konfliktuj\u0105cymi zmianami takie kompilacje nie przejd\u0105 z powodu b\u0142\u0119du kompilacji. W zwi\u0105zku z tym deweloper funkcji B b\u0119dzie musia\u0142 poprawi\u0107 kod, uruchomi\u0107 testy, a wtedy wtyczka automatycznie zastosuje pull request.<\/p>\n<p>Przed wdro\u017ceniem tej wtyczki mieli\u015bmy \u015brednio 2,7 uruchomienia testu na jeden pull request. Z wtyczk\u0105 jest ich 3,6. To by\u0142o dla nas satysfakcjonuj\u0105ce.<\/p>\n<p>Warto zauwa\u017cy\u0107, \u017ce ta wtyczka ma wad\u0119: uruchamia kompilacj\u0119 tylko jeden raz. Oznacza to, \u017ce wci\u0105\u017c pozostaje ma\u0142e okno, przez kt\u00f3re do develop mog\u0105 trafi\u0107 konfliktuj\u0105ce zmiany. Ale prawdopodobie\u0144stwo tego jest niskie, i zdecydowali\u015bmy si\u0119 na ten kompromis mi\u0119dzy liczb\u0105 uruchomie\u0144 a prawdopodobie\u0144stwem awarii. Przez dwa lata zdarzy\u0142o si\u0119 to tylko raz, wi\u0119c pewnie nie bez powodu.<\/p>\n<p><strong><em>Na napisanie pierwszej wersji wtyczki do Bitbucket zaj\u0119\u0142o nam dwa tygodnie. <\/em><\/strong><\/p>\n<h3>Nowe testy<\/h3>\n<p>\nW mi\u0119dzyczasie nasz zesp\u00f3\u0142 nadal rasta\u0142. Pojawia\u0142y si\u0119 nowe testy.<\/p>\n<p>Pomy\u015bleli\u015bmy: po co naprawia\u0107 b\u0142\u0119dy, skoro mo\u017cna je zapobiega\u0107? Dlatego wdro\u017cyli\u015bmy <strong>statyczn\u0105 analiz\u0119 kodu<\/strong>. Zacz\u0119li\u015bmy od linta, kt\u00f3ry wchodzi w sk\u0142ad Android SDK. Ale w tym czasie w og\u00f3le nie potrafi\u0142 pracowa\u0107 z kodem Kotlin, a ju\u017c 75% aplikacji by\u0142o napisane w Kotlin. Dlatego do linta dodano wbudowane <strong>kontrole Android Studio.<\/strong><\/p>\n<p>Do tego musieli\u015bmy si\u0119 nieco wysili\u0107: wzi\u0105\u0107 Android Studio, zapakowa\u0107 j\u0105 w Dockerze i uruchomi\u0107 w CI z wirtualnym monitorem, aby my\u015bla\u0142a, \u017ce jest uruchomiona na prawdziwym laptopie. Ale to dzia\u0142a\u0142o.<\/p>\n<p>R\u00f3wnie\u017c w tym czasie zacz\u0119li\u015bmy pisa\u0107 wiele <strong>test\u00f3w instrumentalnych<\/strong> i wdro\u017cyli\u015bmy <strong>testowanie zrzut\u00f3w ekranu<\/strong>To jest sytuacja, w kt\u00f3rej generowany jest wzorcowy zrzut ekranu dla okre\u015blonego ma\u0142ego widoku, a test polega na robieniu zrzutu ekranu z tego widoku i bezpo\u015brednim por\u00f3wnywaniu go z wzorcem pixel po pixelu. Je\u015bli wyst\u0119puje r\u00f3\u017cnica, oznacza to, \u017ce gdzie\u015b nast\u0105pi\u0142 b\u0142\u0105d w uk\u0142adzie lub co\u015b jest nie tak w stylach.<\/p>\n<p>Jednak testy instrumentacyjne i testy zrzut\u00f3w ekranu musz\u0105 by\u0107 uruchamiane na urz\u0105dzeniach: na emulatorach lub na rzeczywistych urz\u0105dzeniach. Bior\u0105c pod uwag\u0119, \u017ce test\u00f3w jest du\u017co i s\u0105 cz\u0119sto wykonywane, potrzebna jest ca\u0142a farm\u0119. Ustawianie w\u0142asnej farmy jest zbyt pracoch\u0142onne, dlatego znale\u017ali\u015bmy gotowe rozwi\u0105zanie \u2014 Firebase Test Lab.<\/p>\n<h3>Firebase Test Lab<\/h3>\n<p>\nZosta\u0142 wybrany, poniewa\u017c Firebase to produkt Google, co znaczy, \u017ce powinien by\u0107 niezawodny i ma\u0142o prawdopodobne, \u017ce kiedykolwiek zniknie. Ceny s\u0105 przyst\u0119pne: 5 USD za godzin\u0119 pracy rzeczywistego urz\u0105dzenia, 1 USD za godzin\u0119 pracy emulatora.<\/p>\n<p><strong><em>Wdro\u017cenie Firebase Test Lab w naszym CI zaj\u0119\u0142o nam oko\u0142o trzech tygodni.<\/em><\/strong><\/p>\n<p>Jednak zesp\u00f3\u0142 nadal r\u00f3s\u0142, a Firebase, niestety, zaczyna\u0142 nas zawodzi\u0107. W tym czasie nie mia\u0142 \u017cadnego SLA. Czasami Firebase zmusza\u0142 do czekania, a\u017c uwolni si\u0119 potrzebna ilo\u015b\u0107 urz\u0105dze\u0144 do test\u00f3w, a nie zaczyna\u0142 ich wykonywa\u0107 od razu, jak tego chcieli\u015bmy. Czekanie w kolejce zajmowa\u0142o do p\u00f3\u0142 godziny, co jest bardzo d\u0142ugo. Testy instrumentacyjne by\u0142y uruchamiane przy ka\u017cdym PR, a op\u00f3\u017anienia znacznie spowalnia\u0142y rozw\u00f3j, a potem przychodzi\u0142 jeszcze rachunek za miesi\u0105c z okr\u0105g\u0142\u0105 sum\u0105. Ostatecznie postanowili\u015bmy zrezygnowa\u0107 z Firebase i stworzy\u0107 w\u0142asne rozwi\u0105zanie, skoro zesp\u00f3\u0142 wystarczaj\u0105co ur\u00f3s\u0142.<\/p>\n<h3>Docker + Python + bash<\/h3>\n<p>\nWzi\u0119li\u015bmy dockera, umie\u015bcili\u015bmy w nim emulatory, napisali\u015bmy prosty program w Pythonie, kt\u00f3ry w odpowiednim momencie uruchamia potrzebn\u0105 ilo\u015b\u0107 emulator\u00f3w w odpowiedniej wersji, a gdy trzeba, je zatrzymuje. I oczywi\u015bcie kilka skrypt\u00f3w bash \u2014 gdzie\u017cby bez nich?<\/p>\n<p><strong><em>Na stworzenie w\u0142asnego \u015brodowiska testowego potrzebowali\u015bmy pi\u0119ciu tygodni.<\/em><\/strong><\/p>\n<p>W rezultacie na ka\u017cdy pull request przypada\u0142 obszerny, blokuj\u0105cy po\u0142\u0105czenie, lista kontroli:<\/p>\n<ul>\n<li>Budowanie APK;<\/li>\n<li>Testy Junit;<\/li>\n<li>Lint;<\/li>\n<li>Kontrole Android Studio;<\/li>\n<li>Testy instrumentacyjne;<\/li>\n<li>Testy zrzut\u00f3w ekranu.<\/li>\n<\/ul>\n<p>\nTo zapobiega\u0142o wielu mo\u017cliwym awariom. Technicznie wszystko dzia\u0142a\u0142o, ale programi\u015bci narzekali, \u017ce czekanie na wyniki trwa zbyt d\u0142ugo.<\/p>\n<p>Zbyt d\u0142ugo \u2014 to ile? Wyeksportowali\u015bmy dane z Bitbucket i TeamCity do systemu analizy i odkryli\u015bmy, \u017ce <strong>\u015bredni czas oczekiwania wynosi 45 minut<\/strong>. To znaczy, \u017ce programista, otwieraj\u0105c pull request, \u015brednio czeka na wyniki budowy 45 minut. Moim zdaniem to bardzo du\u017co i tak pracowa\u0107 nie mo\u017cna.<\/p>\n<p>Oczywi\u015bcie, postanowili\u015bmy przyspieszy\u0107 wszystkie nasze budowy.<\/p>\n<h2>Przyspieszamy<\/h2>\n<p>\nZauwa\u017caj\u0105c, \u017ce cz\u0119sto budowy czekaj\u0105 w kolejce, najpierw <strong>dokupili\u015bmy sprz\u0119tu<\/strong> \u2014 intensywne rozwijanie to najprostsza opcja. Budowy przesta\u0142y czeka\u0107 w kolejce, ale czas oczekiwania zmniejszy\u0142 si\u0119 tylko nieznacznie, poniewa\u017c niekt\u00f3re kontrole same w sobie trwaj\u0105 bardzo d\u0142ugo.<\/p>\n<h3>Usuwamy zbyt d\u0142ugie sprawdzenia<\/h3>\n<p>\nNasz Continuous Integration m\u00f3g\u0142 wychwyci\u0107 takie typy b\u0142\u0119d\u00f3w i problem\u00f3w.<\/p>\n<ul>\n<li><strong>Nie kompiluje si\u0119<\/strong>. CI mo\u017ce wychwyci\u0107 b\u0142\u0105d kompilacji, gdy z powodu konfliktuj\u0105cych zmian co\u015b si\u0119 nie kompiluje. Jak ju\u017c wspomnia\u0142em, wtedy nikt nie mo\u017ce nic skompilowa\u0107, rozw\u00f3j staje w miejscu, a wszyscy si\u0119 denerwuj\u0105.<\/li>\n<li><strong>B\u0142\u0105d w zachowaniu<\/strong>. Na przyk\u0142ad, gdy aplikacja si\u0119 kompiluje, ale podczas klikni\u0119cia przycisku si\u0119 zawiesza, lub przycisk w og\u00f3le nie reaguje. To \u017ale, poniewa\u017c taki b\u0142\u0105d mo\u017ce dotrze\u0107 do u\u017cytkownika.<\/li>\n<li><strong>B\u0142\u0105d w uk\u0142adzie<\/strong>. Na przyk\u0142ad, przycisk dzia\u0142a, ale przesun\u0105\u0142 si\u0119 o 10 pikseli w lewo.<\/li>\n<li><strong>Wzrost d\u0142ugu technicznego<\/strong>.<\/li>\n<\/ul>\n<p>\nZerkaj\u0105c na t\u0119 list\u0119, zrozumieli\u015bmy, \u017ce krytyczne s\u0105 tylko dwa pierwsze punkty. Takie problemy chcemy wychwytywa\u0107 w pierwszej kolejno\u015bci. B\u0142\u0119dy w uk\u0142adzie s\u0105 wykrywane na etapie przegl\u0105du projekt\u00f3w i w\u00f3wczas \u0142atwo je naprawi\u0107. Praca z d\u0142ugiem technicznym wymaga osobnego procesu i planowania, dlatego postanowili\u015bmy nie sprawdza\u0107 go w pull requestach.<\/p>\n<p>Na podstawie tej klasyfikacji przetrz\u0105sn\u0119li\u015bmy ca\u0142\u0105 list\u0119 sprawdze\u0144. <strong>Skre\u015blili\u015bmy Lint<\/strong> i przenie\u015bli\u015bmy jego uruchamianie na noc: po prostu, aby dostarcza\u0142 raport o liczbie problem\u00f3w w projekcie. Z d\u0142ugiem technicznym postanowili\u015bmy pracowa\u0107 osobno, a <strong>zrezygnowali\u015bmy ca\u0142kowicie z kontroli Android Studio<\/strong>. Android Studio w Dockerze do uruchamiania inspekcji brzmi interesuj\u0105co, ale sprawia wiele problem\u00f3w w utrzymaniu. Ka\u017cda aktualizacja wersji Android Studio to walka z niejasnymi b\u0142\u0119dami. R\u00f3wnie\u017c trudno by\u0142o utrzyma\u0107 testy zrzut\u00f3w ekranu, poniewa\u017c biblioteka nie dzia\u0142a\u0142a zbyt stabilnie i zdarza\u0142y si\u0119 fa\u0142szywe alarmy. <strong>Testy zrzut\u00f3w ekranu usuni\u0119to z listy sprawdze\u0144<\/strong>.<\/p>\n<p>W rezultacie zosta\u0142y nam:<\/p>\n<ul>\n<li>Budowanie APK;<\/li>\n<li>Testy Junit;<\/li>\n<li>Testy Instrumentacyjne.<\/li>\n<\/ul>\n<h3>Zdalna pami\u0119\u0107 podr\u0119czna Gradle<\/h3>\n<p>\nBez ci\u0119\u017ckich kontroli wszystko sta\u0142o si\u0119 lepsze. Ale nie ma granic doskona\u0142o\u015bci!<\/p>\n<p>Nasza aplikacja by\u0142a ju\u017c podzielona na oko\u0142o 150 modu\u0142\u00f3w gradle. Zwykle w takim przypadku dobrze dzia\u0142a zdalna pami\u0119\u0107 podr\u0119czna Gradle i postanowili\u015bmy spr\u00f3bowa\u0107.<\/p>\n<p>Gradle remote cache to us\u0142uga, kt\u00f3ra mo\u017ce przechowywa\u0107 artefakty budowy dla poszczeg\u00f3lnych zada\u0144 w indywidualnych modu\u0142ach. Zamiast rzeczywi\u015bcie kompilowa\u0107 kod, Gradle \u0142\u0105czy si\u0119 z remote cache przez HTTP i pyta, czy kto\u015b ju\u017c wykona\u0142 to zadanie. Je\u015bli tak, po prostu pobiera wynik.<\/p>\n<p><strong><em>Uruchomienie Gradle remote cache jest \u0142atwe, poniewa\u017c Gradle dostarcza obraz Docker. Uda\u0142o nam si\u0119 to zrobi\u0107 w trzy godziny.<\/em><\/strong><\/p>\n<p>Wystarczy\u0142o uruchomi\u0107 Docker i doda\u0107 jedn\u0105 lini\u0119 w projekcie. Cho\u0107 mo\u017cna go szybko uruchomi\u0107, aby wszystko dzia\u0142a\u0142o dobrze, potrzeba na to znacznie wi\u0119cej czasu.<\/p>\n<p>Poni\u017cej przedstawiony wykres cache misses.<\/p>\n<p><img decoding=\"async\" alt=\"Ewolucja CI w zespole rozwoju mobilnego\" src=\"\/wp-content\/uploads\/2019\/04\/458ef23b5506b04f3bd385be0c18607a.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nNa pocz\u0105tku procent nietrafie\u0144 w cache wynosi\u0142 oko\u0142o 65. Po trzech tygodniach uda\u0142o si\u0119 obni\u017cy\u0107 t\u0119 warto\u015b\u0107 do 20%. Okaza\u0142o si\u0119, \u017ce zadania, kt\u00f3re zbiera aplikacja na Androida, maj\u0105 dziwne zale\u017cno\u015bci przejrzyste, przez co Gradle nie trafi\u0142 do cache.<\/p>\n<p>W\u0142\u0105czaj\u0105c cache, znacznie przyspieszyli\u015bmy budow\u0119. Ale opr\u00f3cz budowy, przeprowadzane s\u0105 r\u00f3wnie\u017c testy instrumentalne, kt\u00f3re trwaj\u0105 d\u0142ugo. Mo\u017cliwe, \u017ce nie wszystkie testy trzeba uruchamia\u0107 dla ka\u017cdego pull requesta. Aby to ustali\u0107, stosujemy analiz\u0119 wp\u0142ywu.<\/p>\n<h3>Analiza wp\u0142ywu<\/h3>\n<p>\nNa pull request gromadzimy git diff i znajdujemy zmienione modu\u0142y Gradle.<\/p>\n<p><img decoding=\"async\" alt=\"Ewolucja CI w zespole rozwoju mobilnego\" src=\"\/wp-content\/uploads\/2019\/04\/471ca37206a0da4d5741c8ee1dbb7f03.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nMa sens uruchamianie tylko tych test\u00f3w instrumentalnych, kt\u00f3re sprawdzaj\u0105 zmienione modu\u0142y oraz wszystkie modu\u0142y, kt\u00f3re od nich zale\u017c\u0105. Nie ma sensu uruchamia\u0107 test\u00f3w dla s\u0105siednich modu\u0142\u00f3w: tam kod si\u0119 nie zmieni\u0142 i nic nie mo\u017ce si\u0119 zepsu\u0107.<\/p>\n<p>Z testami instrumentalnymi nie jest tak prosto, poniewa\u017c musz\u0105 znajdowa\u0107 si\u0119 w najwy\u017cszym module Application. Zastosowali\u015bmy heurystyk\u0119 z analiz\u0105 kodu bajtowego, aby zrozumie\u0107, do kt\u00f3rego modu\u0142u odnosi si\u0119 ka\u017cdy test.<\/p>\n<p><strong><em>Modernizacja pracy test\u00f3w instrumentalnych, aby sprawdza\u0142y tylko zaanga\u017cowane modu\u0142y, zaj\u0119\u0142a oko\u0142o o\u015bmiu tygodni.<\/em><\/strong><\/p>\n<p>Dzia\u0142ania maj\u0105ce na celu przyspieszenie weryfikacji zako\u0144czy\u0142y si\u0119 sukcesem. Z 45 minut zeszli\u015bmy do oko\u0142o 15. Kwadrans czekania na build ju\u017c jest akceptowalny.<\/p>\n<p>Ale teraz deweloperzy zacz\u0119li narzeka\u0107, \u017ce nie wiedz\u0105, jakie buildy s\u0105 uruchamiane, gdzie mog\u0105 sprawdzi\u0107 logi, dlaczego build jest czerwony, kt\u00f3ry test nie przeszed\u0142 itd.<\/p>\n<p><img decoding=\"async\" alt=\"Ewolucja CI w zespole rozwoju mobilnego\" src=\"\/wp-content\/uploads\/2019\/04\/aa76151174cd4e5a45ff281818e312f5.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nProblemy z informacj\u0105 zwrotn\u0105 spowalniaj\u0105 rozw\u00f3j, dlatego starali\u015bmy si\u0119 zapewni\u0107 jak najbardziej zrozumia\u0142e i szczeg\u00f3\u0142owe informacje o ka\u017cdym PR i buildzie. Zacz\u0119li\u015bmy od komentarzy w Bitbucket do PR z informacj\u0105, kt\u00f3ry build si\u0119 nie powi\u00f3d\u0142 i dlaczego, pisali\u015bmy adresowe wiadomo\u015bci w Slack. Ostatecznie zrobili\u015bmy stron\u0119 dashboard PR z list\u0105 wszystkich build\u00f3w, kt\u00f3re s\u0105 teraz uruchamiane i ich stanem: w kolejce, uruchamiany, nie powi\u00f3d\u0142 si\u0119 lub zako\u0144czony. Mo\u017cna klikn\u0105\u0107 na build i przej\u015b\u0107 do jego logu.<\/p>\n<p><img decoding=\"async\" alt=\"Ewolucja CI w zespole rozwoju mobilnego\" src=\"\/wp-content\/uploads\/2019\/04\/2d16b7a6b6d23e23903d291ef1526999.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\n<strong><em>Na szczeg\u00f3\u0142ow\u0105 informacj\u0119 zwrotn\u0105 po\u015bwi\u0119cili\u015bmy sze\u015b\u0107 tygodni.<\/em><\/strong><\/p>\n<h2>Plany<\/h2>\n<p>\nPrzechodzimy do najnowszej historii. Rozwi\u0105zuj\u0105c problem informacyjny, weszli\u015bmy na nowy poziom \u2014 postanowili\u015bmy zbudowa\u0107 w\u0142asn\u0105 farm\u0119 emulator\u00f3w. Kiedy jest wiele test\u00f3w i emulator\u00f3w, trudno nimi zarz\u0105dza\u0107. W rezultacie wszystkie nasze emulatory przenios\u0142y si\u0119 do klastra k8s z elastycznym zarz\u0105dzaniem zasobami.<\/p>\n<p>Ponadto istniej\u0105 tak\u017ce inne plany.<\/p>\n<ul>\n<li><strong>Przywr\u00f3ci\u0107 Lint<\/strong> (i inny statyczny analizator). Ju\u017c pracujemy w tym kierunku.<\/li>\n<li>Uruchomi\u0107 na PR blokuj\u0105cego wszystkie <strong>testy end-to-end<\/strong> na wszystkich wersjach SDK.<\/li>\n<\/ul>\n<p>\nA wi\u0119c, prze\u015bledzili\u015bmy histori\u0119 rozwoju Continuous Integration w Avito. Teraz chcia\u0142bym da\u0107 kilka rad z perspektywy do\u015bwiadczonego.<\/p>\n<h1>Porady<\/h1>\n<p>\nGdybym m\u00f3g\u0142 da\u0107 tylko jedn\u0105 rad\u0119, by\u0142aby to ta:<\/p>\n<blockquote><p>Prosz\u0119, b\u0105d\u017acie ostro\u017cni z skryptami shellowymi!<\/p><\/blockquote>\n<p>\nBash to bardzo elastyczne i pot\u0119\u017cne narz\u0119dzie, na kt\u00f3rym bardzo \u0142atwo i szybko pisze si\u0119 skrypty. Ale mo\u017cna w nim wpa\u015b\u0107 w pu\u0142apk\u0119, i niestety my w ni\u0105 wpadli\u015bmy.<\/p>\n<p>Zacz\u0119\u0142o si\u0119 od prostych skrypt\u00f3w, kt\u00f3re by\u0142y uruchamiane na naszych maszynach budowlanych:<\/p>\n<pre><code class=\"bash\">#!\/usr\/bin\/env bash\n.\/gradlew assembleDebug<\/code><\/pre>\n<p>\nAle jak wiadomo, wszystko z biegiem czasu si\u0119 rozwija i komplikuje \u2014 uruchommy jeden skrypt z drugiego, przekazujmy mu jakie\u015b parametry \u2014 w rezultacie trzeba by\u0142o napisa\u0107 funkcj\u0119, kt\u00f3ra okre\u015bla, na jakim poziomie zagnie\u017cd\u017cenia bash obecnie si\u0119 znajdujemy, aby wstawi\u0107 odpowiednie cudzys\u0142owy, aby to wszystko si\u0119 uruchomi\u0142o.<\/p>\n<p><img decoding=\"async\" alt=\"Ewolucja CI w zespole rozwoju mobilnego\" src=\"\/wp-content\/uploads\/2019\/04\/244a574f7b5d3b4fc77cfc4ccda2db69.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nMo\u017cecie sobie wyobrazi\u0107 koszty pracy zwi\u0105zane z rozwojem takich skrypt\u00f3w. Radz\u0119 nie wpada\u0107 w t\u0119 pu\u0142apk\u0119.<\/p>\n<p>Czym mo\u017cna to zast\u0105pi\u0107?<\/p>\n<ul>\n<li>Ka\u017cdym j\u0119zykiem skryptowym. Pisanie w <strong>Pythonie lub Kotlin Script<\/strong> jest wygodniejsze, poniewa\u017c to programowanie, a nie skrypty.<\/li>\n<li>Lub opisz ca\u0142\u0105 logik\u0119 build\u00f3w w postaci <strong>Custom gradle tasks<\/strong> dla twojego projektu.<\/li>\n<\/ul>\n<p>\nZdecydowali\u015bmy si\u0119 na drug\u0105 opcj\u0119 i teraz systematycznie usuwamy wszystkie skrypty bash i piszemy wiele niestandardowych zada\u0144 gradle.<\/p>\n<p><strong>Rada nr 2: przechowuj infrastruktur\u0119 w kodzie.<\/strong><\/p>\n<p>To wygodne, gdy konfiguracja Continuous Integration nie jest przechowywana w interfejsie UI Jenkins czy TeamCity itp., a w formie plik\u00f3w tekstowych bezpo\u015brednio w repozytorium projektu. To zapewnia wersjonowalno\u015b\u0107. Nie b\u0119dzie trudno cofn\u0105\u0107 si\u0119 lub zbudowa\u0107 kod na innej ga\u0142\u0119zi.<\/p>\n<p>Skrypty mo\u017cna przechowywa\u0107 w projekcie. A co z otoczeniem?<\/p>\n<p><strong>Wskaz\u00f3wka nr 3: pomoc w otoczeniu mo\u017ce zaoferowa\u0107 Docker.<\/strong><\/p>\n<p>Z pewno\u015bci\u0105 pomo\u017ce on programistom Android, niestety na razie nie dla iOS.<\/p>\n<p>To przyk\u0142ad prostego pliku docker, kt\u00f3ry zawiera jdk i 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# Pobierz 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# Zainstaluj Android Build Tool i biblioteki\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>\nNapisa\u0142em ten plik docker (powiem w tajemnicy, \u017ce mo\u017cna go nie pisa\u0107, a po prostu pobra\u0107 gotowy z GitHub) i po zbudowaniu obrazu otrzymujesz maszyn\u0119 wirtualn\u0105, na kt\u00f3rej mo\u017cesz tworzy\u0107 aplikacj\u0119 i uruchamia\u0107 testy Junit.<\/p>\n<p>Dwa g\u0142\u00f3wne argumenty, dlaczego to ma sens: skalowalno\u015b\u0107 i powtarzalno\u015b\u0107. U\u017cywaj\u0105c Dockera, mo\u017cna szybko uruchomi\u0107 dziesi\u0105tki agent\u00f3w budowlanych, kt\u00f3re b\u0119d\u0105 mia\u0142y dok\u0142adnie takie samo otoczenie jak poprzednie. To bardzo u\u0142atwia \u017cycie in\u017cynierom CI. Wpakowanie android-sdk do dockera jest ca\u0142kiem proste, z emulatorami jest troch\u0119 trudniej: trzeba si\u0119 troch\u0119 postara\u0107 (no, albo zn\u00f3w pobra\u0107 gotowe z GitHub).<\/p>\n<p><strong>Wskaz\u00f3wka nr 4: pami\u0119taj, \u017ce kontrole s\u0105 przeprowadzane nie dla samych kontroli, ale dla ludzi.<\/strong><\/p>\n<p>Dla programist\u00f3w bardzo wa\u017cna jest szybka i przede wszystkim zrozumia\u0142a informacja zwrotna: co si\u0119 zepsu\u0142o, jaki test nie przeszed\u0142, gdzie mo\u017cna zobaczy\u0107 logi budowy.<\/p>\n<p><strong>Wskaz\u00f3wka nr 5: b\u0105d\u017a pragmatyczny w rozwoju Continuous Integration.<\/strong><\/p>\n<p>Dok\u0142adnie rozumiej, jakie typy b\u0142\u0119d\u00f3w chcesz zapobiega\u0107, ile jeste\u015b got\u00f3w po\u015bwi\u0119ci\u0107 zasob\u00f3w, czasu, czasu maszynowego. Zbyt d\u0142ugie kontrole mo\u017cna na przyk\u0142ad przenie\u015b\u0107 na noc. A z tych, kt\u00f3re wy\u0142apuj\u0105 niezbyt wa\u017cne b\u0142\u0119dy, mo\u017cna ca\u0142kowicie zrezygnowa\u0107.<\/p>\n<p><strong>Wskaz\u00f3wka nr 6: korzystaj z gotowych narz\u0119dzi.<\/strong><\/p>\n<p>Obecnie istnieje wiele firm, kt\u00f3re oferuj\u0105 chmurowe CI.<\/p>\n<p><img decoding=\"async\" alt=\"Ewolucja CI w zespole rozwoju mobilnego\" src=\"\/wp-content\/uploads\/2019\/04\/e57aabd49ec01d14e83b64331fd84ec5.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nDla ma\u0142ych zespo\u0142\u00f3w to dobre rozwi\u0105zanie. Nie musisz niczego utrzymywa\u0107, wystarczy, \u017ce zap\u0142acisz troch\u0119 pieni\u0119dzy, zbierzesz swoj\u0105 aplikacj\u0119 i nawet uruchomisz testy instrumentalne.<\/p>\n<p><strong>Rada nr 7: w du\u017cym zespole bardziej op\u0142acalne s\u0105 rozwi\u0105zania in-house.<\/strong><\/p>\n<p>Ale pr\u0119dzej czy p\u00f3\u017aniej, wraz ze wzrostem zespo\u0142u, rozwi\u0105zania in-house stan\u0105 si\u0119 bardziej op\u0142acalne. Jest jeden aspekt tych rozwi\u0105za\u0144. W ekonomii obowi\u0105zuje zasada malej\u0105cych przychod\u00f3w: w ka\u017cdym projekcie ka\u017cde kolejne usprawnienie pojawia si\u0119 coraz trudniej, wymaga coraz wi\u0119kszych inwestycji.<\/p>\n<p>Ekonomia opisuje ca\u0142e nasze \u017cycie, w tym Continuous Integration. Stworzy\u0142em wykres nak\u0142ad\u00f3w pracy na ka\u017cdym etapie rozwoju naszego Continuous Integration.<\/p>\n<p><img decoding=\"async\" alt=\"Ewolucja CI w zespole rozwoju mobilnego\" src=\"\/wp-content\/uploads\/2019\/04\/97bf8bff64f3ac9ae587fae195251da2.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nWida\u0107, \u017ce ka\u017cde usprawnienie staje si\u0119 coraz trudniejsze i trudniejsze. Patrz\u0105c na ten wykres, mo\u017cna zrozumie\u0107, \u017ce rozwijanie Continuous Integration powinno by\u0107 zgodne z rozwojem wielko\u015bci zespo\u0142u. Dla zespo\u0142u licz\u0105cego dwa osoby wydawanie 50 dni na rozw\u00f3j wewn\u0119trznej farmy emulator\u00f3w to niezbyt dobry pomys\u0142. Ale z kolei, dla du\u017cego zespo\u0142u w og\u00f3le nie zajmowanie si\u0119 Continuous Integration to te\u017c z\u0142a idea, poniewa\u017c na problemy z integracj\u0105, napraw\u0119 komunikacji itp. b\u0119dzie trzeba po\u015bwi\u0119ci\u0107 jeszcze wi\u0119cej czasu.<\/p>\n<p>Zacz\u0119li\u015bmy od tego, \u017ce automatyzacja jest potrzebna, poniewa\u017c ludzie s\u0105 drodzy, pope\u0142niaj\u0105 b\u0142\u0119dy i s\u0105 leniwi. Jednak automatyzuj\u0105 te\u017c ludzie. Dlatego te same problemy odnosz\u0105 si\u0119 r\u00f3wnie\u017c do automatyzacji.<\/p>\n<ul>\n<li>Automatyzacja jest kosztowna. Pami\u0119taj o wykresie nak\u0142ad\u00f3w pracy.<\/li>\n<li>Podczas automatyzacji ludzie pope\u0142niaj\u0105 b\u0142\u0119dy.<\/li>\n<li>Czasami trudno jest si\u0119 zmobilizowa\u0107 do automatyzacji, poniewa\u017c wszystko dzia\u0142a jak nale\u017cy. Po co jeszcze co\u015b usprawnia\u0107, po co ca\u0142y ten Continuous Integration?<\/li>\n<\/ul>\n<p>\nAle mam statystyki: w 20% kompilacji wykrywane s\u0105 b\u0142\u0119dy. I to nie dlatego, \u017ce nasi programi\u015bci \u017ale pisz\u0105 kod. To dlatego, \u017ce programi\u015bci s\u0105 pewni, \u017ce je\u015bli pope\u0142ni\u0105 jakikolwiek b\u0142\u0105d, nie trafi on do develop, bo wykryj\u0105 go automatyczne kontrole. W zwi\u0105zku z tym programi\u015bci mog\u0105 po\u015bwi\u0119ca\u0107 wi\u0119cej czasu na pisanie kodu i ciekawe rzeczy, a nie na lokalne uruchamianie i sprawdzanie.<\/p>\n<p><strong>Zajmijcie si\u0119 Continuous Integration. Ale z umiarem.<\/strong><\/p>\n<blockquote><p>Przy okazji, Niko\u0142aj Nesterow nie tylko sam robi \u015bwietne prezentacje, ale tak\u017ce jest cz\u0142onkiem komitetu programowego <noindex><a rel=\"nofollow\" href=\"https:\/\/appsconf.ru\/moscow\/2019\">AppsConf<\/a><\/noindex> i pomaga innym przygotowywa\u0107 dla was merytoryczne wyst\u0105pienia. Pe\u0142ni\u0119 i u\u017cyteczno\u015b\u0107 programu nadchodz\u0105cej konferencji mo\u017cna oceni\u0107 po tematach w <noindex><a rel=\"nofollow\" href=\"https:\/\/appsconf.ru\/moscow\/2019\/schedule\">harmonogramie<\/a><\/noindex>. A po szczeg\u00f3\u0142y przyjd\u017acie 22-23 kwietnia do Infoprzestrzeni.<\/p><\/blockquote>\n<p>\u0179r\u00f3d\u0142o: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/oleg-bunin\/blog\/447608\/\">habr.com<\/a><\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u0421\u0435\u0433\u043e\u0434\u043d\u044f \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u043e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u044b\u0445 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u043e\u0432 \u0440\u0430\u0437\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u0430\u0445. \u0423\u0441\u043b\u043e\u0432\u0438\u044f \u0443\u0441\u043f\u0435\u0445\u0430 \u043a\u043e\u043c\u0430\u043d\u0434\u043d\u043e\u0439 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0432 \u0432\u0438\u0434\u0435 \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0441\u0445\u0435\u043c\u044b. \u041d\u0430\u043f\u0438\u0441\u0430\u0432 \u043a\u043e\u0434, \u0432\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0443\u0431\u0435\u0434\u0438\u0442\u044c\u0441\u044f, \u0447\u0442\u043e \u043e\u043d: \u0420\u0430\u0431\u043e\u0442\u0430\u0435\u0442. \u041d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043b\u043e\u043c\u0430\u0435\u0442, \u0432 \u0442\u043e\u043c \u0447\u0438\u0441\u043b\u0435 \u043a\u043e\u0434, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0430\u043f\u0438\u0441\u0430\u043b\u0438 \u0432\u0430\u0448\u0438 \u043a\u043e\u043b\u043b\u0435\u0433\u0438. \u0415\u0441\u043b\u0438 \u043e\u0431\u0430 \u0443\u0441\u043b\u043e\u0432\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u044e\u0442\u0441\u044f, \u0442\u043e \u0432\u044b \u043d\u0430 \u043f\u0443\u0442\u0438 \u043a \u0443\u0441\u043f\u0435\u0445\u0443. \u0427\u0442\u043e\u0431\u044b \u043b\u0435\u0433\u043a\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u044d\u0442\u0438 \u0443\u0441\u043b\u043e\u0432\u0438\u044f \u0438 \u043d\u0435 \u0441\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0442\u044c \u0441 [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":23270,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-31304","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.1.1 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u0421\u0435\u0433\u043e\u0434\u043d\u044f \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u043e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u044b\u0445 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u043e\u0432 \u0440\u0430\u0437\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u0432 \u043a\u043e\u043c\u0430\u043d\u0434\u0430\u0445.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/evolyutsiya-ci-v-komande-mobilnoj-razrabotki\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.1.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"pl_PL\" \/>\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\/pl\/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\udd47Ewolucja CI w zespole rozwoju mobilnego | ProHoster","description":"Obecnie wi\u0119kszo\u015b\u0107 produkt\u00f3w programowych jest rozwijana w zespo\u0142ach.","canonical_url":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/evolyutsiya-ci-v-komande-mobilnoj-razrabotki","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"pl_PL","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\/pl\/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\/pl\/wp-json\/wp\/v2\/posts\/31304","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/comments?post=31304"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts\/31304\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/media\/23270"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/media?parent=31304"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/categories?post=31304"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/tags?post=31304"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}