Samouczek testowania Androida: Testowanie jednostkowe jak prawdziwy zielony droid
Opublikowany: 2022-03-11Jako doświadczeni programiści aplikacji, w miarę dojrzewania aplikacji, które tworzymy, czujemy, że nadszedł czas, aby rozpocząć testowanie. Reguły biznesowe często oznaczają, że system musi zapewniać stabilność w różnych wydaniach. Idealnie chcemy również zautomatyzować proces budowania i automatycznie opublikować aplikację. W tym celu potrzebujemy narzędzi testowych Adnroid, aby zagwarantować, że kompilacja działa zgodnie z oczekiwaniami.
Testy mogą zapewnić dodatkowy poziom pewności co do rzeczy, które tworzymy. Zbudowanie idealnego, wolnego od błędów produktu jest trudne (jeśli nie niemożliwe). Dlatego naszym celem będzie zwiększenie naszych szans na odniesienie sukcesu na rynku poprzez stworzenie zestawu testowego, który szybko wykryje nowo wprowadzone błędy w naszej aplikacji.
Jeśli chodzi o Androida i ogólnie różne platformy mobilne, testowanie aplikacji może być wyzwaniem. Wdrażanie testów jednostkowych i przestrzeganie zasad programowania sterowanego testami lub podobnych może często wydawać się co najmniej nieintuicyjne. Niemniej jednak testowanie jest ważne i nie powinno być brane za pewnik ani ignorowane. David, Kent i Martin omówili korzyści i pułapki testowania w serii rozmów między sobą w artykule zatytułowanym „Czy TDD jest martwe?”. Możesz tam również znaleźć rzeczywiste rozmowy wideo i uzyskać więcej informacji, czy testowanie pasuje do Twojego procesu rozwoju i w jakim stopniu możesz je włączyć, zaczynając od teraz.
W tym samouczku dotyczącym testowania Androida przeprowadzę Cię przez testy jednostkowe i akceptacyjne, testy regresji na Androidzie. Skoncentrujemy się na abstrakcji jednostki testów na Androida, a następnie na przykładach testów akceptacyjnych, z naciskiem na jak najszybszy i najprostszy proces, aby skrócić cykle opinii programistów-QA.
Czy powinienem to przeczytać?
W tym samouczku omówimy różne możliwości testowania aplikacji na Androida. Deweloperzy lub kierownicy projektów, którzy chcą lepiej zrozumieć obecne możliwości testowania platformy Android, mogą zdecydować, korzystając z tego samouczka, jeśli chcą zastosować którekolwiek z podejść wymienionych w tym artykule. Nie jest to jednak srebrna kula, ponieważ dyskusja związana z takim tematem z natury różni się w zależności od produktu wraz z terminami, jakością kodu bazowego kodu, poziomem sprzężenia systemu, preferencjami programistów w zakresie projektowania architektury, przewidywaną żywotnością funkcji do test itp.
Myślenie w jednostkach: testowanie Androida
W idealnym przypadku chcemy niezależnie przetestować jedną logiczną jednostkę/komponent architektury. W ten sposób możemy zagwarantować, że nasz komponent działa poprawnie dla zestawu danych wejściowych, których oczekujemy. Zależności można podszywać, co umożliwi nam pisanie testów, które wykonują się szybko. Ponadto będziemy mogli symulować różne stany systemu na podstawie danych wejściowych do testu, obejmując w tym procesie egzotyczne przypadki.
Celem testów jednostkowych Androida jest wyizolowanie każdej części programu i wykazanie, że poszczególne części są poprawne. Test jednostkowy zapewnia ścisłą, pisemną umowę, którą musi spełniać fragment kodu. W rezultacie daje kilka korzyści. — Wikipedia
Roboelektryczny
Robolectric to platforma do testowania jednostkowego systemu Android, która umożliwia przeprowadzanie testów w JVM na programistycznej stacji roboczej. Robolectric przepisuje klasy Android SDK podczas ich ładowania i umożliwia ich uruchamianie na zwykłej maszynie wirtualnej JVM, co skutkuje krótkimi czasami testów. Co więcej, obsługuje inflację widoków, ładowanie zasobów i więcej rzeczy, które są zaimplementowane w natywnym kodzie C na urządzeniach z Androidem, dzięki czemu potrzeba emulatorów i urządzeń fizycznych do uruchamiania automatycznych testów jest przestarzała.
Mockito
Mockito to framework, który pozwala nam na pisanie czystych testów w javie. Upraszcza proces tworzenia dubletów testowych (mocków), które służą do zastąpienia pierwotnych zależności komponentu/modułu używanego w produkcji. Odpowiedź StackOverflow omawia różnice między mockami a skrótami w dość prostych terminach, które możesz przeczytać, aby dowiedzieć się więcej.
// you can mock concrete classes, not only interfaces LinkedList mockedList = mock(LinkedList.class); // stubbing appears before the actual execution when(mockedList.get(0)).thenReturn("first"); // the following prints "first" System.out.println(mockedList.get(0)); // the following prints "null" because get(999) was not stubbed System.out.println(mockedList.get(999));
Dodatkowo za pomocą Mockito możemy zweryfikować, czy metoda została wywołana:
// mock creation List mockedList = mock(List.class); // using mock object - it does not throw any "unexpected interaction" exception mockedList.add("one"); mockedList.clear(); // selective, explicit, highly readable verification verify(mockedList).add("one"); verify(mockedList).clear();
Teraz wiemy, że możemy określić pary akcja-reakcja, które definiują, co się stanie, gdy wykonamy określoną akcję na zaklętym obiekcie/komponentze. Dlatego możemy mockować całe moduły naszej aplikacji, a na każdy przypadek testowy sprawić, by mockowany moduł reagował w inny sposób. Różne sposoby będą odzwierciedlać możliwe stany testowanego komponentu i fikcyjnej pary komponentów.
Testów jednostkowych
W tej sekcji przyjmiemy architekturę MVP (Model View Presenter). Działania i fragmenty to widoki, modele są warstwą repozytorium dla wywołań do bazy danych lub usług zdalnych, a prezenter jest „mózgiem”, który łączy to wszystko razem, wdrażając określoną logikę do kontrolowania widoków, modeli i przepływu danych przez wniosek.
Komponenty abstrahujące
Szydercze widoki i modele
W tym przykładzie testowania Androida będziemy mockować widoki, modele i komponenty repozytorium, a także przetestujemy prezentera. Jest to jeden z najmniejszych testów, skierowany do pojedynczego komponentu w architekturze. Co więcej, użyjemy stubbingu metod do utworzenia odpowiedniego, testowalnego łańcucha reakcji:
@RunWith(RobolectricTestRunner.class) @Config(manifest = "app/src/main/AndroidManifest.xml", emulateSdk = 18) public class FitnessListPresenterTest { private Calendar cal = Calendar.getInstance(); @Mock private IFitnessListModel model; @Mock private IFitnessListView view; private IFitnessListPresenter presenter; @Before public void setup() { MockitoAnnotations.initMocks(this); final FitnessEntry entryMock = mock(FitnessEntry.class); presenter = new FitnessListPresenter(view, model); /* Define the desired behaviour. Queuing the action in "doAnswer" for "when" is executed. Clear and synchronous way of setting reactions for actions (stubbing). */ doAnswer((new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { ArrayList<FitnessEntry> items = new ArrayList<>(); items.add(entryMock); ((IFitnessListPresenterCallback) presenter).onFetchAllSuccess(items); return null; } })).when(model).fetchAllItems((IFitnessListPresenterCallback) presenter); } /** Verify if model.fetchItems was called once. Verify if view.onFetchSuccess is called once with the specified list of type FitnessEntry The concrete implementation of ((IFitnessListPresenterCallback) presenter).onFetchAllSuccess(items); calls the view.onFetchSuccess(...) method. This is why we verify that view.onFetchSuccess is called once. */ @Test public void testFetchAll() { presenter.fetchAllItems(false); // verify can be called only on mock objects verify(model, times(1)).fetchAllItems((IFitnessListPresenterCallback) presenter); verify(view, times(1)).onFetchSuccess(new ArrayList<>(anyListOf(FitnessEntry.class))); } }
Mocowanie globalnej warstwy sieciowej za pomocą MockWebServer
Często wygodnie jest zakpić z globalnej warstwy sieciowej. MockWebServer pozwala nam kolejkować odpowiedzi na określone żądania, które wykonujemy w naszych testach. Daje nam to szansę na symulowanie niejasnych odpowiedzi, których oczekujemy od serwera, ale nie są one łatwe do odtworzenia. Pozwala nam to zapewnić pełne pokrycie przy pisaniu niewielkiego dodatkowego kodu.
Repozytorium kodu MockWebServer zapewnia zgrabny przykład, do którego można się odwołać, aby lepiej zrozumieć tę bibliotekę.
Niestandardowe Testy Podwójne
Możesz napisać własny model lub komponent repozytorium i wstrzyknąć go do testu, dostarczając inny moduł do grafu obiektów za pomocą Daggera (http://square.github.io/dagger/). Mamy możliwość sprawdzenia, czy stan widoku został poprawnie zaktualizowany na podstawie danych dostarczonych przez komponent modelu makiety:
/** Custom mock model class */ public class FitnessListErrorTestModel extends FitnessListModel { // ... @Override public void fetchAllItems(IFitnessListPresenterCallback callback) { callback.onError(); } @Override public void fetchItemsInRange(final IFitnessListPresenterCallback callback, DateFilter filter) { callback.onError(); } }
@RunWith(RobolectricTestRunner.class) @Config(manifest = "app/src/main/AndroidManifest.xml", emulateSdk = 18) public class FitnessListPresenterDaggerTest { private FitnessActivity activity; private FitnessListFragment fitnessListFragment; @Before public void setup() { /* setupActivity runs the Activity lifecycle methods on the specified class */ activity = Robolectric.setupActivity(FitnessActivity.class); fitnessListFragment = activity.getFitnessListFragment(); /* Create the objectGraph with the TestModule */ ObjectGraph localGraph = ObjectGraph.create(TestModule.newInstance(fitnessListFragment)); /* Injection */ localGraph.inject(fitnessListFragment); localGraph.inject(fitnessListFragment.getPresenter()); } @Test public void testInteractorError() { fitnessListFragment.getPresenter().fetchAllItems(false); /* suppose that our view shows a Toast message with the specified text below when an error is reported, so we check for it. */ assertEquals(ShadowToast.getTextOfLatestToast(), "Something went wrong!"); } @Module( injects = { FitnessListFragment.class, FitnessListPresenter.class }, overrides = true, library = true ) static class TestModule { private IFitnessListView view; private TestModule(IFitnessListView view){ this.view = view; } public static TestModule newInstance(IFitnessListView view){ return new TestModule(view); } @Provides public IFitnessListInteractor provideFitnessListInteractor(){ return new FitnessListErrorTestModel(); } @Provides public IFitnessListPresenter provideFitnessPresenter(){ return new FitnessListPresenter(view); } } }
Uruchamianie testów
Studio Android
Możesz łatwo kliknąć prawym przyciskiem myszy klasę testową, metodę lub cały pakiet testowy i uruchomić testy z okna dialogowego opcji w IDE.
Terminal
Uruchamianie testów aplikacji na Androida z poziomu terminala tworzy raporty dla testowanych klas w folderze „build” modułu docelowego. Co więcej, jeśli planujesz skonfigurować zautomatyzowany proces kompilacji, użyjesz podejścia terminalowego. Dzięki Gradle możesz uruchomić wszystkie testy związane z debugowaniem, wykonując następujące czynności:
gradle testDebug
Dostęp do zestawu źródeł „test” z wersji Android Studio
Wersja 1.1 Android Studio i wtyczka Android Gradle zapewniają obsługę testów jednostkowych Twojego kodu. Możesz dowiedzieć się więcej, czytając ich doskonałą dokumentację na ten temat. Ta funkcja jest eksperymentalna, ale jest również świetnym włączeniem, ponieważ możesz teraz łatwo przełączać się między testami jednostkowymi a zestawami źródeł testowych oprzyrządowania z IDE. Zachowuje się tak samo, jakbyś zmieniał smaki w IDE.
Łagodzenie procesu
Pisanie testów aplikacji na Androida może nie być tak zabawne, jak tworzenie oryginalnej aplikacji. Stąd kilka wskazówek, jak ułatwić proces pisania testów i uniknąć typowych problemów przy tworzeniu projektu, pomoże na dłuższą metę.
AssertJ Android
AssertJ Android, jak można się domyślić po nazwie, to zestaw funkcji pomocniczych zbudowanych z myślą o systemie Android. Jest rozszerzeniem popularnej biblioteki AssertJ. Funkcjonalność zapewniana przez AssertJ Android waha się od prostych asercji, takich jak „assertThat(view).isGone()”, do rzeczy tak złożonych, jak:

assertThat(layout).isVisible() .isVertical() .hasChildCount(4) .hasShowDividers(SHOW_DIVIDERS_MIDDLE)
Dzięki AssertJ Android i jego rozszerzalności masz gwarancję prostego, dobrego punktu wyjścia do pisania testów dla aplikacji na Androida.
Ścieżka Robolectric i Manifest
Podczas korzystania z Robolectric możesz zauważyć, że musisz określić lokalizację manifestu, a wersja SDK jest ustawiona na 18. Możesz to zrobić, dołączając adnotację „Config”.
@Config(manifest = "app/src/main/AndroidManifest.xml", emulateSdk = 18)
Uruchamianie testów, które wymagają Robolectric z terminala, może wprowadzić nowe wyzwania. Na przykład możesz zobaczyć wyjątki, takie jak „Nie ustawiono motywu”. Jeśli testy działają poprawnie z IDE, ale nie z terminala, być może próbujesz uruchomić je ze ścieżki w terminalu, w której nie można rozwiązać określonej ścieżki manifestu. Zakodowana na sztywno wartość konfiguracji ścieżki manifestu może nie wskazywać na właściwą lokalizację z punktu wykonania polecenia. Można to rozwiązać za pomocą niestandardowych biegaczy:
public class RobolectricGradleTestRunner extends RobolectricTestRunner { public RobolectricGradleTestRunner(Class<?> testClass) throws InitializationError { super(testClass); } @Override protected AndroidManifest getAppManifest(Config config) { String appRoot = "../app/src/main/"; String manifestPath = appRoot + "AndroidManifest.xml"; String resDir = appRoot + "res"; String assetsDir = appRoot + "assets"; AndroidManifest manifest = createAppManifest(Fs.fileFromPath(manifestPath), Fs.fileFromPath(resDir), Fs.fileFromPath(assetsDir)); return manifest; } }
Konfiguracja Gradle
Aby skonfigurować Gradle do testowania jednostkowego, możesz użyć następujących elementów. Może być konieczne zmodyfikowanie nazw zależności i wersji wymaganych w zależności od potrzeb projektu.
// Robolectric testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:1.9.5' testCompile 'com.squareup.dagger:dagger:1.2.2' testProvided 'com.squareup.dagger:dagger-compiler:1.2.2' testCompile 'com.android.support:support-v4:21.0.+' testCompile 'com.android.support:appcompat-v7:21.0.3' testCompile('org.robolectric:robolectric:2.4') { exclude module: 'classworlds' exclude module: 'commons-logging' exclude module: 'httpclient' exclude module: 'maven-artifact' exclude module: 'maven-artifact-manager' exclude module: 'maven-error-diagnostics' exclude module: 'maven-model' exclude module: 'maven-project' exclude module: 'maven-settings' exclude module: 'plexus-container-default' exclude module: 'plexus-interpolation' exclude module: 'plexus-utils' exclude module: 'wagon-file' exclude module: 'wagon-http-lightweight' exclude module: 'wagon-provider-api' }
Usługi Roboelektryczne i Zabawowe
Jeśli korzystasz z Usług Google Play, będziesz musiał utworzyć własną stałą całkowitą dla wersji Usług Play, aby Robolectric działał poprawnie w tej konfiguracji aplikacji.
<meta-data android:name="com.google.android.gms.version" android:value="@integer/gms_version" tools:replace="android:value" />
Zależności Roboelektryczne wspierające biblioteki
Innym interesującym problemem testowania jest to, że Robolectric nie jest w stanie poprawnie odwoływać się do bibliotek wsparcia. Rozwiązaniem jest dodanie pliku „project.properties” do modułu, w którym znajdują się testy. Na przykład dla bibliotek Support-v4 i AppCompat plik powinien zawierać:
android.library.reference.1=../../build/intermediates/exploded-aar/com.android.support/support-v4/21.0.3 android.library.reference.2=../../build/intermediates/exploded-aar/com.android.support/appcompat-v7/21.0.3
Testy akceptacji/regresji
Testy akceptacyjne/regresyjne automatyzują część ostatniego etapu testowania w rzeczywistym, 100% procentowym środowisku Androida. Na tym poziomie nie używamy mockowanych klas Android OS - testy uruchamiamy na rzeczywistych urządzeniach i emulatorach.
Te okoliczności sprawiają, że proces jest znacznie bardziej niestabilny ze względu na różnorodność urządzeń fizycznych, konfiguracji emulatorów, stanów urządzeń i zestawów funkcji każdego urządzenia. Co więcej, w dużej mierze zależy od wersji systemu operacyjnego i rozmiaru ekranu telefonu, aby decydować o sposobie wyświetlania treści.
Stworzenie odpowiedniego testu, który przejdzie na szeroką gamę urządzeń, jest nieco skomplikowane, ale jak zwykle należy marzyć o dużych rozmiarach, a jednocześnie zaczynać od małych. Tworzenie testów w Robotium to proces iteracyjny. Za pomocą kilku sztuczek można to znacznie uprościć.
Robotium
Robotium to platforma automatyzacji testów systemu Android o otwartym kodzie źródłowym, która istnieje od stycznia 2010 roku. Warto wspomnieć, że Robotium jest rozwiązaniem płatnym, ale ma uczciwą bezpłatną wersję próbną.
Aby przyspieszyć proces pisania testów Robotium, odejdziemy od ręcznego pisania testów na rzecz nagrywania testów. Kompromis polega na jakości kodu i szybkości. Jeśli wprowadzasz poważne zmiany w interfejsie użytkownika, wiele odniesiesz dzięki podejściu do rejestrowania testów i możliwości szybkiego rejestrowania nowych testów.
Testdroid Recorder to darmowy rejestrator testów, który tworzy testy Robotium, rejestrując kliknięcia wykonywane w interfejsie użytkownika. Instalacja narzędzia jest bardzo łatwa, jak opisano w ich dokumentacji wraz z filmem krok po kroku.
Ponieważ Testdroid Recorder jest wtyczką Eclipse, a w tym artykule odwołujemy się do Android Studio, byłby to idealny powód do niepokoju. Jednak w tym przypadku nie stanowi to problemu, ponieważ możesz użyć wtyczki bezpośrednio z pakietem APK i nagrać z nim testy.
Po utworzeniu testów możesz je skopiować i wkleić w Android Studio wraz z dowolną zależnością wymaganą przez rejestrator Testdroid i gotowe. Nagrany test wyglądałby podobnie do poniższej klasy:
public class LoginTest extends ActivityInstrumentationTestCase2<Activity> { private static final String LAUNCHER_ACTIVITY_CLASSNAME = "com.toptal.fitnesstracker.view.activity.SplashActivity"; private static Class<?> launchActivityClass; static { try { launchActivityClass = Class.forName(LAUNCHER_ACTIVITY_CLASSNAME); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } private ExtSolo solo; @SuppressWarnings("unchecked") public LoginTest() { super((Class<Activity>) launchActivityClass); } // executed before every test method @Override public void setUp() throws Exception { super.setUp(); solo = new ExtSolo(getInstrumentation(), getActivity(), this.getClass() .getCanonicalName(), getName()); } // executed after every test method @Override public void tearDown() throws Exception { solo.finishOpenedActivities(); solo.tearDown(); super.tearDown(); } public void testRecorded() throws Exception { try { assertTrue( "Wait for edit text (id: com.toptal.fitnesstracker.R.id.login_username_input) failed.", solo.waitForEditTextById( "com.toptal.fitnesstracker.R.id.login_username_input", 20000)); solo.enterText( (EditText) solo .findViewById("com.toptal.fitnesstracker.R.id.login_username_input"), "[email protected]"); solo.sendKey(ExtSolo.ENTER); solo.sleep(500); assertTrue( "Wait for edit text (id: com.toptal.fitnesstracker.R.id.login_password_input) failed.", solo.waitForEditTextById( "com.toptal.fitnesstracker.R.id.login_password_input", 20000)); solo.enterText( (EditText) solo .findViewById("com.toptal.fitnesstracker.R.id.login_password_input"), "123456"); solo.sendKey(ExtSolo.ENTER); solo.sleep(500); assertTrue( "Wait for button (id: com.toptal.fitnesstracker.R.id.parse_login_button) failed.", solo.waitForButtonById( "com.toptal.fitnesstracker.R.id.parse_login_button", 20000)); solo.clickOnButton((Button) solo .findViewById("com.toptal.fitnesstracker.R.id.parse_login_button")); assertTrue("Wait for text fitness list activity.", solo.waitForActivity(FitnessActivity.class)); assertTrue("Wait for text KM.", solo.waitForText("KM", 20000)); /* Custom class that enables proper clicking of ActionBar action items */ TestUtils.customClickOnView(solo, R.id.action_logout); solo.waitForDialogToOpen(); solo.waitForText("OK"); solo.clickOnText("OK"); assertTrue("waiting for ParseLoginActivity after logout", solo.waitForActivity(ParseLoginActivity.class)); assertTrue( "Wait for button (id: com.toptal.fitnesstracker.R.id.parse_login_button) failed.", solo.waitForButtonById( "com.toptal.fitnesstracker.R.id.parse_login_button", 20000)); } catch (AssertionFailedError e) { solo.fail( "com.example.android.apis.test.Test.testRecorded_scr_fail", e); throw e; } catch (Exception e) { solo.fail( "com.example.android.apis.test.Test.testRecorded_scr_fail", e); throw e; } } }
Jeśli przyjrzysz się uważnie, zauważysz, jaka część kodu jest dość prosta.
Podczas nagrywania testów nie ograniczaj się do stwierdzeń „czekaj”. Poczekaj na pojawienie się okien dialogowych, działań, tekstów. Gwarantuje to, że aktywność i hierarchia widoków będą gotowe do interakcji podczas wykonywania akcji na bieżącym ekranie. W tym samym czasie rób zrzuty ekranu. Testy automatyczne są zwykle bezobsługowe, a zrzuty ekranu to jeden ze sposobów, w jaki można zobaczyć, co faktycznie wydarzyło się podczas tych testów.
Niezależnie od tego, czy testy zakończą się pomyślnie, czy nie, raporty są Twoim najlepszym przyjacielem. Znajdziesz je w katalogu build „module/build/outputs/reports”:
Teoretycznie zespół QA mógłby rejestrować testy i je optymalizować. Można to zrobić, wkładając wysiłek w ustandaryzowany model optymalizacji przypadków testowych. Kiedy zwykle nagrywasz testy, zawsze musisz poprawić kilka rzeczy, aby działały bezbłędnie.
Na koniec, aby uruchomić te testy z Android Studio, możesz je wybrać i uruchomić tak, jakbyś uruchamiał testy jednostkowe. Z terminala jest to jednoliniowiec:
gradle connectedAndroidTest
Wykonywanie testów
Testowanie jednostek Androida za pomocą Robolectric jest niezwykle szybkie, ponieważ działa bezpośrednio w JVM na twoim komputerze. W porównaniu z tym testy akceptacyjne na emulatorach i urządzeniach fizycznych są znacznie wolniejsze. W zależności od rozmiaru testowanych przepływów może to zająć od kilku sekund do kilku minut na przypadek testowy. Faza testów akceptacyjnych powinna być używana jako część zautomatyzowanego procesu kompilacji na serwerze ciągłej integracji.
Szybkość można poprawić przez zrównoleglenie na wielu urządzeniach. Sprawdź to świetne narzędzie Jake'a Whartona i chłopaków z Square http://square.github.io/spoon/. Ma też niezłą relację.
Na wynos
Dostępnych jest wiele narzędzi do testowania Androida, a wraz z dojrzewaniem ekosystemu proces tworzenia testowalnego środowiska i pisania testów stanie się łatwiejszy. Jest jeszcze więcej wyzwań, z którymi trzeba się zmierzyć, a dzięki szerokiej społeczności programistów pracujących nad codziennymi problemami jest dużo miejsca na konstruktywne dyskusje i szybkie informacje zwrotne.
Skorzystaj z podejść opisanych w tym samouczku testowania systemu Android, aby poprowadzić Cię w rozwiązywaniu czekających Cię wyzwań. Jeśli i kiedy napotkasz problemy, zapoznaj się z tym artykułem lub odnośnikami, do których prowadzą linki, aby znaleźć rozwiązania znanych problemów.
W przyszłym poście omówimy równoległość, automatyzację kompilacji, ciągłą integrację, zaczepy Github/BitBucket, wersjonowanie artefaktów i najlepsze praktyki w zakresie głębszego zarządzania ogromnymi projektami aplikacji mobilnych.