You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: _drafts/2019-01-07-testy_jednostkowe.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -113,7 +113,7 @@ Projektowanie przypadków testowych oparciu o `klasy równoważności` polega na
113
113
Testowanie `przejść między stanami` analizuje dozwolone i niedozwolone przejścia między stanami aplikacji za pomocą `automatu skończenie stanowego`. Analizując diagram lub tabelę stanową można zaprojektować testy w taki sposób by pokryły wszystkie przejścia, konkretny ciąg czy testowały przejścia zabronione.
114
114
115
115
>Diagram stanów wraz z możliwymi przejściami dla kosztów dostawy ukazany jest poniżej.
Testowanie z użyciem `tabeli decyzyjnych` polega na sprawdzeniu działania jednostki testowanej w odniesieniu do kombinacji warunków wejściowych znajdujących się w tabeli.
`Android Studio` ułatwia proces testowania dzięki integracji z bibliotekami wspomagającymi testowanie oraz dostarczeniu dedykowanych narzędzi. Dodając odpowiednie zależności `AndroidX` do projektu już za pomocą kilku kliknięć można wykorzystać możliwości takich bibliotek jaki: `JUnit`, `Mockito`, `Espresso`, `Robolectric` czy `UI Automator`. Lokalne testy jednostkowe uruchamiane na maszynie lokalnej `JVM` znajdują się w lokalizacji: `moduleName/src/test/java/` natomiast instrumentalne testy jednostkowe przeznaczone do uruchamiania na urządzeniu lub emulatorze znajdują się w: `moduleName/src/androidTest/java/`. W przypadku testów instrumentalnych przeznaczonych dla różnych wariantów budowania aplikacji ścieżka zmienia się na: `moduleName/src/androidTestBuildVariantName/java/`.
13
13
14
-
//TODO photo
14
+
{: .center-image }
15
15
16
16
## Tworzenie i uruchamianie
17
17
`Testy jednostkowe` można tworzyć dodając klasy do odpowiednich folderów lub za pomocą skrótu `Ctrl+Shift+T` kierując kursor na klasę lub metodę. Uruchamianie i analiza następuje z poziomu narzędzia GUI lub konsoli w Android Studio, które umożliwia m.in. eksport wyników testów, przeglądanie statystyk oraz pokrycie kodu.
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
23
24
24
## Dobre praktyki
25
-
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ą `BDD: Given/When/Then`. 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.
25
+
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.
26
26
27
27
## Klasa testowa
28
28
Testy w JUnit (metody) zawierają się w `klasie testowej`, które z kolei mogą być częścią `zestawu klas testowych`. Aby klasa była klasą testową w JUnit4 musi zawierać deklarację przynajmniej jednej metody testowej oznaczonej adnotacją `@Test`. Podstawowym elementem testów są asercje sprawdzające wartość logiczną, równość wartości czy referencji.
29
29
30
-
//TODO klasa z jedna metoda i roznymi asercjami
30
+
{% highlight kotlin %}
31
+
class SmallTest {
32
+
33
+
@Test
34
+
fun startGameWithDifferentTeamsSize() {
35
+
val game = Game()
36
+
game.addPlayer(Game.Team.RED, "Johnnie")
37
+
game.addPlayer(Game.Team.BLUE, "Jack")
38
+
game.addPlayer(Game.Team.BLUE, "Jim")
39
+
game.start()
40
+
assertFalse(game.hasStarted())
41
+
}
42
+
}
43
+
{% endhighlight %}
31
44
32
45
## Cykl życia
33
46
Klasy testowe poza metodami testowymi mogą składać się także z metod inicjalizacyjnych i końcowych. Metoda oznaczona adnotacją `@Before` wykonywana jest przed każdym testem i służy przygotowaniu środowiska testowego, natomiast z adnotacją `@After` po każdym teście co wykorzystywane jest do czyszczenia środowiska. W analogiczny sposób działają metody oznaczone jako `@BeforeClass` oraz `@AfterClass`, które wykonują się kolejno przed i po uruchomieniu wszystkich testów. Jeśli metoda testowa ma zostać wyłączona z testów należy użyć adnotacji `@Ignore`. Adnotacja `@Test` może zostać wzbogacona o maksymalny czas wykonania (`timeout`) lub oczekiwany typ wyjątku.
34
47
35
-
//TODO klasa z calym cyklem i ignore i test
48
+
{% highlight kotlin %}
49
+
class FullTest {
50
+
51
+
//mock some dependencies
52
+
val game = Game()
53
+
54
+
@Before
55
+
fun init() {
56
+
//clear game before every test
57
+
game.clearTeams()
58
+
}
59
+
60
+
@After
61
+
fun uninit() {
62
+
//clear game before every test
63
+
game.stop()
64
+
}
65
+
//use @BeforeClass, @AfterClass in the same way
66
+
67
+
@Ignore //ignore this test temporary
68
+
fun startGameWithDifferentTeamsSize() {
69
+
game.addPlayer(Game.Team.RED, "Johnnie")
70
+
game.addPlayer(Game.Team.BLUE, "Jack")
71
+
game.addPlayer(Game.Team.BLUE, "Jim")
72
+
game.start()
73
+
assertFalse(game.hasStarted())
74
+
}
75
+
76
+
@Test(timeout = 1000) //after 1000ms when test couldn't finished than just failed
77
+
fun startAndStopGame() {
78
+
game.addPlayer(Game.Team.RED, "Johnnie")
79
+
game.addPlayer(Game.Team.RED, "William")
80
+
game.addPlayer(Game.Team.BLUE, "Jack")
81
+
game.addPlayer(Game.Team.BLUE, "Jim")
82
+
game.start()
83
+
assertTrue(game.hasStarted())
84
+
game.stop()
85
+
assertFalse(game.hasStarted())
86
+
}
87
+
88
+
@Test
89
+
fun modifyTeamsAndStartGame() {
90
+
game.addPlayer(Game.Team.RED, "Johnnie")
91
+
game.addPlayer(Game.Team.RED, "William")
92
+
game.addPlayer(Game.Team.BLUE, "Jack")
93
+
game.start()
94
+
assertFalse(game.hasStarted())
95
+
game.removePlayer("William")
96
+
game.start()
97
+
assertTrue(game.hasStarted())
98
+
}
99
+
}
100
+
{% endhighlight %}
36
101
37
102
## Parametry
38
-
Klasa testowa może posiadać także jedną metodę generującą zestawy danych dla metod testowych. Klasa ta musi być oznaczona adnotacją `@RunWith(Parameterized.class)` natomiast metoda zwracająca kolekcje danych oraz pola przyjmujące wstrzyknięte wartości są oznaczone jako `@Parameter`. Pola mogą przyjmować wstrzykniętą wartość również za pomocą konstruktora.
39
-
40
-
//TODO przyklad z parametrami
103
+
Klasa testowa może posiadać także jedną metodę generującą zestawy danych dla metod testowych. Klasa ta musi być oznaczona adnotacją `@RunWith(Parameterized.class)` natomiast metoda statyczna zwracająca kolekcje danych oznaczone jako `@Parameters`. Właściwości mogą przyjmować wstrzykniętą wartość za pomocą konstruktora lub być zdefiniowane w ciele klasy i oznaczone jako @Parameter(number).
104
+
105
+
{% highlight kotlin %}
106
+
@RunWith(Parameterized::class)
107
+
class ParameterizedTest(val redGoals: Int, val blueGoals: Int, val scores: String) {
108
+
109
+
companion object {
110
+
val game = Game()
111
+
112
+
@BeforeClass @JvmStatic
113
+
fun initTeams() {
114
+
game.addPlayer(Game.Team.RED, "Johnnie")
115
+
game.addPlayer(Game.Team.RED, "William")
116
+
game.addPlayer(Game.Team.BLUE, "Jack")
117
+
game.addPlayer(Game.Team.BLUE, "Jim")
118
+
}
119
+
120
+
@AfterClass @JvmStatic
121
+
fun uninitTeams() {
122
+
game.clearTeams()
123
+
}
124
+
125
+
//create test data
126
+
@Parameters @JvmStatic
127
+
fun createData(): Collection<Array<Any>> {
128
+
return listOf(
129
+
arrayOf(0, 2, "RED 0:2 BLUE"),
130
+
arrayOf(10, 5, "RED 10:5 BLUE"),
131
+
arrayOf(3, 3, "RED 3:3 BLUE"))
132
+
}
133
+
}
134
+
135
+
//use auto parameterized test method
136
+
@Test
137
+
fun scoreAndCheckResultWithParameterized() {
138
+
game.start()
139
+
repeat(redGoals) { game.goal(Game.Team.RED) }
140
+
repeat(blueGoals) { game.goal(Game.Team.BLUE) }
141
+
assertEquals(scores, game.getScore())
142
+
game.stop()
143
+
}
144
+
145
+
//instead of manual parameterized
146
+
@Test
147
+
fun scoreAndCheckResultNormal() {
148
+
//first test
149
+
game.start()
150
+
repeat(2) { game.goal(Game.Team.BLUE) }
151
+
assertEquals("RED 0:2 BLUE", game.getScore())
152
+
game.stop()
153
+
154
+
//second test
155
+
game.start()
156
+
repeat(10) { game.goal(Game.Team.RED) }
157
+
repeat(5) { game.goal(Game.Team.BLUE) }
158
+
assertEquals("RED 10:5 BLUE", game.getScore())
159
+
game.stop()
160
+
161
+
//third test
162
+
game.start()
163
+
repeat(3) { game.goal(Game.Team.RED) }
164
+
repeat(3) { game.goal(Game.Team.BLUE) }
165
+
assertEquals("RED 3:3 BLUE", game.getScore())
166
+
game.stop()
167
+
}
168
+
}
169
+
{% endhighlight %}
41
170
42
171
## Zasady
43
172
JUnit umożliwia dodawanie zachowania do każdego testu za pomocą adnotacji `@Rule` oraz tworzenie nowych zasad. Aby stworzyć własną zasadę należy w klasie zasady implementować interfejs `TestRule`.
44
173
45
-
//TODO przyklad z tworzeniem i uzyciem wlasnej zasady
174
+
{% highlight kotlin %}
175
+
class RuleTest {
176
+
177
+
@Rule @JvmField
178
+
val rule = PrintTestRule() //this rule print message before and after every test
179
+
180
+
val game = Game()
181
+
182
+
@Test
183
+
fun addPlayersWithSameNameToTheSameTeam() {
184
+
game.addPlayer(Game.Team.RED, "Johnnie")
185
+
game.addPlayer(Game.Team.RED, "William")
186
+
game.addPlayer(Game.Team.RED, "Jack")
187
+
game.addPlayer(Game.Team.RED, "William")
188
+
assertEquals(3, game.getRedTeam().size)
189
+
}
190
+
191
+
@Test
192
+
fun addPlayersWithSameNameToTheAnotherTeam() {
193
+
game.addPlayer(Game.Team.RED, "Johnnie")
194
+
game.addPlayer(Game.Team.RED, "William")
195
+
game.addPlayer(Game.Team.BLUE, "Jack")
196
+
game.addPlayer(Game.Team.BLUE, "William")
197
+
assertEquals(1, game.getBlueTeam().size)
198
+
}
199
+
}
200
+
201
+
class PrintTestRule : TestRule {
202
+
203
+
private lateinit var base: Statement
204
+
private lateinit var description: Description
205
+
206
+
override fun apply(base: Statement, description: Description): Statement {
207
+
this.base = base
208
+
this.description = description
209
+
return PrintTestStatement(base)
210
+
}
211
+
212
+
class PrintTestStatement(private val base: Statement) : Statement() {
213
+
override fun evaluate() {
214
+
println("Log before test action")
215
+
base.evaluate()
216
+
println("Log after testaction")
217
+
}
218
+
}
219
+
}
220
+
{% endhighlight %}
46
221
47
222
`AndroidX Test` zawiera zestaw gotowych zasad dla JUnit, które zwiększają elastyczność, redukują powtarzający się kod oraz wspomagają testowanie komponentów Android. Wykorzystywane są przede wszystkim testach UI przy użyciu `Espresso`. `ActivityTestRule` dostarcza do klasy testowej żądanej Aktywności (`Activity`), która jest dostępna w całym cyklu życia klasy testowej, `ServiceTestRule` dostarcza Usługę (`Service`) natomiast `IntentsTestRule` dostarcza Intencję (`Intent`).
48
223
49
-
## Filtry
50
-
`AndroidJUnitRunner` umożliwia stosowanie adnotacji w celach czysto informacyjnych. Adnotacja `@RequiresDevice` mówi, że test powinien zostać przeprowadzony tylko na urządzeniu fizycznym, `@SdkSupress` określa minimalne API natomiast `@SmallTest`, `@MediumTest` i `@LargeTest` mówią o wielkości testu co przekłada się na czas jego wykonania.
224
+
//TODO maybe
51
225
52
-
//TODO example
226
+
## Filtry
227
+
`AndroidJUnitRunner` umożliwia stosowanie adnotacji dla testów instrumentalnych w celach informacyjnych. Adnotacja `@RequiresDevice` mówi, że test powinien zostać przeprowadzony tylko na urządzeniu fizycznym, `@SdkSupress` określa minimalne API natomiast `@SmallTest`, `@MediumTest` i `@LargeTest` informują o wielkości testu co przekłada się na czas jego wykonania.
0 commit comments