To ćwiczenie programowania jest częścią kursu „Android dla zaawansowanych w Kotlinie”. Korzyści z tego kursu będą dla Ciebie najbardziej wartościowe, jeśli wykonasz je w sekwencjach ćwiczeń z programowania, ale nie jest to obowiązkowe. Wszystkie ćwiczenia z kursu są wymienione na stronie Zaawansowane ćwiczenia z programowania na Androida w Kotlin.
Wstęp
Po wdrożeniu pierwszej funkcji pierwszej aplikacji prawdopodobnie udało Ci się uruchomić kod, by sprawdzić, czy wszystko działa zgodnie z oczekiwaniami. Przeprowadzono test, a nie test ręczny. W miarę dodawania i aktualizowania funkcji udało Ci się pewnie kontynuować uruchamianie kodu i sprawdzanie, czy wszystko działa. Robimy to ręcznie za każdym razem, gdy męczymy się, popełniamy błędy i nie staramy się skalować.
Komputery są świetne w skalowaniu i automatyzacji. Programiści w dużych i małych firmach piszą testy automatyczne, które są przeprowadzane przez oprogramowanie i nie wymagają ręcznego uruchamiania aplikacji, by sprawdzić, czy kod działa.
W tej serii ćwiczeń z programowania nauczysz się tworzyć testy (zwane pakietami testowymi) dla rzeczywistych aplikacji.
W pierwszym ćwiczeniu z programowania poznasz podstawy testowania na urządzeniach z Androidem. Napiszesz pierwsze testy i nauczysz się testować LiveData
i ViewModel
.
Co musisz wiedzieć
Pamiętaj:
- Język programowania Kotlin
- Te główne biblioteki Jetpack na Androida:
ViewModel
iLiveData
- architektury aplikacji zgodnej ze wskazówkami dotyczącymi architektury aplikacji i ćwiczeń z programowania na Androida,
Czego się nauczysz
Poznasz te tematy:
- Pisanie i uruchamianie testów jednostkowych w Androidzie
- Jak korzystać z testowania z wykorzystaniem wersji testowej
- Jak wybrać testy z instrumentem i testy lokalne
Poznasz też następujące biblioteki i pojęcia związane z kodem:
- Jednostka J4
- Hacrest
- Biblioteka testowa Androida X
- Podstawowa biblioteka testowa komponentów AndroidX Architecture
Jakie zadania wykonasz:
- Skonfiguruj, uruchom i zinterpretuj zarówno testy lokalne, jak i instrumentowane w Androidzie.
- Zapisuj testy jednostkowe w Androidzie przy użyciu jednostek JUnit4 i Hamcrest.
- Napisz proste testy
LiveData
iViewModel
.
W ramach tej serii ćwiczeń z programowania wykonasz kilka zadań w aplikacji Zadania do wykonania. Ta aplikacja pozwoli Ci zapisywać zadania do wykonania i wyświetlać je na liście. Następnie możesz oznaczyć je jako ukończone, odfiltrować lub usunąć.
Ta aplikacja została napisana w Kotlin i ma kilka ekranów, używa komponentów Jetpack oraz jest zgodna z architekturą przewodnika po architekturze aplikacji. Naucząc się testować tę aplikację, będziesz mieć możliwość testowania aplikacji korzystających z tych samych bibliotek i architektury.
Na początek pobierz kod:
Możesz też skopiować kod GitHuba:
$ git clone https://github.com/googlecodelabs/android-testing.git $ cd android-testing $ git checkout starter_code
W tym zadaniu uruchomisz aplikację i przeanalizujesz kod.
Krok 1. Uruchom przykładową aplikację
Po pobraniu aplikacji Do zrobienia otwórz ją w Android Studio i uruchom. Powinien się skompilować. Poznaj aplikację, wykonując te czynności:
- Utwórz nowe zadanie z pływającym przyciskiem czynności plus. Wpisz tytuł, a następnie dodatkowe informacje o zadaniu. Zapisz przy użyciu zielonego przycisku wyboru.
- Na liście zadań kliknij tytuł właśnie zakończonego zadania i wyświetl jego ekran szczegółów, aby wyświetlić pozostałe opisy.
- Na liście lub na ekranie szczegółów zaznacz pole wyboru obok zadania, by ustawić jego stan na Ukończone.
- Wróć do ekranu zadań, otwórz menu filtra i przefiltruj zadania według stanu Aktywne lub Ukończone.
- Otwórz panel nawigacji i kliknij Statystyki.
- Wróć na ekran przeglądu i z menu panelu nawigacji wybierz Wyczyść ukończone, aby usunąć wszystkie zadania o stanie Ukończone.
Krok 2. Sprawdź przykładowy kod aplikacji
Aplikacja Do zrobienia wykorzystuje przykłady z popularnych testów i architektury architektury Blueprints (korzystając z próbki architektury reaktywnej). Ta aplikacja jest zgodna z architekturą Przewodnika po architekturze aplikacji. Wykorzystuje ono obiekty ViewModels z fragmentami, repozytorium i salą. Jeśli znasz dowolny z tych przykładów, aplikacja ma podobną architekturę:
- Sala z widokiem z programowania
- Ćwiczenia z programowania – podstawy Kotlin na Androida
- Zaawansowane ćwiczenia programowania na Androidzie
- Przykład słonecznika na Androida
- Kurs dotyczący tworzenia aplikacji na Androida z pomocą kursu Kotlin Udacity
Ważne jest, aby lepiej zrozumieć ogólną architekturę aplikacji niż zrozumieć jej działanie na jednej warstwie.
Podsumowanie przesyłek znajdziesz tutaj:
Przesyłka: | |
| Dodawanie lub edytowanie ekranu zadania: kod warstwy interfejsu służący do dodawania i edytowania zadań. |
| Warstwa danych: dotyczy warstwy danych z zadaniami. Zawiera on bazę danych, sieć i kod repozytorium. |
| Ekran statystyk: kod warstwy interfejsu wyświetlany na ekranie statystyk. |
| Ekran szczegółów zadania: kod warstwy interfejsu dotyczący pojedynczego zadania. |
| Ekran Listy zadań: kod warstwy interfejsu zawierający listę wszystkich zadań. |
| Klasy narzędzi: udostępniane klasy w różnych częściach aplikacji, np. do układu przesuwania używanego na wielu ekranach. |
Warstwa danych (.data)
Ta aplikacja zawiera symulowaną warstwę sieciową w pakiecie remote i warstwę bazy danych w pakiecie local. Dla uproszczenia w tym projekcie symulacja warstwy sieciowej jest symulowana za pomocą tylko HashMap
z opóźnieniem, a nie przez rzeczywiste żądania sieciowe.
DefaultTasksRepository
koordynuje lub pośredniczy między warstwą sieciową a warstwą bazy danych i zwraca dane do warstwy interfejsu.
Warstwa UI ( .addedittask, .statistics, .taskdetail, .tasks)
Każdy pakiet warstw interfejsu zawiera fragment i model widoku, a także wszystkie inne klasy wymagane w interfejsie (takie jak adapter do listy zadań). TaskActivity
to działanie zawierające wszystkie fragmenty.
Nawigacja
Nawigacja w aplikacji jest sterowana za pomocą komponentu Nawigacja. Jest on zdefiniowany w pliku nav_graph.xml
. Nawigacja jest uruchamiana w modelach widoku danych za pomocą klasy Event
. Modele widoku danych określają też, jakie argumenty należy przekazać. Fragmenty śledzą Event
i przeprowadzają rzeczywistą nawigację między ekranami.
W tym zadaniu wykonasz pierwsze testy.
- W Android Studio otwórz panel Projekt i znajdź te 3 foldery:
com.example.android.architecture.blueprints.todoapp
com.example.android.architecture.blueprints.todoapp (androidTest)
com.example.android.architecture.blueprints.todoapp (test)
Takie foldery są nazywane zbiorami źródłowymi. Zestawy źródłowe to foldery zawierające kod źródłowy aplikacji. Zestawy źródłowe są oznaczone kolorem zielonym (androidTest i test). Podczas tworzenia nowego projektu na Androida domyślnie otrzymujesz trzy poniższe zestawy źródeł. Są to:
main
: zawiera kod aplikacji. Ten kod jest dostępny we wszystkich wersjach aplikacji, które możesz tworzyć (są to wersje kompilacji).androidTest
: zawiera testy z instrumentacją.test
: zawiera testy nazywane testami lokalnymi.
Różnica między testami lokalnymi a testami polowymi polega na sposobie ich przeprowadzania.
Testy lokalne (test
zestaw źródeł)
Te testy są przeprowadzane lokalnie na maszynie wirtualnej dla programistów i nie wymagają emulatora ani urządzenia fizycznego. Dlatego grają szybko, ale ich wierność jest niższa, co oznacza, że grają mniej jak w prawdziwym świecie.
W Android Studio lokalne testy są oznaczone zieloną i czerwoną ikoną trójkąta.
Instrumentalne testy (androidTest
zestaw źródłowy)
Testy są przeprowadzane na prawdziwych lub emulowanych urządzeniach z Androidem, dzięki czemu odzwierciedlają rzeczywiste wyniki, ale są też znacznie wolniejsze.
W Android Studio testy z instrukcjami są reprezentowane przez Androida z zieloną i czerwoną trójkątem.
Krok 1. Uruchom test lokalny
- Otwórz folder
test
, aż znajdziesz plik ExampleUnitTest.kt. - Kliknij ją prawym przyciskiem myszy i wybierz Uruchom przykładowy test.
W oknie Uruchom u dołu ekranu powinny pojawić się te dane wyjściowe:
- Zwróć uwagę na zielone znaczniki wyboru i rozwiń wyniki testu, by potwierdzić, że 1 test o nazwie
addition_isCorrect
został zaliczony. Warto wiedzieć, że dodanie działa zgodnie z oczekiwaniami.
Krok 2. Sprawdź, czy test się nie powiódł
Poniżej znajduje się test, który właśnie udało Ci się przeprowadzić.
PrzykładowyTestTest.kt
// A test class is just a normal class
class ExampleUnitTest {
// Each test is annotated with @Test (this is a Junit annotation)
@Test
fun addition_isCorrect() {
// Here you are checking that 4 is the same as 2+2
assertEquals(4, 2 + 2)
}
}
Uwaga: testy
- są klasą w jednym z testowych zbiorów źródłowych.
- zawierają funkcje, które rozpoczynają się od adnotacji
@Test
(każda funkcja jest pojedynczym testem). - u8susel zawierają instrukcje.
Android służy do testowania biblioteki JUnit na potrzeby testów (w tym module Jlab4). Potwierdzenia i adnotacja @Test
pochodzą z jednostki.
Potwierdzenie to podstawa testu. Jest to instrukcja, która sprawdza, czy kod lub aplikacja działa zgodnie z oczekiwaniami. W tym przypadku potwierdzenie to assertEquals(4, 2 + 2)
, które sprawdza, czy 4 jest równe 2 + 2.
Aby zobaczyć, jak wygląda niepowodzenie testu, dodaj potwierdzenie, które możesz łatwo zobaczyć. Sprawdza, czy trzecia liczba to 1+1.
- Dodaj
assertEquals(3, 1 + 1)
do testuaddition_isCorrect
.
PrzykładowyTestTest.kt
class ExampleUnitTest {
// Each test is annotated with @Test (this is a Junit annotation)
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
assertEquals(3, 1 + 1) // This should fail
}
}
- Uruchom test.
- W wynikach testu zobaczysz symbol X widoczny obok testu.
- Zwróć też uwagę:
- Jedna nieudana próba potwierdzenia danych kończy się niepowodzeniem w całości testu.
- otrzymasz oczekiwaną wartość (3) w porównaniu do wartości, która została już obliczona (2).
- Nastąpi przekierowanie do wiersza z błędnym potwierdzeniem
(ExampleUnitTest.kt:16)
.
Krok 3. Uruchom test z instrumentacją
Testy z instrumentacją są w zestawie źródłowym androidTest
.
- Otwórz zbiór źródłowy
androidTest
. - Uruchom test o nazwie
ExampleInstrumentedTest
.
Przykładowy instrument testowy
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.example.android.architecture.blueprints.reactive",
appContext.packageName)
}
}
W przeciwieństwie do testu lokalnego ten test działa na urządzeniu (w przykładzie poniżej widać emulowany telefon Pixel 2):
Jeśli masz podłączone urządzenie lub uruchomiony emulator, powinien uruchomić go w emulatorze.
W tym zadaniu utworzysz testy dla getActiveAndCompleteStats
, które obliczają procent aktywnych i ukończonych statystyk zadań dla aplikacji. Możesz zobaczyć te statystyki na ekranie statystyk aplikacji.
Krok 1. Utwórz klasę testową
- W zestawie źródłowym
main
w aplikacjitodoapp.statistics
otwórzStatisticsUtils.kt
. - Znajdź funkcję
getActiveAndCompletedStats
.
StatystykiUtils.kt
internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {
val totalTasks = tasks!!.size
val numberOfActiveTasks = tasks.count { it.isActive }
val activePercent = 100 * numberOfActiveTasks / totalTasks
val completePercent = 100 * (totalTasks - numberOfActiveTasks) / totalTasks
return StatsResult(
activeTasksPercent = activePercent.toFloat(),
completedTasksPercent = completePercent.toFloat()
)
}
data class StatsResult(val activeTasksPercent: Float, val completedTasksPercent: Float)
Funkcja getActiveAndCompletedStats
akceptuje listę zadań i zwraca StatsResult
. StatsResult
to klasa danych zawierająca 2 cyfry, odsetek ukończonych zadań i aktywnych zadań.
Android Studio udostępnia narzędzia do generowania atutów testowych i pomocy w testowaniu tej funkcji.
- Kliknij prawym przyciskiem myszy
getActiveAndCompletedStats
i wybierz Wygeneruj > Test.
Otworzy się okno Tworzenie testu:
- zmień Nazwę klasy: na
StatisticsUtilsTest
(zamiastStatisticsUtilsKtTest
; lepiej jest unikać słowa kluczowego KT w nazwie klasy testowej). - Zachowaj pozostałe ustawienia domyślne. JUnit 4 to odpowiednia biblioteka testowa. Pakiet docelowy jest prawidłowy (odzwierciedla lokalizację klasy
StatisticsUtils
) i nie musisz zaznaczać żadnych pól wyboru (spowoduje to wygenerowanie dodatkowego kodu, ale test zapiszesz od zera). - Naciśnij OK.
Otworzy się okno Wybierz katalog docelowy.
Przeprowadzisz test lokalny, ponieważ Twoja funkcja wykonuje obliczenia matematyczne i nie będzie zawierać kodu dla Androida. Nie trzeba go więc uruchamiać na prawdziwym lub emulowanym urządzeniu.
- Wybierz katalog
test
(nieandroidTest
), ponieważ będziesz przygotowywać testy lokalne. - Kliknij OK.
- Zwróć uwagę na wygenerowaną klasę
StatisticsUtilsTest
wtest/statistics/
.
Krok 2. Napisz pierwszą funkcję testową
Chcesz napisać test, który sprawdzi:
- jeśli nie ma ukończonych zadań i jedno aktywne
- że odsetek aktywnych testów wynosi 100%,
- a procent ukończonych zadań wynosi 0%.
- Otwórz aplikację
StatisticsUtilsTest
. - Utwórz funkcję o nazwie
getActiveAndCompletedStats_noCompleted_returnsHundredZero
.
StatystykiUtilsTest.kt
class StatisticsUtilsTest {
fun getActiveAndCompletedStats_noCompleted_returnsHundredZero() {
// Create an active task
// Call your function
// Check the result
}
}
- Dodaj adnotację
@Test
nad nazwą funkcji, by wskazać, że jest to test. - Utwórz listę zadań.
// Create an active task
val tasks = listOf<Task>(
Task("title", "desc", isCompleted = false)
)
- Zadzwoń do
getActiveAndCompletedStats
i wykonaj te zadania.
// Call your function
val result = getActiveAndCompletedStats(tasks)
- Upewnij się, że ciąg
result
jest zgodny z oczekiwaniami, używając twierdzeń.
// Check the result
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)
Oto pełny kod.
StatystykiUtilsTest.kt
class StatisticsUtilsTest {
@Test
fun getActiveAndCompletedStats_noCompleted_returnsHundredZero() {
// Create an active task (the false makes this active)
val tasks = listOf<Task>(
Task("title", "desc", isCompleted = false)
)
// Call your function
val result = getActiveAndCompletedStats(tasks)
// Check the result
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)
}
}
- Uruchom test (kliknij prawym przyciskiem myszy
StatisticsUtilsTest
i wybierz Uruchom).
Powinny one spełniać te wymagania:
Krok 3. Dodaj zależność Hamcrest
Testy są tylko dowodem na działanie kodu, więc są czytelne, jeśli są czytelne dla człowieka. Porównaj te dwa stwierdzenia:
assertEquals(result.completedTasksPercent, 0f)
// versus
assertThat(result.completedTasksPercent, `is`(0f))
Drugie zdanie można dostrzec bardziej jak ludzkie zdanie. Jest napisany za pomocą platformy asercyjnej zwanej Hamcrestem. Innym przydatnym narzędziem do tworzenia zrozumiałych twierdzeń jest Biblioteka prawdziwości. Wykorzystasz Hamcrest w tym ćwiczeniu z programowania do pisania twierdzeń.
- Otwórz
build.grade (Module: app)
i dodaj tę zależność.
app/build.gradle
dependencies {
// Other dependencies
testImplementation "org.hamcrest:hamcrest-all:$hamcrestVersion"
}
Zazwyczaj podczas dodawania zależności korzystasz z implementation
, ale tym razem używasz testImplementation
. Gdy aplikacja będzie gotowa do udostępnienia światu, najlepiej nie wypełniać jej APK kodem testowym ani zależnościami. Użyj konfiguracji Gradle, by określić, czy biblioteka ma być uwzględniona w kodzie głównym czy testowym. Oto najczęstsze konfiguracje:
implementation
– zależność jest dostępna we wszystkich zestawach źródłowych, w tym w testowych zestawach.testImplementation
– zależność jest dostępna tylko w zestawie testowym.androidTestImplementation
– zależność jest dostępna tylko w zestawie źródłowymandroidTest
.
Wybrana konfiguracja określa miejsce, w którym można korzystać z zależności. Jeśli napiszesz:
testImplementation "org.hamcrest:hamcrest-all:$hamcrestVersion"
Oznacza to, że usługa Hamcrest będzie dostępna tylko w zestawie testowym. Dzięki temu w aplikacji końcowej nie będzie też aplikacji Hamcrest.
Krok 4. Użyj aplikacji Hamcrest do zapisu twierdzeń
- Zaktualizuj test
getActiveAndCompletedStats_noCompleted_returnsHundredZero()
, aby korzystać z testera Hamcrest iassertThat
zamiastassertEquals
.
// REPLACE
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)
// WITH
assertThat(result.activeTasksPercent, `is`(100f))
assertThat(result.completedTasksPercent, `is`(0f))
Jeśli pojawi się taka prośba, możesz skorzystać z importu import org.hamcrest.Matchers.`is`
.
Końcowy test będzie wyglądać tak jak w poniższym kodzie.
StatystykiUtilsTest.kt
import com.example.android.architecture.blueprints.todoapp.data.Task
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.`is`
import org.junit.Test
class StatisticsUtilsTest {
@Test
fun getActiveAndCompletedStats_noCompleted_returnsHundredZero {
// Create an active tasks (the false makes this active)
val tasks = listOf<Task>(
Task("title", "desc", isCompleted = false)
)
// Call your function
val result = getActiveAndCompletedStats(tasks)
// Check the result
assertThat(result.activeTasksPercent, `is`(100f))
assertThat(result.completedTasksPercent, `is`(0f))
}
}
- Uruchom zaktualizowany test, aby sprawdzić, czy nadal działa.
Dzięki tym ćwiczeniom nie poznasz wszystkie informacje o Hamcrestie, więc jeśli chcesz dowiedzieć się więcej, przeczytaj oficjalny samouczek.
To jest zadanie opcjonalne.
W tym zadaniu napiszesz więcej testów z wykorzystaniem JUnit i Hamcrest. Będziesz też przygotowywać testy z użyciem strategii opracowanej na podstawie programowania testowania. Testuj programowanie nakierowane na programowanie lub TDD to szkoła programowania, według której zamiast pisać swój kod funkcji, najpierw pisz testy. Następnie napisz kod funkcji, aby ukończyć testy.
Krok 1. Napisz testy
Napisz testy, gdy masz normalną listę zadań:
- Jeśli jest 1 ukończone zadanie i nie ma żadnych aktywnych zadań, wartość procentowa
activeTasks
powinna wynosić0f
, a odsetek ukończonych zadań to100f
. - Jeśli 2 ukończone zadania są 3 aktywne, odsetek wartości powinien wynosić
40f
, a wartość procentowa to60f
.
Krok 2. Testowanie testu
Zpisany kod getActiveAndCompletedStats
ma błąd. Zwróć uwagę na to, co się dzieje, gdy lista jest pusta lub ma wartość NULL. W obu przypadkach wartość procentowa powinna wynosić zero.
internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {
val totalTasks = tasks!!.size
val numberOfActiveTasks = tasks.count { it.isActive }
val activePercent = 100 * numberOfActiveTasks / totalTasks
val completePercent = 100 * (totalTasks - numberOfActiveTasks) / totalTasks
return StatsResult(
activeTasksPercent = activePercent.toFloat(),
completedTasksPercent = completePercent.toFloat()
)
}
Aby naprawić kod i utworzyć testy, użyj programowania opartego na testach. Aby to zrobić, postępuj zgodnie z tymi wskazówkami.
- Napisz test, stosując strukturę o podanej nazwie oraz Kiedy, kiedy i Później.
- Upewnij się, że test się nie powiedzie.
- Napisz minimalny kod potrzebny do zdania testu.
- Powtórz te czynności dla wszystkich testów.
Zamiast naprawiać błąd, trzeba zacząć od napisania testów. Następnie możesz potwierdzić, że przeprowadzasz testy chroniące przed przypadkowym ponownym wprowadzeniem tych błędów w przyszłości.
- Jeśli lista jest pusta (
emptyList()
), obie wartości powinny wynosić 0f. - Jeśli podczas wczytywania zadań wystąpił błąd, lista będzie mieć wartość
null
i obie wartości powinny wynosić 0f. - Uruchom testy i upewnij się, że ich niepowodzenie:
Krok 3. Rozwiąż problem
Teraz po zakończeniu testów napraw błąd.
- Napraw błąd w danych
getActiveAndCompletedStats
, zwracając wartość0f
, jeśli atrybuttasks
ma wartośćnull
lub jest pusty:
internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {
return if (tasks == null || tasks.isEmpty()) {
StatsResult(0f, 0f)
} else {
val totalTasks = tasks.size
val numberOfActiveTasks = tasks.count { it.isActive }
StatsResult(
activeTasksPercent = 100f * numberOfActiveTasks / tasks.size,
completedTasksPercent = 100f * (totalTasks - numberOfActiveTasks) / tasks.size
)
}
}
- Uruchom testy ponownie i sprawdź, czy się zgadzają.
Wykonując testy TDD i pisając testy, pomogłeś:
- Nowe funkcje są zawsze powiązane z testami, więc testy pełnią funkcję dokumentacji kodu.
- Testy sprawdzają poprawność wyników i chronią przed zgłoszonymi przez Ciebie błędami.
Rozwiązanie: pisanie większej liczby testów
Poniżej znajdziesz wszystkie testy i odpowiadający im kod funkcji.
StatystykiUtilsTest.kt
class StatisticsUtilsTest {
@Test
fun getActiveAndCompletedStats_noCompleted_returnsHundredZero {
val tasks = listOf(
Task("title", "desc", isCompleted = false)
)
// When the list of tasks is computed with an active task
val result = getActiveAndCompletedStats(tasks)
// Then the percentages are 100 and 0
assertThat(result.activeTasksPercent, `is`(100f))
assertThat(result.completedTasksPercent, `is`(0f))
}
@Test
fun getActiveAndCompletedStats_noActive_returnsZeroHundred() {
val tasks = listOf(
Task("title", "desc", isCompleted = true)
)
// When the list of tasks is computed with a completed task
val result = getActiveAndCompletedStats(tasks)
// Then the percentages are 0 and 100
assertThat(result.activeTasksPercent, `is`(0f))
assertThat(result.completedTasksPercent, `is`(100f))
}
@Test
fun getActiveAndCompletedStats_both_returnsFortySixty() {
// Given 3 completed tasks and 2 active tasks
val tasks = listOf(
Task("title", "desc", isCompleted = true),
Task("title", "desc", isCompleted = true),
Task("title", "desc", isCompleted = true),
Task("title", "desc", isCompleted = false),
Task("title", "desc", isCompleted = false)
)
// When the list of tasks is computed
val result = getActiveAndCompletedStats(tasks)
// Then the result is 40-60
assertThat(result.activeTasksPercent, `is`(40f))
assertThat(result.completedTasksPercent, `is`(60f))
}
@Test
fun getActiveAndCompletedStats_error_returnsZeros() {
// When there's an error loading stats
val result = getActiveAndCompletedStats(null)
// Both active and completed tasks are 0
assertThat(result.activeTasksPercent, `is`(0f))
assertThat(result.completedTasksPercent, `is`(0f))
}
@Test
fun getActiveAndCompletedStats_empty_returnsZeros() {
// When there are no tasks
val result = getActiveAndCompletedStats(emptyList())
// Both active and completed tasks are 0
assertThat(result.activeTasksPercent, `is`(0f))
assertThat(result.completedTasksPercent, `is`(0f))
}
}
StatystykiUtils.kt
internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {
return if (tasks == null || tasks.isEmpty()) {
StatsResult(0f, 0f)
} else {
val totalTasks = tasks.size
val numberOfActiveTasks = tasks.count { it.isActive }
StatsResult(
activeTasksPercent = 100f * numberOfActiveTasks / tasks.size,
completedTasksPercent = 100f * (totalTasks - numberOfActiveTasks) / tasks.size
)
}
}
Świetna robota z podstawami pisania i uruchamiania testów. W dalszej części dowiesz się, jak pisać podstawowe testy ViewModel
i LiveData
.
Z pozostałej części ćwiczeń dowiesz się, jak pisać testy w 2 klasach Androida, które są wspólne dla większości aplikacji: ViewModel
i LiveData
.
Zaczynasz od napisania testów: TasksViewModel
.
Skoncentrujesz się na testach, które opierają się na wszystkich aspektach Twojego modelu w widoku danych i nie wymagają kodu repozytorium. Kod repozytorium obejmuje kod asynchroniczny, bazy danych i wywołania sieciowe, które zwiększają złożoność testu. Teraz pozwoli Ci to unikać i skoncentrować się na pisaniu testów funkcji ViewModel, które nie testują bezpośrednio żadnego elementu repozytorium.
W teście, który wpiszesz, sprawdzimy, czy po wywołaniu metody addNewTask
uruchomi się Event
w celu otwarcia nowego okna zadania. Oto kod aplikacji, który będzie testowany.
TaskTaskViewModel.kt
fun addNewTask() {
_newTaskEvent.value = Event(Unit)
}
Krok 1. Tworzenie klasy TestViewModelTest
Wykonując te same czynności, co w przypadku klienta StatisticsUtilTest
, w tym kroku utworzysz plik testowy dla TasksViewModelTest
.
- Otwórz klasę, którą chcesz przetestować, w pakiecie
tasks
(TasksViewModel.
) - W kodzie kliknij prawym przyciskiem myszy nazwę zajęć
TasksViewModel
-> Wygeneruj -> Test.
- Na ekranie Utwórz test kliknij OK, aby zaakceptować (nie musisz zmieniać żadnych ustawień domyślnych).
- W oknie Choose Destination Directory (Wybierz katalog docelowy) wybierz katalog test.
Krok 2. Rozpocznij pisanie testu ViewModel
W tym kroku dodasz test modelu widoku, by sprawdzić, czy po wywołaniu metody addNewTask
uruchamia się narzędzie Event
do otwierania nowego okna zadania.
- Utwórz nowy test o nazwie
addNewTask_setsNewTaskEvent
.
TaskTaskViewModelTest.kt
class TasksViewModelTest {
@Test
fun addNewTask_setsNewTaskEvent() {
// Given a fresh TasksViewModel
// When adding a new task
// Then the new task event is triggered
}
}
A co z kontekstem aplikacji?
Jeśli tworzysz instancję TasksViewModel
, aby ją przetestować, jej konstruktor wymaga kontekstu aplikacji. Ale w tym teście nie tworzysz pełnej aplikacji z aktywnościami, UI i fragmentami, więc jak uzyskać kontekst aplikacji?
TaskTaskViewModelTest.kt
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(???)
Biblioteki testowe Androida X zawierają klasy i metody dostarczające wersji komponentów, takich jak aplikacje i działania, które są przeznaczone do testów. Jeśli prowadzisz test lokalny i potrzebujesz symulowanych klas platformy Androida(takich jak kontekst aplikacji), wykonaj te czynności, aby prawidłowo skonfigurować test Androida X:
- Dodaj zależności główne i wewnętrzne dla AndroidX Test
- Dodaj zależność biblioteki testowania Robo
- Dodaj do klasy adnotacje uruchamiające test AndroidaJunit4
- Zapisz kod testowy Androida X
Wykonaj te czynności, a następnie dowiesz się, co one robią.
Krok 3. Dodaj zależności Gradle
- Skopiuj te zależności do pliku
build.gradle
modułu aplikacji, aby dodać podstawowe zależności Android Core Core i Test Exxce oraz Androida w wersji Robolectric.
app/build.gradle
// AndroidX Test - JVM testing
testImplementation "androidx.test.ext:junit-ktx:$androidXTestExtKotlinRunnerVersion"
testImplementation "androidx.test:core-ktx:$androidXTestCoreVersion"
testImplementation "org.robolectric:robolectric:$robolectricVersion"
Krok 4. Dodaj testera JUnit
- Dodaj
@RunWith(AndroidJUnit4::class)
nadczas lekcji.
TaskTaskViewModelTest.kt
@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {
// Test code
}
Krok 5. Użyj testu Androida X
Teraz możesz skorzystać z biblioteki testowej Androida X. Obejmuje to metodę ApplicationProvider.getApplicationContex
t
, która pobiera kontekst aplikacji.
- Utwórz
TasksViewModel
przy użyciuApplicationProvider.getApplicationContext()
z biblioteki testowej Androida X.
TaskTaskViewModelTest.kt
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
- Zadzwoń do firmy
addNewTask
pod numertasksViewModel
.
TaskTaskViewModelTest.kt
tasksViewModel.addNewTask()
Na tym etapie test powinien wyglądać tak jak poniżej.
TaskTaskViewModelTest.kt
@Test
fun addNewTask_setsNewTaskEvent() {
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
// When adding a new task
tasksViewModel.addNewTask()
// Then the new task event is triggered
// TODO test LiveData
}
- Uruchom test, aby sprawdzić, czy wszystko działa.
Pojęcie: Jak działa test AndroidX?
Co to jest test Androida X?
AndroidX Test to zbiór bibliotek do testowania. Obejmuje on klasy i metody, które udostępniają wersje komponentów takich jak aplikacje i działania, które są przeznaczone do testów. Ten kod to przykład funkcji testowej XX służącej do pobierania kontekstu aplikacji.
ApplicationProvider.getApplicationContext()
Jedną z zalet interfejsów API AndroidX Test jest to, że są one zaprojektowane do działania zarówno w przypadku testów lokalnych , jak i testów z instrumentem. To dobrze, bo:
- Możesz przeprowadzić taki sam test jak test lokalny lub test z instrumentacją.
- Nie musisz uczyć się korzystania z różnych interfejsów API do testów lokalnych i instrumentowanych.
Na przykład kod został napisany przez biblioteki testów Androida X, więc możesz przenieść klasę TasksViewModelTest
z folderu test
do folderu androidTest
, a testy będą nadal przeprowadzane. getApplicationContext()
działa trochę inaczej w zależności od tego, czy jest uruchomiony jako test lokalny czy instrumentalny:
- Jeśli jest to instrumentowany test, otrzyma on rzeczywisty kontekst aplikacji podany podczas uruchamiania emulatora lub połączenia się z prawdziwym urządzeniem.
- Jeśli jest to test lokalny, używany jest symulowany środowisko Androida.
Co to jest Robolectric?
Symulowane środowisko Androida, którego AndroidX Test używa do testowania lokalnych, udostępnia Robolectric. Robolectric to biblioteka, która tworzy symulowany środowisko Androida do testowania i działa szybciej niż uruchamianie emulatora czy uruchamianie urządzenia. Bez zależności od Robolectric otrzymasz ten błąd:
Co robi @RunWith(AndroidJUnit4::class)
?
Tester to komponent JUnit, który przeprowadza testy. Jeśli nie uruchomi się tester, nie zostaną one uruchomione. Domyślny bieg testu dostarczony przez JUnit, który otrzymujesz automatycznie. @RunWith
zastępuje to domyślne narzędzie do uruchamiania testów.
Uruchamiający test AndroidJUnit4
, który umożliwia testowanie Androida X w zależności od tego, czy są to narzędzia, czy testy lokalne.
Krok 6. Rozwiązywanie ostrzeżeń Robolectric
Po uruchomieniu kodu zwróć uwagę, że używasz Robolectric.
Dzięki testowi AndroidX i testerowi AndroidJunit4 można to zrobić bez konieczności pisania choćby jednej linijki kodu Robolectric.
Możesz zobaczyć dwa ostrzeżenia.
No such manifest file: ./AndroidManifest.xml
"WARN: Android SDK 29 requires Java 9..."
Możesz naprawić ostrzeżenie dotyczące No such manifest file: ./AndroidManifest.xml
, aktualizując plik Gradle.
- Dodaj ten wiersz do pliku Gradle, aby użyć właściwego pliku manifestu Androida. Opcja includeAndroidResources umożliwia dostęp do zasobów Androida w testach jednostkowych, w tym do pliku AndroidManifest.
app/build.gradle
// Always show the result of every unit test when running via command line, even if it passes.
testOptions.unitTests {
includeAndroidResources = true
// ...
}
Ostrzeżenie "WARN: Android SDK 29 requires Java 9..."
jest bardziej skomplikowane. Uruchamianie testów na urządzeniach z Androidem Q wymaga środowiska Java 9. Zamiast próbować skonfigurować Android Studio tak, aby używało Javy 9, w tym ćwiczeniu z programowania zachowaj wartość docelową i skompiluj pakiet SDK w wersji 28.
Podsumowanie:
- Testy modelu opartego na czystym widoku można zazwyczaj przeprowadzić w zestawie źródłowym
test
, ponieważ ich kod zwykle nie wymaga Androida. - Aby uzyskać wersje testowe komponentów, takich jak Aplikacje i Działania, możesz skorzystać z biblioteki testowej Androida.
- Jeśli chcesz uruchomić symulowany kod Androida w zbiorze źródłowym
test
, możesz dodać zależność Robolectric i adnotację@RunWith(AndroidJUnit4::class)
.
Gratulacje, do przeprowadzenia testu używasz zarówno biblioteki testów Androida X, jak i aplikacji Robolectric. Test jeszcze się nie zakończył (nie masz jeszcze wypisanego oświadczenia, ponieważ ma to wyglądać tak: // TODO test LiveData
). Poćwicz teraz tworzenie instrukcji za pomocą LiveData
.
Z tego zadania dowiesz się, jak prawidłowo potwierdzić wartość LiveData
.
Oto miejsce, w którym przerwano testowanie bez wyświetlania modelu atrybucji addNewTask_setsNewTaskEvent
.
TaskTaskViewModelTest.kt
@Test
fun addNewTask_setsNewTaskEvent() {
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
// When adding a new task
tasksViewModel.addNewTask()
// Then the new task event is triggered
// TODO test LiveData
}
Aby przetestować aplikację LiveData
, wykonaj 2 rzeczy:
- Używaj modułu:
InstantTaskExecutorRule
- Zadbaj o
LiveData
obserwację
Krok 1. Używaj reguły ExTaskereororRule
InstantTaskExecutorRule
to reguła JUnit. Użycie wraz z adnotacją @get:Rule
spowoduje, że kod w klasie InstantTaskExecutorRule
zostanie uruchomiony przed testami i po nich. Aby zobaczyć dokładny kod, użyj skrótu klawiszowego @get:Rule
Command + B.
Ta reguła uruchamia wszystkie zadania związane z komponentami architektury w tym samym wątku, dzięki czemu wyniki testu są synchroniczne i powtarzalne. Jeśli tworzysz testy, które obejmują testowanie LiveData, użyj tej reguły.
- Dodaj zależność Gradle w podstawowej bibliotece testowej komponentów architektury (która zawiera tę regułę).
app/build.gradle
testImplementation "androidx.arch.core:core-testing:$archTestingVersion"
- Otwórz:
TasksViewModelTest.kt
- Dodaj
InstantTaskExecutorRule
do klasyTasksViewModelTest
.
TaskTaskViewModelTest.kt
class TasksViewModelTest {
@get:Rule
var instantExecutorRule = InstantTaskExecutorRule()
// Other code...
}
Krok 2. Dodaj klasę LiveDataTestUtil.kt
Teraz zadbaj o to, aby testowane przez Ciebie LiveData
były przestrzegane.
Gdy używasz LiveData
, zwykle występuje aktywność lub fragment (LifecycleOwner
) obserwowany LiveData
.
viewModel.resultLiveData.observe(fragment, Observer {
// Observer code here
})
Ta obserwacja jest ważna. Musisz mieć aktywnych obserwatorów w LiveData
, aby
- wyzwalają zdarzenia
onChanged
. - powodują przekształcenia.
Aby uzyskać oczekiwane działanie LiveData
w modelu widoku danych LiveData
, musisz użyć właściwości LiveData
za pomocą właściwości LifecycleOwner
.
Może to powodować problem: w teście TasksViewModel
nie masz aktywności ani fragmentu, które można by zaobserwować na urządzeniu LiveData
. Aby obejść ten problem, możesz użyć metody observeForever
, która zapewnia ciągłość właściwości LiveData
i nie wymaga użycia właściwości LifecycleOwner
. Podczas korzystania z funkcji observeForever
musisz usunąć obserwatora, aby nie doszło do wycieku informacji.
Wygląda to mniej więcej tak. Sprawdź to:
@Test
fun addNewTask_setsNewTaskEvent() {
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
// Create observer - no need for it to do anything!
val observer = Observer<Event<Unit>> {}
try {
// Observe the LiveData forever
tasksViewModel.newTaskEvent.observeForever(observer)
// When adding a new task
tasksViewModel.addNewTask()
// Then the new task event is triggered
val value = tasksViewModel.newTaskEvent.value
assertThat(value?.getContentIfNotHandled(), (not(nullValue())))
} finally {
// Whatever happens, don't forget to remove the observer!
tasksViewModel.newTaskEvent.removeObserver(observer)
}
}
To sporo powtarzalnego kodu, dzięki któremu można zobaczyć pojedynczy LiveData
podczas testu. Istnieje kilka sposobów, aby pozbyć się tego problemu. Zamierzasz utworzyć funkcję rozszerzenia o nazwie LiveDataTestUtil
, która ułatwi dodawanie obserwatorów.
- Utwórz nowy plik Kotlin o nazwie
LiveDataTestUtil.kt
w zbiorze źródłowymtest
.
- Skopiuj i wklej poniższy kod.
LiveDataTestUtil.kt,
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
fun <T> LiveData<T>.getOrAwaitValue(
time: Long = 2,
timeUnit: TimeUnit = TimeUnit.SECONDS,
afterObserve: () -> Unit = {}
): T {
var data: T? = null
val latch = CountDownLatch(1)
val observer = object : Observer<T> {
override fun onChanged(o: T?) {
data = o
latch.countDown()
this@getOrAwaitValue.removeObserver(this)
}
}
this.observeForever(observer)
try {
afterObserve.invoke()
// Don't wait indefinitely if the LiveData is not set.
if (!latch.await(time, timeUnit)) {
throw TimeoutException("LiveData value was never set.")
}
} finally {
this.removeObserver(observer)
}
@Suppress("UNCHECKED_CAST")
return data as T
}
Jest to dość skomplikowana metoda. Funkcja ta tworzy funkcję rozszerzenia Kotlin o nazwie getOrAwaitValue
, która dodaje obserwatora, pobiera wartość LiveData
i oczyszcza obserwatora. W skrócie jest to skrócona wersja wielokrotnego użytku przedstawionego powyżej kodu observeForever
. Pełne omówienie tych zajęć znajdziesz w tym poście na blogu.
Krok 3. Zapisz potwierdzenie za pomocą funkcji getOrAwaitValue
W tym kroku należy użyć metody getOrAwaitValue
i utworzyć instrukcję potwierdzającą, że newTaskEvent
został wywołany.
- Pobierz wartość
LiveData
dlanewTaskEvent
przy użyciugetOrAwaitValue
.
val value = tasksViewModel.newTaskEvent.getOrAwaitValue()
- Upewnij się, że wartość nie jest pusta.
assertThat(value.getContentIfNotHandled(), (not(nullValue())))
Pełny test powinien wyglądać tak jak w poniższym kodzie.
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.example.android.architecture.blueprints.todoapp.getOrAwaitValue
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.not
import org.hamcrest.Matchers.nullValue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {
@get:Rule
var instantExecutorRule = InstantTaskExecutorRule()
@Test
fun addNewTask_setsNewTaskEvent() {
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
// When adding a new task
tasksViewModel.addNewTask()
// Then the new task event is triggered
val value = tasksViewModel.newTaskEvent.getOrAwaitValue()
assertThat(value.getContentIfNotHandled(), not(nullValue()))
}
}
- Uruchom kod i obejrzyj test.
Skoro wiesz już, jak przygotować test, napisz go samodzielnie. Na podstawie tych zdobytych umiejętności poćwicz kolejne testy TasksViewModel
.
Krok 1. Utwórz własny test ViewModel
Piszesz do: setFilterAllTasks_tasksAddViewVisible()
. Ten test powinien sprawdzić, czy filtr jest ustawiony na wyświetlanie wszystkich zadań i czy widoczny jest przycisk Dodaj zadanie.
- Dla ułatwienia napisz
addNewTask_setsNewTaskEvent()
w aplikacjiTasksViewModelTest
o nazwiesetFilterAllTasks_tasksAddViewVisible()
, który ustawia tryb filtrowania naALL_TASKS
i potwierdza, żetasksAddViewVisible
LiveData totrue
.
Użyj poniższego kodu, aby rozpocząć.
TestujModelZadań
@Test
fun setFilterAllTasks_tasksAddViewVisible() {
// Given a fresh ViewModel
// When the filter type is ALL_TASKS
// Then the "Add task" action is visible
}
Uwaga:
- Wartość
TasksFilterType
w przypadku wszystkich zadań wynosiALL_TASKS.
- Widoczność przycisku umożliwiającego dodanie zadania jest określana przez zasadę
LiveData
tasksAddViewVisible.
- Uruchom test.
Krok 2. Porównywanie testu z rozwiązaniem
Porównaj swoje rozwiązanie z rozwiązaniem przedstawionym poniżej.
TestujModelZadań
@Test
fun setFilterAllTasks_tasksAddViewVisible() {
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
// When the filter type is ALL_TASKS
tasksViewModel.setFiltering(TasksFilterType.ALL_TASKS)
// Then the "Add task" action is visible
assertThat(tasksViewModel.tasksAddViewVisible.getOrAwaitValue(), `is`(true))
}
Sprawdź, czy wykonujesz te czynności:
tasksViewModel
jest tworzony za pomocą tej samej instrukcji AndroidaApplicationProvider.getApplicationContext()
.- Wywołujesz metodę
setFiltering
, przekazując wyliczenie typu filtraALL_TASKS
. - Aby sprawdzić, czy
tasksAddViewVisible
zawiera wartość prawda, użyj metodygetOrAwaitNextValue
.
Krok 3. Dodawanie reguły @Before
Zwróć uwagę, że na początku obu testów definiujesz parametr TasksViewModel
.
TestujModelZadań
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
Jeśli kod konfiguracji jest powtarzany wielokrotnie, możesz użyć adnotacji @przed, aby utworzyć metodę konfiguracji i usunąć z niego powtarzające się kody. Ponieważ wszystkie te testy będą testować element TasksViewModel
, a potrzebują modelu widoku danych, przenieś ten kod do bloku @Before
.
- Utwórz zmienną instancji
lateinit
o nazwietasksViewModel|
. - Utwórz metodę o nazwie
setupViewModel
. - Dodaj adnotację do tekstu
@Before
. - Przenieś kod instancji modelu widoku do pola
setupViewModel
.
TestujModelZadań
// Subject under test
private lateinit var tasksViewModel: TasksViewModel
@Before
fun setupViewModel() {
tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
}
- Uruchom kod.
Ostrzeżenie
Nie wykonuj tych czynności, nie inicjuj
tasksViewModel
ze swoją definicją:
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
Spowoduje to użycie tej samej instancji we wszystkich testach. Należy tego unikać, ponieważ każdy test powinien mieć nową instancję testowanego tematu (w tym przypadku ViewView).
Ostateczny kod tagu TasksViewModelTest
powinien wyglądać tak jak poniżej.
TasksViewModelTest
@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {
// Subject under test
private lateinit var tasksViewModel: TasksViewModel
// Executes each task synchronously using Architecture Components.
@get:Rule
var instantExecutorRule = InstantTaskExecutorRule()
@Before
fun setupViewModel() {
tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
}
@Test
fun addNewTask_setsNewTaskEvent() {
// When adding a new task
tasksViewModel.addNewTask()
// Then the new task event is triggered
val value = tasksViewModel.newTaskEvent.awaitNextValue()
assertThat(
value?.getContentIfNotHandled(), (not(nullValue()))
)
}
@Test
fun getTasksAddViewVisible() {
// When the filter type is ALL_TASKS
tasksViewModel.setFiltering(TasksFilterType.ALL_TASKS)
// Then the "Add task" action is visible
assertThat(tasksViewModel.tasksAddViewVisible.awaitNextValue(), `is`(true))
}
}
Kliknij tutaj, aby zobaczyć różnice między uruchomionym kodem a ostatecznym kodem.
Aby pobrać kod ukończonych ćwiczeń z programowania, możesz użyć poniższego polecenia git:
$ git clone https://github.com/googlecodelabs/android-testing.git $ cd android-testing $ git checkout end_codelab_1
Możesz też pobrać repozytorium jako plik ZIP, rozpakować go i otworzyć w Android Studio.
Tematy te:
- Uruchamianie testów w Android Studio
- Różnica między testami lokalnymi (
test
) a testami instrumentacji (androidTest
). - Jak przygotowywać testy lokalne za pomocą JUnit i Hamcrest.
- Konfigurowanie testów ViewModel w Bibliotece testowej Androida
Kurs Udacity:
Dokumentacja dla programistów Androida:
- Przewodnik po architekturze aplikacji
- Jednostka J4
- Hacrest
- Biblioteka testowa Robo
- Biblioteka testowa Androida X
- Podstawowa biblioteka testowa komponentów AndroidX Architecture
- zbiory źródłowe
- Testowanie z poziomu wiersza poleceń
Materiały wideo:
Inne:
Linki do innych ćwiczeń z programowania znajdziesz w kursie dotyczącym programowania na Androida dla zaawansowanych w Kotlin.