Skip to content

Commit 88e4ddf

Browse files
committed
robolectric post updated
1 parent 02220cc commit 88e4ddf

File tree

4 files changed

+138
-3
lines changed

4 files changed

+138
-3
lines changed

_drafts/2019-01-07-testy_jednostkowe.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ keywords: "testowanie, testing, testy, jednostkowe, automatyczne, lokalne, integ
1212
`Testy jednostkowe` (`unit test`) poddają weryfikacji sposób działania różnych elementów składowych kodu takich jak `metody`, `klasy` czy `moduły`. Testy powinny być pisane w sposób `niezależny`, tzn. testowana jednostka nie jest podatna na wpływy innych elementów, w przeciwnym razie wynik testu może być niewiarygodny. Testy jednostkowe należą do `testów automatycznych` za których tworzenie i wykonywanie powinien odpowiadać programista. Warto przeprowadzać je często i regularnie przy każdym nowym przyroście dzięki czemu na wczesnym etapie tworzenia oprogramowania możliwe jest wykrycie usterki. Pozwala to na przeciwdziałanie kumulowania się błędów (jeden błąd rodzi kolejne) co w rezultacie redukuje koszty w póżniejszych etapach cyklu życia aplikacji. Tworzenie testów jednostkowych polega przede wszystkim na pisaniu `asercji` czyli porównywanie uzyskanego wyniku z oczekiwanym. Do przeprowadzania testów jednostkowych można wykorzystać np. bibliotekę `jUnit`. Przeważnie jednostki testowe wymagają do poprawnego działania obiektów różnego typu. Aby przekazywane argumenty nie wpływały na wynik testu danej jednostki (niezależne testy) należy dostarczać tzw. `atrapę` obiektu czyli naiwną implementację zależności.
1313

1414
## Testy lokalne i instrumentalne
15-
Testy (nie tylko jednostkowe), które mogą zostać wykonane na wirtualnej maszynie deweloperskiej nazywane są `testami lokalnymi`. Przeprowadzenie jednostkowych testów lokalnych charakteryzuje się niskim kosztem oraz szybkością w związku z czym powinny stanowić przeważającą część testów. Natomiast testy, które wymagają uruchomienia na fizycznym urządzeniu lub emulatorze nazywane są `testami instrumentalnymi`. Wynika to z faktu, że pewne fragmenty kodu są zależne bibliotek `Android SDK` oraz cyklu życia komponentów, którymi zarządza system. Takie testy w stosunku do testów lokalnych są dosyć kosztowne i powolne. Przykładem biblioteki realizującej instrumentalne testy jednostkowe jest `Robolectrict`.
15+
Testy (nie tylko jednostkowe), które mogą zostać wykonane na wirtualnej maszynie deweloperskiej nazywane są `testami lokalnymi`. Przeprowadzenie jednostkowych testów lokalnych charakteryzuje się niskim kosztem oraz szybkością w związku z czym powinny stanowić przeważającą część testów. Natomiast testy, które wymagają uruchomienia na fizycznym urządzeniu lub emulatorze nazywane są `testami instrumentalnymi`. Wynika to z faktu, że pewne fragmenty kodu są zależne bibliotek `Android SDK` oraz cyklu życia komponentów, którymi zarządza system. Takie testy w stosunku do testów lokalnych są dosyć kosztowne i powolne. Biblioteka `Robolectrict` umożliwia realizacje lokalnych testów jednostkowych zależnych od Android SDK co może być w wielu przypadkach alternatywą dla kosztownych testów instrumentalnych.
1616

1717
## Atrapa i zaślepka
1818
`Zaślepka` (`stub`) jest minimalną implementacją zależnego modułu używaną podczas testów innego modułu. Ma za zadanie zastępować wywoływany moduł poprzez zwracanie w prosty sposób wyniku bez dokonywania obliczeń w taki sposób, aby wykonywany test zawsze przeszedł pozytywnie. `Atrapa` (`mock`) dostarcza natomiast naiwną implementację zależności, która umożliwia rejestrowanie interakcji z implementowanym interfejsem (np. ilość wywołań i parametry) i w przeciwieństwie do zaślepki bierze udział w procesie testowania. Atrapa jest determinowana w momencie działania programu i reprezentuje instancje oczekiwanego obiektu. Posługując się fachową nomenklaturą można wyróżnić jeszcze szerszy podział obiektów zastępczych jednakże ważniejsze od teoretycznego podziału jest właściwa implementacja. Biblioteka `Mockito` ułatwia tworzenie i zarządzanie naiwnymi implementacjami.

_drafts/2019-01-14-junit.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ keywords: "testowanie, testing, testy, jednostkowe, automatyczne, lokalne, instr
2020
![Rysunek pokrycia kodu](/assets/img/diagrams/testing/junit_coverage.png){: .center-image }
2121

2222
## Motywacja
23-
Podstawą procesu testowania jest tworzenie i wykonywanie testów jednostkowych, które odpowiednio napisane w łatwy i wiarygodny sposób weryfikują poprawności logiki jednostki testowanej. Wykonywanie testów jednostkowych przy każdym przyroście ułatwia szybkie wyłapanie błędu. Należy jednak pamiętać o izolacji testowanej jednostki od pozostałych zależności. `JUnit` w Android jest wykorzystywany przede wszystkim do pisania `lokalnych testów` jednostkowych lub prostych `testów instrumentalnych` dla których można dostarczyć zależności (własne lub przy pomocy biblioteki np. Mockito). W pozostałych sytuacjach należy wykorzystać bibliotekę dostarczają zależności środowiska uruchomieniowego np. `Robolectric`. Tworzenie asercji może zostać usprawnione przez wykorzystanie `Hamcrest`. Alternatywą dla implementacji JUnit dla Android jest wykorzystanie biblioteki `Truth` we współpracy z asercjami Android.
23+
Podstawą procesu testowania jest tworzenie i wykonywanie testów jednostkowych, które odpowiednio napisane w łatwy i wiarygodny sposób weryfikują poprawności logiki jednostki testowanej. Wykonywanie testów jednostkowych przy każdym przyroście ułatwia szybkie wyłapanie błędu. Należy jednak pamiętać o izolacji testowanej jednostki od pozostałych zależności. `JUnit` w Android jest wykorzystywany przede wszystkim do pisania `lokalnych testów` jednostkowych lub prostych `testów instrumentalnych` dla których można dostarczyć zależności (własne lub przy pomocy biblioteki np. Mockito). W pozostałych sytuacjach należy wykorzystać bibliotekę dostarczają zależności środowiska uruchomieniowego np. `Robolectric`. Tworzenie asercji może zostać usprawnione przez wykorzystanie `Hamcrest`. Alternatywą dla implementacji JUnit dla Android jest wykorzystanie biblioteki `Truth` we współpracy z asercjami Android.
2424

2525
## Dobre praktyki
2626
Tworząc metody testowe należy przede wszystkim pamiętać o wykluczeniu wszelkich zależności w taki sposób, aby na wynik testu testowanej jednostki nie miały wpływu inne zależności. Klasy testowe powinny znajdować się w pakiecie o tej samej nazwie co klasy implementacji, a nazwy klas testowych powinny być podobne do klas testowanych. Metody opisowe powinny być nazywane w sposób opisowy i jednoznaczny w nawiązaniu do celu testu nawet jeśli z tego powodu nazwa metody jest długa. W tym celu można posłużyć się konwencją `Given/When/Then`, gdzie `Given` określa warunki początkowe, `When` opisuje akcje, a `Then` informuje o oczekiwanym rezultacie. Ponadto należy dążyć do minimalizacji asercji, czasu wykonywania testów oraz zwiększać pokrycie kodu. Testy powinny być krótkie, proste i ściśle dotyczyć jednej jednostki. Jeśli sytuacja tego wymaga należy wykorzystywać metody cyklu życia testów, aby zapewnić odpowiednią inicjalizację i czyszczenie środowiska.

_drafts/2019-01-21-mockito.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,4 +341,4 @@ class PowerMockTest {
341341
}
342342
{% endhighlight %}
343343

344-
Jako alternatywę dla Mockito i PowerMock w `Kotlin` warto rozważyć bibliotekę `Mockk`, która rozwiązuje problemy występujące w Mockito i wspiera natywne cechy Kotlin takie jak np.: rozszerzenia. Framework `Robolectric` umożliwi natomiast przeprowadzanie w łatwy sposób instrumentalnych testów jednostkowych.
344+
Jako alternatywę dla Mockito i PowerMock w `Kotlin` warto rozważyć bibliotekę `Mockk`, która rozwiązuje problemy występujące w Mockito i wspiera natywne cechy Kotlin takie jak np.: rozszerzenia. Framework `Robolectric` umożliwi natomiast przeprowadzanie w łatwy sposób lokalnych testów jednostkowych zależnych od Android SDK.

_drafts/2019-01-28-robolectric.md

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,138 @@ github: testing/tree/master/robolectric
88
description: "Robolectric"
99
keywords: "testowanie, testing, testy, jednostkowe, automatyczne, instrumentalne, zaślepka, atrapa, unit test, mock, stub, robolectric, android, programowanie, programming"
1010
---
11+
12+
## Przeznaczenie
13+
Uruchamianie jednostkowych testów instrumentalnych na urządzeniu lub emulatorze jest kosztowne i powolne. Jednakże w wielu sytuacjach nie sposób uciec od testowania logiki kodu powiązanego z komponentami Androida co sprawia, że testy instrumentalne są stosowane pomimo wynikających kosztów. Alternatywnym sposobem testowania kodu zależnego od `Android SDK` może być użycie framework `Robolectric`, który umożliwia przeprowadzanie `lokalnych testów` jednostkowych w środowisku wykonawyczm `JVM` na komponentach Android takich jak m.in. `Activity`, `Fragment`, `Intent`, `Service`, `ContentProvider`. W przeciwieństwie do testów instrumentalnych Robolectric wykonuje się piaskownicy systemowej (`sandbox`) co pozwala na konfiguracje środowiska Android dla wszystkich testów. Robolectric dostarcza również dublerów komponentów Android dla których nie potrafi dokonać tłumaczenia do testów jednostkowych (np. sensory hardware, usługi systemowe). Obsługuje inflację widoków, ładowanie zasobów i wiele innych aspektów zaimplementowanych w natywnym kodzie na urządzeniach co pozwala na wykonanie większości czynności jak na prawidzwym urządzeniu. Robolectric w łatwy sposób umożliwia zapewnienie własnej implementacji wybranych metod Android SDK dzięki czemu możliwa jest np. symulacja warunków czy zachowań sensorów. Wykorzystanie Robolectric jest bliższe podejściu testowania czarnoskrzynkowego (skupionego na zachowaniu) i nie wyklucza jednoczesnego stosowania innych bibliotek naiwnych implementacji takich jak np. `Mockito`.
14+
15+
## Uruchomienie
16+
Aby uruchomić test w Robolectric należy opatrzeć klasę testową adnotacją `@RunWith(RobolectricTestRunner.class)` oraz uzyskać instancję żądanej klasy komponentu.
17+
18+
{% highlight kotlin %}
19+
@RunWith(RobolectricTestRunner::class)
20+
class SimpleTest {
21+
22+
lateinit var activity: MainActivity
23+
24+
@Before
25+
fun init() {
26+
//this method provides that activity goes throw all create lifecycle
27+
activity = Robolectric.setupActivity(MainActivity::class.java)
28+
}
29+
30+
@Test
31+
fun checkViewsValue() {
32+
assertEquals("title", activity.textViewTitle.text)
33+
assertEquals("action", activity.buttonAction.text)
34+
}
35+
36+
@Test
37+
fun clickingTextViewChangeText() {
38+
activity.textViewTitle.performClick()
39+
assertEquals("clicked", activity.textViewTitle.text)
40+
}
41+
42+
@Test
43+
fun clickingButtonNavigateToSecondActivity() {
44+
activity.buttonAction.performClick()
45+
val expectedIntent = Intent(activity, SecondActivity::class.java)
46+
val actual = shadowOf(getApplicationContext() as Application).nextStartedActivity
47+
assertNotNull(actual)
48+
assertEquals(expectedIntent.component, actual.component)
49+
}
50+
}
51+
{% endhighlight %}
52+
53+
54+
## Shadow
55+
56+
57+
## Konfiguracja
58+
Aby zmienić domyślną konfiguracje testów należy oznaczyć klasy lub metody adnotacją `@Config` wraz z deklaracją konfiguracji. Metody z adnotacją `@Config` nadpisują zachowanie poziomu klasy. Konfiguracji mogą podlegać m.in. poziom `sdk`, plik `manifest`, klasy `shadows`, ścieżki do zasobów czy `kwalifikatory` (np. języka, regionu itp).
59+
60+
{% highlight kotlin %}
61+
@RunWith(RobolectricTestRunner::class)
62+
@Config(minSdk = LOLLIPOP)
63+
class ConfigurationTest {
64+
65+
lateinit var activity: MainActivity
66+
67+
@Before
68+
fun init() {
69+
//this method provides that activity goes throw all create lifecycle
70+
activity = Robolectric.setupActivity(MainActivity::class.java)
71+
}
72+
73+
@Test
74+
fun checkViewsValueOnLollipopAndAbove() {
75+
assertEquals("title", activity.textViewTitle.text)
76+
assertEquals("action", activity.buttonAction.text)
77+
}
78+
79+
@Config(minSdk = KITKAT, maxSdk = LOLLIPOP)
80+
@Test
81+
fun checkViewsValueOnMinKitkatMaxLollipop() {
82+
assertEquals("title", activity.textViewTitle.text)
83+
assertEquals("action", activity.buttonAction.text)
84+
}
85+
86+
@Config(sdk = intArrayOf(O, O_MR1, P))
87+
@Test
88+
fun checkViewsValueOnlyOnOreoAndPie() {
89+
assertEquals("title", activity.textViewTitle.text)
90+
assertEquals("action", activity.buttonAction.text)
91+
}
92+
93+
@Config(qualifiers = "pl")
94+
@Test
95+
fun checkViewsValueInPolish() {
96+
assertEquals("tytuł", activity.textViewTitle.text)
97+
assertEquals("akcja", activity.buttonAction.text)
98+
}
99+
100+
@Config(qualifiers = "xhdpi")
101+
@Test
102+
fun checkViewsValueOnXhdpi() {
103+
assertEquals(32, activity.buttonAction.paddingTop)
104+
}
105+
}
106+
{% endhighlight %}
107+
108+
## Cykl życia
109+
`ActivityController` odpowiada za tworzenie oraz zarządzanie cyklem życia tworzonej Aktywności. Metoda `buildActivity` tworzy instancję `ActivityController`, która umożliwia wywołanie wybranych metod cyklu życia oraz pobranie instancji Aktywności. Aby bezpośrednio uzyskać obiekt Aktywności z przebytym pełnym cyklem tworzenia należy wywołać metodę `setupActivity`.
110+
111+
{% highlight kotlin %}
112+
@RunWith(RobolectricTestRunner::class)
113+
class LifecycleTest {
114+
115+
@Test
116+
fun checkMainActivityHasWorkingFinishLifecycle() {
117+
//this activity goes throw all create lifecycle
118+
val controller = Robolectric.buildActivity(MainActivity::class.java)
119+
val activity = controller.create().start().resume().visible().get()
120+
//equivalent of val activity = Robolectric.setupActivity(MainActivity::class.java)
121+
//visible methods assures that activity view's is attached
122+
123+
assertFalse(activity.isFinishing)
124+
activity.finish()
125+
assertTrue(activity.isFinishing)
126+
}
127+
128+
@Test
129+
fun checkMainActivityHasWorkingLifecycle() {
130+
val controller = Robolectric.buildActivity(MainActivity::class.java)
131+
val activity = controller.create().get()
132+
133+
assertEquals(Lifecycle.State.CREATED, activity.lifecycle.currentState)
134+
//do more assertion for this state
135+
controller.start()
136+
assertEquals(Lifecycle.State.STARTED, activity.lifecycle.currentState)
137+
//do more assertion for this state
138+
controller.resume()
139+
assertEquals(Lifecycle.State.RESUMED, activity.lifecycle.currentState)
140+
//do more assertion for this state
141+
controller.pause().stop().destroy()
142+
assertEquals(Lifecycle.State.DESTROYED, activity.lifecycle.currentState)
143+
}
144+
}
145+
{% endhighlight %}

0 commit comments

Comments
 (0)