Software testing, especially unit testing, is where it is very common for a tester to need to test a class or just one method of the class, without having the various dependencies for that class or method available. This project shows how to use the mocking frameworks on an Android application.
4. Capitolo 1
Mocking Framework
Nel testing del software, specialmente nel testing di unità, è molto frequente che un tester ha
bisogno di testare una classe o solo un metodo della classe, senza avere a disposizione le varie
dipendenze per quella classe o metodo. I motivi per i quali non si hanno disponibili le dipendenze
sono dovuti al mancato completamento di quella dipendenza durante lo sviluppo del software, un
alto costo nell’invocare quella dipendenza (e.g. web service di terze parti, rete lenta, database), e
anche la potenziale interferenza dei bug delle dipendenze nel nostro test di unità.
A questo punto ci vengono in aiuto i Mock Object che nel testing di unità, e soprattutto nel
testing in isolamento, ci permettono di simulare in modo controllato un oggetto reale. Per fare ciò
esistono metodi più “artigianali” come ad esempio estrarre un’interfaccia rispetto alla dipendenza
da sostituire e far implementare i metodi ad una classe “finta” (Mock). Mentre se vogliamo creare
un Mock parziale della classe, la possiamo estendere e fare i Mock solo dei metodi che ci interessano.
Poi in entrambi i casi dobbiamo iniettare il Mock durante il testing.
Un’altra strada è quella di utilizzare un framework che permette di fare le operazioni espresse
sopra in maniera più comoda e veloce. In Java esistono diversi framework come: EasyMock,
JMock, Mockito, PowerMock, ecc.
In questo elaborato vengono analizzati i Mock in Android. Sappiamo bene che il linguag-
gio utilizzato per programmare Android è Java e quindi possiamo usare uno dei framework
precedentemente citati. In questo elaborato verranno analizzati:
• Mockito;
• PowerMock.
1.1 L’uso dei Mocking framework
Come accennato in precedenza esistono diversi framework per utilizzare i mock. Potrebbe risultare
interessante conoscere il framework più usato, in che modo i tester utilizzano questi framework,
e altro ancora. Molte di queste domande vengono empiricamente risposte nella pubblicazione di
Shaikh Mostafa, Xiaoyin Wang7. In questa pubblicazione viene eseguito uno studio statistico su
2046 progetti open source sviluppati in java presenti su GitHub. In realtà i progetti scaricati erano
5000, ma solo 2046 avevano almeno una classe di test.
Le domande che si pone lo studio sono le seguenti:
• Qual è il più popolare mocking framework utilizzato nei progetti open source?
1
5. CAPITOLO 1. MOCKING FRAMEWORK
• Quali sono le caratteristiche dei mocking framework più utilizzate in questi progetti?
• Per quali tipi di dipendenze i tester generano mock?
Figura 1.1: Popolarità Mocking Framework
Come riportato nella fig. 1.1, vediamo che è Mockito il framework più utilizzato.
Per quanto riguarda la seconda risposta, nella pubblicazione vengono riportate le features più
usate per ogni Mocking framework, per brevità riporto solo i dati rispetto a Mockito.
Figura 1.2: Le API di Mockito più utilizzate
La tabella in figura 1.2 mostra che i tester non usano Mockito solo per generare semplici stub
o mock object, ma usano features come “verify” che permette di verificare l’invocazione di una
certa dipendenza, oppure “spy” che permette di realizzare un mock parziale di una classe (vedi
2.2). Quindi vengono utilizzate features che sono il cuore di questi framework.
Tesina di IS2: Mock in Android 2
6. CAPITOLO 1. MOCKING FRAMEWORK
La terza risposta, forse la più interessante, fa notare che lo studio empirico ha scoperto che il
39% di tutte le classi per le quali si è creato un mock sono classi di libreria, i restanti mock object
sono utilizzati per simulare classi che durante lo sviluppo non erano ancora state create. Quindi si
conclude che la maggiore ragione per la quale il tester usa i mock potrebbe essere lo sviluppo del
codice in parallelo (cioé più sviluppatori che si concentrano sul proprio modulo in parallelo) e non
quello di testare il codice in isolamento.
1.2 User Interface Test e Unit Test
Esistono diverse classificazioni dei test, ma in particolare nei software con interfaccia grafica, come
ad esempio le applicazioni Android, il test della GUI si ritiene spesso un’attività fondamentale.
In questo elaborato ci siamo concentrati sul test di unità combinato all’uso dei mock, proprio per
questo motivo vengono sinteticamente riportate le differenze rispetto all’User Interface Testing
valutando diversi punti di vista. Notiamo che per sviluppare codice di qualità nessuno dei due
approcci dovrebbe sostituire l’altro, ma dovrebbero essere complementari, anzi combinati ancora
con altri tipi di testing.
Figura 1.3: UI Test e Unit Test (Piramide)
I test dell’interfaccia utente, ad esempio su app Android, sono lanciati sull’emulatore oppure sul
dispositivo fisico. Questo richiede maggiore tempo di esecuzione dei test e anche di maggiori risorse
computazionali se ad esempio lanciamo più Monkey test in parallelo sulle GUI. Però possiamo
fare testing di sistema guidato dalla GUI, ad esempio su un dispositivo o emulatore. Quest’ultimi
Tesina di IS2: Mock in Android 3
7. CAPITOLO 1. MOCKING FRAMEWORK
potrebbero essere più rilevanti per il committente. Alcuni difetti potremmo scoprirli solo eseguendo
l’applicazione e facendo generare codice dinamico relativo alla GUI.
Rifacendoci a quanto mostrato in figura 1.3, quando fallisce uno dei test non è immediatamente
chiaro dove risiede il problema, dato che il test potrebbe potenzialmente utilizzare l’intero sistema
e quindi l’errore potrebbe essere ovunque nell’applicazione e non necessariamente nell’interfaccia.
Testando la GUI è molto difficile raggiungere le parti più remote di un software, cosa che invece
riesce a fare benissimo il test di unità che appunto si focalizza su un singolo metodo. Ovviamente
per raggiungere questo obiettivo bisognerà sviluppare test di unità per ogni metodo e/o classe.
Questo porta ad un numero maggiore di test rispetto ai test della GUI, come evidenziato alla base
della fig. 1.3.
1.3 Unit Test in Android
Il testing di unità in Android può essere effettuato in due modi1
:
• Local Unit Test: Il test è compilato per eseguire sulla Java Virtual Machine (JVM) locale e
questo minimizza il tempo di esecuzione. Il test di unità eseguito in questo modo non deve
avere dipendenze con l’Android framework oppure per queste dipendenze dobbiamo usare i
Mock Object.
• Istrumented tests: Questo test di unità esegue sul dispositivo fisico o sull’emulatore. In
questo caso possiamo avere accesso alle “instrumentation information”, come ad esempio il
Context. Questo è un approccio da usare se ad esempio è difficile creare i mock per le
dipendenze del framework Android.
1.3.1 Local Unit test
I test di unità in locale sono un approccio più efficiente, perché evitano l’overhead di caricare l’app
target e il codice di test nell’emulatore o il dispositivo fisico. Questo porta ad una riduzione del
tempo necessario al test di unità.
I test di unità sono eseguiti su una versione modificata della libreria Android.jar , che non
contiene il vero codice android.
Questo ci assicura che testiamo solo il nostro codice, quindi che non dipende dal particolare
comportamento della piattaforma android. Per essere sicuri che testiamo solo il nostro codice,
quando usiamo la versione modificata di Android.jar, qualsiasi metodo che usa una API di Android
causa eccezione (nel caso in cui non abbiamo creato un Mock per questa dipendenza).
1
Building Effective Unit Tests
Tesina di IS2: Mock in Android 4
8. Capitolo 2
Mockito
Mockito è un framework open source per creare Mock nel test di unità automatizzato in ambiente
Java. Quest’ultimo è il framework consigliato su developer.android.com e per il quale Google
mette a disposizione del codice di esempio. Può essere utilizzato insieme a JUnit4 che è spesso già
presente nell’ IDE Android Studio.
Figura 2.1: Logo di Mockito
2.1 Configurazione con Android Studio
Per utilizzare Mockito in Android Studio ci basta dichiarare la dipendenza di “mockito-core” con
Gradle. Bisogna aggiungere al file build.gradle la seguente linea di codice:
1 testCompile ’org.mockito:mockito-core:2.4.0’
Codice Componente 2.1: Mockito Dependency
Notiamo che verrà utilizzata la versione 2.4.0, che non è la più recente ma ci permette di
utilizzare Mockito insieme a PowerMock1
, un altro framework che verrà analizzato in seguito.
1
Usare PowerMock insieme a Mockito
5
9. CAPITOLO 2. MOCKITO
2.2 Utilizzo di Mockito insieme a JUnit4
Quando istanziamo la classe di test bisogna aggiungere una annotazione
”@RunWith(MockitoJUnitRunner.class)” all’inizio della classe di test. Questa annotazione
valida il corretto uso del framework e semplifica l’inizializzazione degli oggetti mock.
Per creare un oggetto Mock al fine di risolvere una certa dipendenza si usa “@Mock”.
1 @Mock
2 private ClasseMock oggettoMock;
Codice Componente 2.2: Mockito: @Mock
Un’altra potente annotazione è “@Spy” che permette di creare quello che potremmo definire un
mock parziale, poiché se non viene creato un fake per un metodo allora verrà chiamato il metodo
reale. Si suppone che si lasci utilizzare il metodo reale, quando ci fidiamo del suo comportamento
o quando è un codice di libreria affidabile. Se il fake per la classe spiata viene realizzato allora
verrà richiamato quest’ultimo e non il reale.
1 @Spy
2 private ClasseSpy oggettoSpy;
Codice Componente 2.3: Mockito: @Spy
2.3 Mockito method
Le API di Mockito sono innumerevoli 2
, in questa sotto sezione vengono descritte quelle usate nei
test svolti sull’App Android realizzata dall’autore di questo elaborato. Notiamo che queste API
potranno essere usate anche con PowerMock.
Iniziamo con when e thenReturn, che possono essere utilizzati per creare velocemente un fake
di un metodo.
1 when(oggettoMock.metodoFake(param1,param2)).thenReturn(valoreDiRitorno);
Codice Componente 2.4: Mockito: when thenReturn
Analizziamo le parole chiave:
• when: vuole come parametro il metodo dell’oggetto mock per il quale vogliamo creare il fake,
richiamato con la “dot notation” come sopra;
• thenReturn: ha come parametro valoreDiRitorno, l’oggetto o il valore della variabile di ritorno
(e.g. valoreDiRitorno può essere un intero, oppure l’oggetto di una classe).
Analizziamo ora doNothing che viene utilizzato per i metodi void per fare in modo che l’invocazione
di quel metodo non abbia nessun effetto:
1 doNothing().when(oggettoMock).metodoOggetto(param);
Codice Componente 2.5: Mockito: doNothing thenReturn
2
La documentazione ufficiale della libreria di Mockito https://static.javadoc.io/org.mockito/mockito-
core/2.7.9/org/mockito/Mockito.html
Tesina di IS2: Mock in Android 6
10. CAPITOLO 2. MOCKITO
Con questo codice quando oggettoMock.metodoOggetto(param) verrà richiamato, non verrà fatta
nessuna operazione. Notiamo che abbiamo specificato un parametro preciso e quindi quella API
funzionerà solo quando il metodo avrà in ingresso quel parametro.
1 anyInt();
2 ...
3 any(ClasseCustom.class);
4
5 when(oggettoMock.metodoFake(anyInt(),anyString())).thenReturn(
valoreDiRitorno);
Codice Componente 2.6: Mockito: any
In questo caso e nel precedente al posto di un parametro specifico possiamo usare anyInt(),
anyString(). Ad esempio anyInt() che per qualsiasi intero viene passato per parametro allora fai
una certa cosa. Nel caso di classi custom si può usare any(ClasseCustom.class), quindi per qualsiasi
oggetto della classe ClasseCustom.
In alcune situazioni, durante il test di unità, potremmo essere interessati a conosce il valore
dei parametri con cui è stato chiamato un metodo, sia esso con valore di ritorno oppure void. Per
catturare questi valori si usa il seguente codice:
1 ArgumentCaptor<String> argumentCaptor = ArgumentCaptor.forClass(String.class
);
2 //dopo aver lanciato il metodo da testare
3 verify(oggettoMock).getSomeThing(argumentCaptor.capture());
4 Assert.assertEquals("Parametro inatteso", "gioacchino", argumentCaptor.
getValue());
Codice Componente 2.7: Mockito: ArgumentCaptor
Analizziamo il codice:
• ArgumentCaptor: è una classe di Mockito che permette di catturare i valori passati per
parametro ad un metodo. Questo avviene utilizzando argumentCaptor.capture() al posto dei
parametri della firma del metodo (vedi riga 3);
• Con assertEquals verifichiamo se il parametro inserito durante l’esecuzione corrisponde a
quello desiderato, per farci ritornare il valore catturato si utilizza argumentCaptor.getValue().
Con verify possiamo conteggiare il numero di volte che un metodo viene richiamato all’interno del
nostro test di unità. Nei commenti viene riportato cosa fa ogni metodo.
1 Mockito.times(int n); //esattamente n volte
2 Mockito.atLeastOnce(); //almeno una volta
3 Mockito.atLeast(int n); //almeno n volte
4 Mockito.atMost(int n) //più di n volte
5 //sono usati insieme a:
6 verify(oggettoMock, Mockito.times(n)).getSomeThing(anyInt());
Codice Componente 2.8: Mockito: times()
Tesina di IS2: Mock in Android 7
11. CAPITOLO 2. MOCKITO
Con il codice della riga 6 possiamo verificare se getSomeThing(...) viene chiamato n volte.
Nel caso in cui questo non avviene Mockito lancia delle eccezioni autoesplicative che potremmo
catturare con un try catch e poi asserire un fallimento con JUnit, come nel seguente codice. Tutte
queste eccezioni sono di tipo MockitoAssertionError.
1 try {
2 verify(sensorManagerMock, Mockito.times(1)).unregisterListener(any(
SensorEventListener.class));
3 }catch (MockitoAssertionError e){
4 Assert.fail("Il metodo non è stato chiamato una sola volta, oppure senza
oggetto SensorEventListener");
5 }
Codice Componente 2.9: Mockito: MockitoAssertionError
2.4 Hello world!
Iniziamo analizzando il banale test di unità di checkAnswer() in isolamento. Descriviamo il caso
di test:
Figura 2.2: Descrizione Test Case
Il codice:
1 /** Questo metodo dato il valore intero della risposta scelta controlla che
la risposta sia corretta.
2 * @param chooseAnswer il valore intero della risposta scelta
3 * @return un intero che vale: 0->risposta esatta, 1->risposta sbagliata
4 * */
5 public int checkAnswer(int chooseAnswer){
6 //Se la risposta è corretta
Tesina di IS2: Mock in Android 8
12. CAPITOLO 2. MOCKITO
7 if(chooseAnswer == arrayListQuestion.get(currentQuestionNumber).
getCorrectIndex()){
8 userCorrectAnswer++;
9 return 0;
10 }else{
11 return 1;
12 }
13 }
14
15 @RunWith(MockitoJUnitRunner.class)
16 public class QuizLogicTest {
17 private QuizLogic quizLogic;
18 @Mock
19 private Question question;
20 @Mock
21 private ArrayList<Question> arrayListQuestion;
22 @Before
23 public void setUp() throws Exception {
24 quizLogic = QuizLogic.getIstance();
25 }
26 @After
27 public void tearDown() throws Exception {
28 quizLogic = null;
29 }
30
31 @Test
32 public void test_checkAnswer() throws Exception {
33 int chooseAnswer = 2;
34 when(question.getCorrectIndex()).thenReturn(chooseAnswer);
when(arrayListQuestion.get(anyInt())).thenReturn(question);
35 arrayListQuestion.add(question);
36 quizLogic.setArrayListQuestion(arrayListQuestion);
37 Assert.assertEquals("Non ritorna 0 quando le risposte sono uguali", 0,
quizLogic.checkAnswer(chooseAnswer));
38 }
39 }
Codice Componente 2.10: Test checkAnswer
Viene creato un oggetto Mock della classe Question e di un’ArrayList di Question. Que-
sto test ha lo scopo di coprire una delle decisioni dell’if della funzione checkAnswer, men-
tre nel codice completo QuizLogicTest.java troviamo i test per coprire tutte le decisioni.
Per fare ciò con Mockito possiamo creare una fake all’interno della classe Question con
when(question.getCorrectIndex()).thenReturn(chooseAnswer);
Con questo codice quando question.getCorrectIndex() verrà chiamato nel metodo ritornerà il
valore chooseAnswer da noi settato.
Notiamo che abbiamo creato un Mock anche della classe java.util.ArrayList. Per qualsiasi
intero “anyInt”, quando richiameremo il metodo get() della classe ArrayList verrà restituito sempre
l’oggetto Mock di Question.
Tesina di IS2: Mock in Android 9
13. CAPITOLO 2. MOCKITO
Successivamente il Mock arrayListQuestion viene passato all’oggetto reale QuizLogic ovvero la
classe sotto test. La verifica viene effettuata con un Assert di JUnit.
In figura possiamo apprezzare la copertura:
Figura 2.3: Coverage
2.5 Mock di java.util.Random
Il seguente è uno dei diversi test utilizzato per coprire le decisioni del metodo getIndexEliminate-
dAnswer(). La particolarità di questo metodo è che usa un numero random per scegliere la doman-
da da eliminare, quindi in teoria questo lo porta a non poter essere testato deterministicamente.
Per questo motivo viene creato un Mock della classe Random.
Descriviamo il caso di test:
Figura 2.4: Descrizione Test Case
Il codice:
1 /** Questo metodo genera randomicamente un numero che è l’indice di una
domanda sbagliata
2 * @return un intero contenente l’indice di una domanda sbagliata */
3 public int getIndexEliminatedAnswer(){
4 //logica per eliminare randomicamente una risposta
5 if(randInt == null){
Tesina di IS2: Mock in Android 10
14. CAPITOLO 2. MOCKITO
6 randInt = new Random();
7 }
8 int eliminatedAnswer = randInt.nextInt(4);
9 if(arrayListQuestion.get(currentQuestionNumber).getCorrectIndex() ==
eliminatedAnswer)
10 {
11 if (arrayListQuestion.get(currentQuestionNumber).getCorrectIndex() == 0)
{
12 eliminatedAnswer++;
13 }else if (arrayListQuestion.get(currentQuestionNumber).getCorrectIndex()
== 3) {
14 eliminatedAnswer--;
15 }else{
16 eliminatedAnswer++;
17 }
18 }
19 return eliminatedAnswer;
20 }
21
22 @Mock
23 private Random randInt;
24
25 @Test
26 public void getIndexEliminatedAnswer() throws Exception {
27 //caso in cui non si deve cancellare la risposta corretta, ma sceglierne
un’altra
28 //inizializzazione
29 when(randInt.nextInt(4)).thenReturn(1);
30 when(question.getCorrectIndex()).thenReturn(1);
31 when(arrayListQuestion.get(anyInt())).thenReturn(question);
32 arrayListQuestion.add(question);
33 quizLogic.setArrayListQuestion(arrayListQuestion);
34 quizLogic.setRandInt(randInt);
35 //assert
36 Assert.assertNotEquals("Elimina una risposta corretta",quizLogic.
getIndexEliminatedAnswer(),1);
37 }
Codice Componente 2.11: Test getIndexEliminatedAnswer
La tecnica utilizzata è analoga a quella del precedente esempio. Con
when(randInt.nextInt(4)).thenReturn(1); scegliamo noi il valore che il metodo nextInt(4)
deve dare in uscita. Successivamente settiamo l’oggetto RandInt della classe QuizLogic. Con
Assert.assertNotEquals verifichiamo che non sia stata eliminata la risposta corretta.
In figura possiamo apprezzare la copertura:
Tesina di IS2: Mock in Android 11
15. CAPITOLO 2. MOCKITO
Figura 2.5: Coverage
2.6 Lanciare eccezioni
Il framework Mockito non si limita a creare mock objects e fake dei metodi di una classe, ma può
anche “lanciare” un’eccezione quando viene eseguita una certa istruzione, e.g. viene invocato un
metodo.
La sintassi è la seguente:
1 doThrow(new NullPointerException()).when(mDatabaseReference).child(anyString
());
Codice Componente 2.12: doThrow
Questo codice impone che quando verrà invocato il metodo child() dell’oggetto mDatabaseRefe-
rence, qualsiasi sia la stringa passata per parametro “anyString()” verrà lanciata l’eccezione Null-
PointerException(); In pratica riusciamo ad iniettare un’eccezione durante l’esecuzione del nostro
codice. Questo può essere utile per verificare se il nostro codice gestisce le eccezioni correttamente.
Vediamo adesso un esempio completo:
1 public void initQuiz(){
2 quizListGenericTypeIndicator =new GenericTypeIndicator<List<Quiz>>() {};
3 DatabaseReference mDatabaseReference=null;
4 DatabaseReference mQuestionReference=null;
5 try{
6 mDatabaseReference = mFirebaseDatabase.getReference();
7 mQuestionReference = mDatabaseReference.child("available_quiz");
8 }catch (NullPointerException e){
9 e.printStackTrace();
10 Log.e(FIREBASE_DB, "initQuiz(): NullPointerException");
Tesina di IS2: Mock in Android 12
16. CAPITOLO 2. MOCKITO
11 firebaseConnected = false;
12 }catch (DatabaseException e){
13 e.printStackTrace();
14 Log.e(FIREBASE_DB, "initQuiz(): DatabaseException");
15 DatabaseError databaseError = DatabaseError.fromException(e);
16 Log.e(FIREBASE_DB, "initQuiz(): databaseError.getMessage():"+
databaseError.getMessage());
17 firebaseConnected = false;
18 }
19 if(firebaseConnected){
20 mQuestionReference.addValueEventListener(new ValueEventListener() {
21 /*onDataChange() viene chiamato la prima volta che si attacca il
listener e poi ogni
22 * volta che cambiano i dati*/
23 @Override
24 public void onDataChange(DataSnapshot dataSnapshot) {
25 ...//vedi codice completo
26 }
27 @Override
28 public void onCancelled(DatabaseError databaseError) {
29 ...
30 }
31 });
32 }
33 }
Codice Componente 2.13: Firebase initQuiz
Descrizione del caso di test:
Figura 2.6: Descrizione Test Case
Tesina di IS2: Mock in Android 13
17. CAPITOLO 2. MOCKITO
1 //Codice di test
2 FireBaseDb fireBaseDb;
3 @Mock
4 private FirebaseDatabase mFirebaseDatabase;
5 @Mock
6 private DatabaseReference mDatabaseReference;
7
8 @Before
9 public void setUp() throws Exception {
10 fireBaseDb = FireBaseDb.getIstance();
11 }
12 @After
13 public void tearDown() throws Exception {
14 }
15 //Nei seguenti due test mi pongo come obiettivo di coprire tutte le
eccezioni
16 @Test
17 public void initQuizTest2() throws Exception {
18 //viene soppressa l’invocazione dei metodi di Log
19 PowerMockito.suppress(PowerMockito.method(Log.class,"e",String.class,
String.class));
20 PowerMockito.suppress(PowerMockito.method(Log.class,"v",String.class,
String.class));
21
22 fireBaseDb.setFirebaseConnected(true);
23 String e = "DummyString";
24 doThrow(new DatabaseException(e)).when(mDatabaseReference).child(anyString
());
25 //iniettiamo un’eccezione
26 when(mFirebaseDatabase.getReference()).thenReturn(mDatabaseReference);
fireBaseDb.setmFirebaseDatabase(mFirebaseDatabase);
27 fireBaseDb.initQuiz();
28 Assert.assertFalse("firebaseConnected risulta erroneamente true",
fireBaseDb.isFirebaseConnected());
29 }
Codice Componente 2.14: Firebase initQuiz Test
Il codice del metodo initQuiz() di FireBaseDB.java prevede che se non esiste l’oggetto Fire-
baseDatabase oppure c’è un errore di tipo DatabaseError (che potrebbe invocare un’eccezione di
tipo DatabaseException 3
) allora non possiamo istanziare il listener.
Con il codice when(mFirebaseDatabase.getReference()).thenReturn(mDatabaseReference); si
impone di ritornare l’oggetto mock mDatabaseReference che lancerà un’eccezione quando il meto-
do child(...) verrà chiamato. Quindi l’API di Mockito doThrow() è molto utile quando vogliamo
testare come vengono gestite le eccezioni previste o non previste.
Anche se verrà trattato nel dettaglio in seguito, notiamo che per risolvere la dipendenza di Log
si è deciso di sopprimere il metodo con PowerMock (vedi 3.5).
3
DatabaseException
Tesina di IS2: Mock in Android 14
18. CAPITOLO 2. MOCKITO
In figura possiamo apprezzare la copertura:
Figura 2.7: Coverage
2.7 Vantaggi e svantaggi
Riepilogando, i vantaggi di Mockito sono:
• fornisce un enorme quantitativo di API per testare classi e metodi pubblic;
• la documentazione java è ben fatta ed è esaustiva, inoltre è possibile trovare molti riferimenti
in letteratura;
• è un framework molto utilizzato in Android, infatti il suo uso è consigliato su
developer.android.com.
Svantaggi:
• Il vero limite di questo framework è il non poter accedere a metodi privati o attributi privati
di una classe;
• Non è possibile creare un fake di un metodo static;
• Può portare alla modifica del codice per scopi di testing, e.g. costringe a creare getter e
setter per accedere ad attributi privati della classe da testare (come nei casi di test descritti).
Per risolvere questi problemi è possibile utilizzare un altro framework che si chiama PowerMock,
che si basa su Mockito, ma ne estende le funzionalità.
Tesina di IS2: Mock in Android 15
19. Capitolo 3
PowerMock
Spesso per scrivere test di unità si incontrano difficoltà e a volte dobbiamo sacrificare un buon
design al solo scopo del testing. Un esempio è rendere pubblici attributi o metodi che altrimenti
con JUnit e altri Mock framework non potremmo raggiungere. Altri casi sono relativi al fatto di
non poter usare metodi o classi final, metodi statici. Una soluzione a questi problemi è PowerMock!
Figura 3.1: Logo di PowerMock
PowerMock è un framework che estende altre librerie di mock come Mockito ed EasyMock ma
aggiunge maggiori capacità. PowerMock usa un custom classloader1
e bytecode manipulation per
consentire il mocking dei metodi statici, costruttori, classi final, metodi privati ed altro ancora.
Quindi possiamo testare il nostro codice senza modificarlo grazie al fatto che gli oggetti mock
vengono agganciati al codice da testare a tempo di esecuzione. Sfruttando la reflection questo
framework permette di modificare anche attributi privati della classe.
1
Java permette di costruire applicazioni estendibili dinamicamente, nel senso che un’applicazione è in grado
di caricare, a tempo di esecuzione, nuovo codice e di eseguirlo, incluso del codice che nemmeno esisteva quando
l’applicazione è stata scritta.
Java infatti effettua il caricamento dinamico delle classi, cioè carica le informazioni relative alle classi durante
l’esecuzione del programma. Di questo caricamento si occupa un oggetto ClassLoader, una classe messa a disposi-
zione da Java. Un ClassLoader si occupa quindi di importare i dati binari che definiscono le classi (e le interfacce)
di un programma in esecuzione.
16
20. CAPITOLO 3. POWERMOCK
3.1 Configurazione con Android Studio
PowerMock sembra avere uno sviluppo meno veloce di Mockito, quindi non lo possiamo usare
insieme all’ultima versione di Mockito. Nulla di insormontabile, ma è importante conoscere questo
aspetto per non incorrere in strane eccezioni durante i test. Nella tabella in fig. 3.2 vengono
riportate le versioni di Mockito supportate dalle varie versioni di PowerMock.
Figura 3.2: Versioni supportate
In questo elaborato è stata utilizzata la versione 1.7.0RC2 di PowerMock, quindi il codice da
inserire in build.gradle è il seguente:
1 testCompile ’junit:junit:4.12’
2 testCompile ’org.mockito:mockito-core:2.4.0’
3 testCompile ’org.powermock:powermock-core:1.7.0RC2’
4 testCompile ’org.powermock:powermock-module-junit4:1.7.0RC2’
5 testCompile ’org.powermock:powermock-api-mockito2:1.7.0RC2’
Codice Componente 3.1: PowerMock Dependency
Le ultime due dipendenze servono per usare PowerMock insieme a JUnit e per sfruttare le API
offerte da Mockito versione 2.
Tesina di IS2: Mock in Android 17
21. CAPITOLO 3. POWERMOCK
3.2 Utilizzo di PowerMock
Per esplicitare l’utilizzo di PowerMock insieme a JUnit è possibile utilizzare le annotazioni.
1 @RunWith(PowerMockRunner.class)
2 @PrepareForTest({ QuizActivity.class, Toast.class})
3
4 Button mockBtnAnswer0 = mock(Button.class);
Codice Componente 3.2: PowerMock Base
La prima annotazione dice a JUnit di eseguire il testing insieme a PowerMock. La seconda
annotazione dice a PowerMock di preparare le classi per il testing. In pratica si indicano le classi
per le quali vogliamo manipolare il bytecode. Questa annotazione è richiesta se vogliamo creare
mock di classi final o classi con metodi privati, final o static.
Con il codice nella quarta riga esprimiamo la volontà di creare un oggetto mock della classe
Button (Android Framework).
In generale nell’annotazione @PrepareForTest va inserita la classe classe da testare, mentre con
mock()2
creiamo l’oggetto mock per simulare una dipendenza della classe sotto test.
3.3 Whitebox
Whitebox è una classe di PowerMock che mette a disposizione diversi metodi statici per bypassare
l’incapsulamento. Questa classe sfrutta la reflection di Java. I metodi statici sono i seguenti:
• Con Whitebox.setInternalState(..) possiamo settare un attributo privato della classe;
• Con Whitebox.getInternalState(..) possiamo recuperare un oggetto oppure un valore di un
attributo della classe.
• Con Whitebox.invokeMethod(..) possiamo invocare un metodo privato dell’oggetto di una
classe
• Con Whitebox.invokeConstructor(..) possiamo creare un’istanza di una classe con costruttore
privato.
Analizziamo nel dettaglio i primi tre.
1 Whitebox.setInternalState(tested,"nomeattributoclasse", valore)
Codice Componente 3.3: PowerMock: setInternalState
Come parametri abbiamo:
• tested: che è la classe che stiamo testando;
• nomeattributoclasse: è il nome dell’attributo privato che vogliamo settare;
2
Facciamo attenzione ad importare import static org.powermock.api.mockito.PowerMockito.mock; e non import
org.mockito.Mock; per evitare eccezioni a RunTime.
Tesina di IS2: Mock in Android 18
22. CAPITOLO 3. POWERMOCK
• valore: è il valore di quell’attributo della classe, notiamo che se è un semplice int passiamo
un valore numerico, mentre se è un’altra classe possiamo passare un mock object di quella
classe.
1 valoreDiRitorno = Whitebox.getInternalState(tested,"nomeattributoclasse")
Codice Componente 3.4: PowerMock: getInternalState
Come parametri abbiamo:
• tested: che è la classe che stiamo testando;
• nomeattributoclasse: è il nome dell’attributo privato che vogliamo recuperare;
• valoreDiRitorno: è il valore di quell’attributo della classe, restituito dal metodo ge-
tInternalState(), che può essere un semplice valore intero oppure il riferimento di un
oggetto.
Questi metodi sono stati utilizzati per testare il metodo onSensorChange() della classe
SensorEventListener.
1 Whitebox.invokeMethod(tested, "metodo", param...);
Codice Componente 3.5: PowerMock: invokeMethod
Come parametri abbiamo:
• tested: che è la classe che stiamo testando;
• metodo: è il nome del metodo privato o protected che vogliamo invocare;
• param...: possiamo inserire uno o più parametri in base alla firma del metodo.
3.4 Mock e Spy
Quando viene usato PowerMock gli oggetti mock e spy devono essere creati in modo diverso rispetto
a Mockito.
1 //Mock ClasseProva
2 ClasseProva oggettoMock = mock(ClasseProva.class);
3 //Spy ClasseProva
4 ClasseProva oggettoSpy = PowerMockito.spy(new ClasseProva());
Codice Componente 3.6: PowerMock: mock e spy
In quanto a funzionalità sono analoghe a quelle già spiegate per Mockito.
Notiamo che Spy invoca la versione reale solo dei metodi implementati in quella classe e non
invoca la vera implementazione dei metodi della classe padre, a meno di Override.
Tesina di IS2: Mock in Android 19
23. CAPITOLO 3. POWERMOCK
3.5 PowerMockito Method
In seguito vedremo diversi metodi offerti da PowerMock. Notiamo che esistono dei metodi che
sono propriamente di PowerMock, come ad esempio mock(Classe.class). Ma esistono metodi di
PowerMock con lo stesso nome dei metodi di Mockito. Questi metodi sono di PowerMockito che è
una classe che estende le funzionalità di Mockito con nuove features come ad esempio il mocking
di metodi privati o statici. Notiamo che, come consigliato dalla documentazione di PowerMock,
anche se i metodi hanno lo stesso nome di Mockito è necessario usare PowerMockito dove appli-
cabile. Questo praticamente avviene facendo attenzione agli import oppure usando esplicitamente
PowerMockito.metodo(...).
Analizziamo suppress:
1 PowerMockito.suppress(method(ClasseProva.class),"nomeMetodo");
Codice Componente 3.7: PowerMock: suppress
Il metodo suppress consente di sopprimere l’esecuzione di un metodo che si trova all’interno
del codice che stiamo testando. Possiamo sopprimere metodi private, protected e public. Notiamo
che:
• method(ClasseProva.class): serve ad indicare in quale classe si trova il metodo da sopprimere;
• nomeMetodo: è il nome del metodo da sopprimere.
Per verificare che un metodo static sia invocato all’interno del nostro codice possiamo usare:
1 PowerMockito.verifyStatic();
2 ClasseProva.metodoStatic(param1, param2,...);
Codice Componente 3.8: PowerMock: verifyStatic
Notiamo che per far funzionare verifyStatic è importante che l’ordine delle istruzioni sia quello
sopraindicato, ovvero prima la riga 1 e poi il metodo static che vogliamo verificare.
Supponiamo di testare un metodo di una ClasseA che eredita dalla ClasseB. Nel caso in cui
vogliamo testare in isolamento il metodo della classeA che chiama il costruttore o un metodo della
classeB, possiamo sopprimere tutte le chiamate alla classe padre. Il codice è il seguente:
1 PowerMockito.suppress(PowerMockito.methodsDeclaredIn(ClasseB.class));
Codice Componente 3.9: PowerMock: suppress
Questa features di PowerMockito è stata utilizzata in Android per testare i metodi: onCreate(),
onResume(), ecc, dato che questi hanno la chiamata del tipo super.onCreate(savedInstanceState)
che genera eccezioni durante il test di unità locale3
.
Nel caso in cui vogliamo creare un mock di un metodo statico di una certa classe possiamo fare
in questo modo:
1 PowerMockito.mockStatic(ClasseProva.class);
2 PowerMockito.when(ClasseProva.metodoStatic()).thenReturn(valore);
Codice Componente 3.10: PowerMock: mockStatic
3
Il motivo è spiegato nella sezione 1.3.1
Tesina di IS2: Mock in Android 20
24. CAPITOLO 3. POWERMOCK
Con:
• mockStatic: viene indicato a PowerMock la classe con i metodi static;
• PowerMockito.when: ci permette di creare il fake per il metodo statico metodoStatic()4
;
Con PowerMock è possibile intercettare la chiamata del costruttore di una particolare classe in
modo da passargli l’oggetto mock o spy creato da noi.
1 PowerMockito.whenNew(ClasseProva.class).withArguments(param1,param2).
thenReturn(oggettoMock);
Codice Componente 3.11: PowerMock: whenNew withArguments
ClasseProva è il nome della classe per la quale vogliamo intercettare il costruttore, con withAr-
guments(..) indichiamo quale costruttore, quindi con quale parametri, deve essere intercettato per
far ritornare il nostro oggettoMock.
3.6 Vantaggi e svantaggi
Riepilogando, i vantaggi di PowerMock sono:
• date le sue potenzialità non rende necessario quasi mai modificare il codice sorgente per scopi
di testing;
• fornisce un elevato quantitativo di API per testare classi e metodi pubblic, private, static e
final;
• può essere usato insieme a Mockito ed EasyMock.
Svantaggi:
• Il suo sviluppo è più lento rispetto a quello di Mockito e quindi non è sempre possibile
utilizzare le ultime API di Mockito;
• La documentazione non è esaustiva e precisa come quella di Mockito;
• Sono presenti relativamente pochi esempi dell’uso di PowerMock rispetto a Mockito.
4
Notiamo che when è anche un metodo di Mockito, ma non potremmo usare Mockito.when dato che
PowerMockito.when è una versione “potenziata” di when che ci permette di trattare metodi statici.
Tesina di IS2: Mock in Android 21
25. Capitolo 4
Accelerometer testing (Mock)
In questa sezione viene spiegato come sono stati usati Mockito e PowerMock per testare il codi-
ce del metodo onSensorChange() della classe SensorEventListener, relativamente all’applicazione
sviluppata nel primo progetto.
4.1 Accelerometer in Android
Per utilizzare i sensori in Android bisogna usare le classi Sensor, SensorManager,
SensorEventListener e SensorEvent.
1 mSensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
2 mSensorManager.registerListener(mSensorListener,mSensorManager.
getDefaultSensor(Sensor.TYPE_ACCELEROMETER),SensorManager.
SENSOR_DELAY_NORMAL);
3 mAccel = 0.00f;
4 mAccelCurrent = SensorManager.GRAVITY_EARTH;
5 mAccelLast = SensorManager.GRAVITY_EARTH;
Codice Componente 4.1: Accelerometer
SensorManager permette di accedere ai sensori del device, questo attraverso un’istanza ottenuta
mediante Context.getSystemService() con argomento SENSOR_SERVICE.
Sensor è una classe final che contiene attributi final che identificano i vari sensori, come ad
esempio Sensor.TYPE_ACCELEROMETER si riferisce all’accelerometro.
La classe SensorEventListener viene usata per ricevere notifiche da SensorManager quando sono
disponibili nuovi dati dal sensore su cui ci siamo messi in ascolto. Questa classe ha due metodi
astratti, ovvero onSensorChanged(SensorEvent event) e onAccuracyChanged(Sensor sensor, int
accuracy) che devono essere implementati nel nostro codice con le operazioni da fare quando i dati
del sensore cambiano.
Possiamo notare che onSensorChanged(SensorEvent event) ha come parametro un oggetto della
classe SensorEvent. Questa classe rappresenta un evento sensore e gestisce informazioni come il
tipo di sensore, il timestamp, l’accuratezza e ovviamente i dati del sensore. In particolare i dati
sono un attributo final di questa classe. L’attributo è un vettore di float chiamato values che
contiene i valori dell’accelerazione misurati in m
s2 .
Nel caso dei dati di accelerazione avremo che:
22
26. CAPITOLO 4. ACCELEROMETER TESTING (MOCK)
• values[0]: è l’accelerazione lungo l’asse x;
• values[1]: è l’accelerazione lungo l’asse y;
• values[2]: è l’accelerazione lungo l’asse z;
Gli assi rispetto allo smartphone sono mostrati nella figura 4.1.
Figura 4.1: Assi accelerometro
Notiamo che quando il cellulare è posizionato in verticale values[1] varrà circa 9.8 m
s2 . Questo
aspetto vedremo che sarà importante per simulare in modo controllato i valori dell’accelerometro.
4.2 Testing del metodo onSensorChange
Nella sezione precedente abbiamo spiegato qual è l’oggetto fornito quando viene invocato questo
metodo dal framework Android. Vediamo ora come è possibile testare questo metodo in isolamento.
1 private SensorEventListener mSensorListener = new SensorEventListener() {
2 public void onSensorChanged(SensorEvent se) {
3 float x = se.values[0]; //values[0]: Acceleration on the x-axis (All
values are in SI units (m/s^2)) float y = se.values
[1];
4 float z = se.values[2];
5 mAccelLast = mAccelCurrent;
6 mAccelCurrent = (float) Math.sqrt((double) (x*x + y*y + z*z));
7 float delta = mAccelCurrent - mAccelLast;
8 mAccel = mAccel * 0.9f + delta; // perform low-cut filter
Tesina di IS2: Mock in Android 23
27. CAPITOLO 4. ACCELEROMETER TESTING (MOCK)
9 //valore da settare per decidere quanto si deve "Shakerare"
il telefono
10 if (mAccel > 7) {
11 //se chiede più volte il suggerimento per la stessa domanda non
aumentiamo gli userHintNumber
12 if(quizLogic.isUserHintRequest() == false){
13 toastHint.show();
14 quizLogic.incrementUserHintNumber();
15 indexBtnHint = quizLogic.getIndexEliminatedAnswer();
16 Log.v(QUIZ_ACTIVITY, "indexButtonNotClickable:"+indexBtnHint);
17 setButtonNotClickable(indexBtnHint);
18 Log.v(QUIZ_ACTIVITY, "onSensorChanged(): indexBtnHint: "+
indexBtnHint);
19 }
20 }
21 }
22 };
Codice Componente 4.2: onSensordChange()
Nell’applicazione Android Quiz questo metodo legge i valori del sensore e se nota che lo smart-
phone è stato “shakerato” con una certa forza, esegue il codice presente nel primo if. Il secondo if
è inserito poiché la regola del gioco a Quiz prevede che possa essere chiesto un solo suggerimento e
quindi di conseguenza verrà oscurato un solo bottone. Quest’ultima operazione viene eseguita nel
metodo setButtonNotClickable(indexBtnHint).
Ritornando all’accelerometro con se.values[] ci prendiamo i valori dei vari assi. Con il codice
successivo calcoliamo la magnitudo dell’accelerazione, ovvero con Math.sqrt((double) (x*x + y*y +
z*z)), in modo da prendere il valore assoluto (ricorda che l’accelerazione può avere valori negativi).
Con delta = mAccelCurrent - mAccelLast eliminiamo l’effetto della forza di gravità, mentre con
mAccel = mAccel * 0.9f + delta cerchiamo di attenuare il rumore prodotto dal sensore.
I test che troviamo nel file QuizActivityTest.java si pongono come obiettivo quello di testare le
tre decisioni possibili dei due if innestati. In questa sezione viene analizzato nel dettaglio uno dei
test (accelTest2()), ovvero il caso in cui mAccel > 7 e quizLogic.isUserHintRequest() = false.
1 //Caso in cui il dispositivo si muove lungo Y e superiamo l’accelerazione da
noi stabilita
2 float[] floats = new float[3];
3 //rispettivamente X, Y, Z
4 floats[0] = 0.1f;
5 floats[1] = 8f + 9.80665f;
6 floats[2] = 0.1f; //0.1f rumore fittizio
Codice Componente 4.3: createMockToast()
Iniziamo con il creare il vettore floats che conterrà i dati finti. Per superare la soglia di acce-
lerazione da noi prevista settiamo floats[1] = 8f + 9.80665f, ovvero 8f è la nostra forza lungo y
(movimento dello smartphone dall’alto verso il basso) più la forza di gravità. In generale possiamo
combinare i valori dei vari assi per superare la soglia, ma in questo caso abbiamo preferito questo
approccio perché più intuitivo.
Tesina di IS2: Mock in Android 24
28. CAPITOLO 4. ACCELEROMETER TESTING (MOCK)
Con mockSetButtonNotClickable(); risolviamo tutte le dipendenze relative al metodo privato
setButtonNotClickable(...);
1 private void mockSetButtonNotClickable(){
2 TextView mockTVQuestion = mock(TextView.class);
3 Button mockBtnAnswer0 = mock(Button.class);
4 Button mockBtnAnswer1 = mock(Button.class);
5 Button mockBtnAnswer2 = mock(Button.class);
6 Button mockBtnAnswer3 = mock(Button.class);
7 Whitebox.setInternalState(quizActivity, "question", mockTVQuestion);
8 Whitebox.setInternalState(quizActivity, "answer0", mockBtnAnswer0);
9 Whitebox.setInternalState(quizActivity, "answer1", mockBtnAnswer1);
10 Whitebox.setInternalState(quizActivity, "answer2", mockBtnAnswer2);
11 Whitebox.setInternalState(quizActivity, "answer3", mockBtnAnswer3);
12 }
Codice Componente 4.4: mockSetButtonNotClickable()
Con createMockToast(); creiamo un mock object della classe Toast di Android, e con la seconda
riga del codice diciamo che quando toastHint.show() viene richiamato non deve essere fatto nulla
(poi però verificheremo se questo metodo è stato invocato).
1 private void createMockToast(){
2 mockToast = mock(Toast.class);
3 doNothing().when(mockToast).show();
4 }
5
6 Whitebox.setInternalState(quizActivity, "toastHint", mockToast);
Codice Componente 4.5: createMockToast()
Adesso viene creato un mock di QuizLogic, imponendo un certo comportamento ai metodi
utilizzati, questo al fine di eseguire il codice presente nell’ if più interno di onSensorChange().
Questo avviene facendo ritornare false quando quizLogic.isUserHintRequest() viene chiamato.
1 QuizLogic mockQuizLogic = mock(QuizLogic.class);
2 when(mockQuizLogic.isUserHintRequest()).thenReturn(false);
3 when(mockQuizLogic.getIndexEliminatedAnswer()).thenReturn(0);
4 Whitebox.setInternalState(quizActivity, "quizLogic", mockQuizLogic);
Codice Componente 4.6: QuizLogic Mock
Successivamente inizializzo le variabili globali legate all’accelerometro:
1 Whitebox.setInternalState(quizActivity, "mAccel", 0.00f);
2 Whitebox.setInternalState(quizActivity, "mAccelCurrent", 9.80665f);
3 Whitebox.setInternalState(quizActivity, "mAccelLast", 9.80665f);
Codice Componente 4.7: attributi classe
La parte più delicata è quella relativa al prossimo codice, in cui si crea un mock per la classe
SensorEvent e SensorEventListener.
Tesina di IS2: Mock in Android 25
29. CAPITOLO 4. ACCELEROMETER TESTING (MOCK)
1 SensorEventListener mockSensorEventListener = mock(SensorEventListener.class
);
2 SensorEvent mockSE = createSensorEvent(floats);
3 SensorEventListener mSensorEventListener = new SensorEventListener() {
4 @Override
5 public void onSensorChanged(SensorEvent event) {}
6 @Override
7 public void onAccuracyChanged(Sensor sensor, int accuracy) {}
8 };
9 mSensorEventListener = Whitebox.getInternalState(quizActivity, "
mSensorListener");
10 mSensorEventListener.onSensorChanged(mockSE);
Codice Componente 4.8: Mock: SensorEvent e SensorEventListener
Con la prima riga e le seguenti non facciamo altro che creare il mock object di tipo SensorE-
ventListener, che poi passiamo all’attributo “mSensorListener” della classe QuizActivity e succes-
sivamente richiamiamo onSensorChanged(...) passando per parametro mockSE. L’esecuzione del
test avviene proprio in questa riga.
Analizziamo nella seguente sottosezione in dettaglio come è stato realizzato il mock della classe
SensorEvent.
4.2.1 Mock della classe SensorEvent
Il seguente codice in sintesi permette di modificare i campi final della classe SensorEvent, in questo
modo possiamo settare un qualsiasi valore float per l’attributo values. Analizziamo il seguente
codice:
1 private SensorEvent createSensorEvent(float[] values){
2 SensorEvent sensorEvent = mock(SensorEvent.class);
3 try {
4 Field valuesField = SensorEvent.class.getField("values");
5 valuesField.setAccessible(true);
6 float[] sensorValue = values;
7 try {
8 valuesField.set(sensorEvent, sensorValue);
9 } catch (IllegalAccessException e) {
10 Assert.fail("Non è possibile accedere al Field indicato");
11 e.printStackTrace();
12 }
13 } catch (NoSuchFieldException e) {
14 Assert.fail("Il field speficato non esiste");
15 e.printStackTrace();
16 }catch (SecurityException e) {
17 Assert.fail("Il security manager di Java ci ha impedito di rendere
accessibile il Field");
18 e.printStackTrace();
19 }
20 return sensorEvent;
Tesina di IS2: Mock in Android 26
30. CAPITOLO 4. ACCELEROMETER TESTING (MOCK)
21 }
Codice Componente 4.9: Mock: SensorEvent
Nella seconda riga viene istanziato un oggetto mock della classe SensorEvent.
Con SensorEvent.class.getField("values")1
verrà restituito un Field object sfruttando la
reflection, passando per parametro l’attributo della classe che ci interessa.
Un oggetto Field fornisce informazioni e accesso dinamico ad un singolo campo di una classe.
Con valuesField.setAccessible(true) vogliamo sopprimere l’accessing checking da parte del lin-
guaggio java quando proviamo a modificare quell’oggetto. Notiamo che nel caso in cui è pre-
sente un security manager2
non è possibile questa modifica e verrà invocata un’eccezione di tipo
SecurityException.
4.2.2 Verifica del test
La parte di verifica comprende il seguente codice:
1 int indexBtnHint = Whitebox.getInternalState(quizActivity, "indexBtnHint");
2 //verifica test
3 try {
4 verify(mockToast, Mockito.times(1)).show());
5 }catch (MockitoAssertionError e){
6 Assert.fail("Il metodo non è stato chiamato una sola volta");
7 e.printStackTrace();
8 }
9 Assert.assertEquals("Non è stato settato il suggerimento previsto",
indexBtnHint, 1);
Codice Componente 4.10: Verifica
Con la classe Whitebox di PowerMock ci facciamo restituire l’attributo indexBtnHint, ovvero
l’indice del bottone che è stato oscurato e successivamente con Assert.assertEquals("Non è stato
settato il suggerimento previsto", indexBtnHint, 1) verifichiamo se è stato correttamente settato.
Nel blocco try catch con il codice verify(mockToast, Mockito.times(1)).show() verifichiamo che
sull’oggetto mockToast viene chiamato una sola volta il metodo .show(). Nel caso in cui questo
non accade, il framework Mockito può lanciare una delle seguenti eccezioni:
• TooManyActualInvocations: il metodo è stato invocato più volte di quanto abbiamo
specificato.
• WantedButNotInvoked: se ad esempio inseriamo Mockito.atLeastOnce()
• ed altre eccezioni che troviamo sulla documentazione di Mockito.
Quindi poiché l’eccezione viene lanciata se verify non è stato correttamente verificato, nel catch
inseriamo Assert.fail(...) con un breve messaggio che spiega il fail relativo a verify.
Nella figura viene riportata in breve la descrizione del caso di test:
1
Java Class Doc
2
Java Security Manager
Tesina di IS2: Mock in Android 27
31. CAPITOLO 4. ACCELEROMETER TESTING (MOCK)
Figura 4.2: Descrizione Test Case
4.3 Testing dei sensori in generale
Come è possibile notare dalla documentazione Android sulla classe SensorEvent il template con
cui un sensore eroga i valori è sempre lo stesso, ovvero attraverso un vettore di float, che contiene
un certo numero di valori in base al sensore. Abbiamo visto il caso dell’accelerometro, ma abbiamo
una situazione analoga per gli altri sensori. Ad esempio per il giroscopio abbiamo tre elementi del
vettore values che rappresentano la velocità angolare lungo un certo asse.
Figura 4.3: Sensore luminosità
Per alcuni sensori possiamo avere un numero diverso di campi. Ad esempio il sensore di
luminosità ha un solo elemento del vettore values come mostrato in fig 4.3, mentre il magnetometro
non calibrato ha sei valori come è visibile in fig 4.4.
Tesina di IS2: Mock in Android 28
32. CAPITOLO 4. ACCELEROMETER TESTING (MOCK)
Figura 4.4: Magnetometro non calibrato
Quindi il codice di test realizzato può essere facilmente generalizzato ad altri sensori. Basta
creare un opportuno vettore di float e settare il corrispondente attributo values di SensorEvent.
Tesina di IS2: Mock in Android 29
33. Capitolo 5
Android Activity Test
In questa sezione viene spiegato come sono stati usati Mockito e PowerMock per testare tut-
to il codice di una classe Activity di Android. Abbiamo già analizzato il testing del metodo
onSensorChange 4.2. Ora analizziamo i test che coprono il restante codice della classe QuizActivity.
5.1 Obiettivi
Avendo a disposizione il codice è stato eseguito maggiormente un White Box Testing sui vari
metodi di QuizActivity.java. La descrizione di tutti i casi di test è presente nelle pagine html del
javadoc di QuizActivityTest. Si è cercato di coprire le decisioni dei costrutti condizionali come if e
switch e quindi le LOC. Inoltre è stata sfruttata la libreria di PowerMock e Mockito per verificare
più in “profondità” il codice e per risolvere le dipendenze dei metodi da testare. In molti casi sono
stati creati mock anche per classi del framework Android, realizzando quindi un test in isolamento
dalla particolare versione di Android e/o dispositivo. Per verificare la copertura delle decisioni
viene usato il tool di Coverage fornito da Android studio, che relativamente ad un package riporta
le classi, metodi e linee di codice coperte.
5.2 Testing delle Activity con i mock
All’interno delle Activity sono presenti metodi come onCreate, onPause che sono ereditati dalla
classe genitore AppCompatActivity1
. Per testare questi metodi sorgono delle complicazioni, la
prima riguarda il fatto che viene generata un’eccezione di tipo java.lang.NullPointerException
quando si esegue super.onCreate(savedInstanceState), come mostrato in fig. 5.1
1
Questi metodi sono ereditati e riscritti da diverse classi prima di arrivare ad AppCompactActivity
https://developer.android.com/reference/android/support/v7/app/AppCompatActivity.html
30
34. CAPITOLO 5. ANDROID ACTIVITY TEST
Figura 5.1: NullPointerException
Il problema è dovuto al fatto che nella chiamata super.onCreate si vanno ad utilizzare dei
metodi di AppCompatDelegate e poi altre classi che riguardano dipendenze che non abbiamo
risolto. Anche utilizzando Spy non possiamo comunque richiamare i metodi reali delle classi più
in alto nella gerarchia di ereditarietà. Per questo motivo si è deciso di sopprimere la chiamata
super.onCreate(savedInstanceState), al fine di testare in isolamento solo il nostro codice scritto
in onCreate. Questo problema si ripete anche per onPause, onResume, e quindi tutti i metodi
che servono a gestire le varie fasi dell’activity lifecycle. Stiamo assumendo che l’esecuzione di
super.onCreate non influenzi la correttezza del nostro codice, cosa che è vera in questo caso, ma
potrebbe non esserlo in generale.
Il seguente codice mostra come è stato risolto il problema per il metodo onCreate:
1 @Override
2 protected void onCreate(Bundle savedInstanceState) {
3 super.onCreate(savedInstanceState);
4 Log.v(QUIZ_ACTIVITY, "onCreate()");
5 //Aca = this;
6 Aca.setContentView(R.layout.activity_quiz);
7 question = (TextView) Aca.findViewById(R.id.tv_question);
8 answer0 = (Button) Aca.findViewById(R.id.btn_answer0);
9 answer1 = (Button) Aca.findViewById(R.id.btn_answer1);
10 answer2 = (Button) Aca.findViewById(R.id.btn_answer2);
11 answer3 = (Button) Aca.findViewById(R.id.btn_answer3);
12 quizLogic = QuizLogic.getIstance();
13 Intent mIntent = Aca.getIntent();
14 quizLogic.setArrayListQuestion((ArrayList<Question>)mIntent.
getSerializableExtra(Aca.getString(R.string.array_list_question_object
)));
15 //Log.v(QUIZ_ACTIVITY, "onCreate(): quizLogic.getArrayListQuestion().size=
"+quizLogic.getArrayListQuestion().size());
16 if (savedInstanceState != null) {
17 if (savedInstanceState.containsKey(Aca.getString(R.string.
button_hint_index))) {
18 indexBtnHint = savedInstanceState.getInt(Aca.getString(R.string.
button_hint_index));
19 }
20 }else{
21 Log.v(QUIZ_ACTIVITY,"onCreate: savedInstanceState != null");
22 indexBtnHint = -1;
23 }
Tesina di IS2: Mock in Android 31
36. CAPITOLO 5. ANDROID ACTIVITY TEST
Figura 5.2: Descrizione Test Case
Il codice di test:
1 @Test
2 public void onCreateTest() throws Exception {
3 //Sopprimiamo la chiamata super();
4 PowerMockito.suppress(PowerMockito.methodsDeclaredIn(AppCompatActivity.
class));
5 PowerMockito.suppress(PowerMockito.method(Log.class,"v",String.class,
String.class));
6 //Mock AppCompactActitvity
7 AppCompatActivity AcaMock = mock(AppCompatActivity.class);
8 //Mock Bundle
9 Bundle bundleMock = mock(Bundle.class);
10 //Mock Intent
11 Intent intentMock = mock(Intent.class);
12 when(AcaMock.getIntent()).thenReturn(intentMock);
13 //Mock TextView Botton
14 TextView mockTVQuestion = mock(TextView.class);
15 Button mockBtnAnswer0 = mock(Button.class);
16 Button mockBtnAnswer1 = mock(Button.class);
17 Button mockBtnAnswer2 = mock(Button.class);
18 Button mockBtnAnswer3 = mock(Button.class);
19 when(AcaMock.findViewById(R.id.tv_question)).thenReturn(mockTVQuestion);
20 when(AcaMock.findViewById(R.id.btn_answer0)).thenReturn(mockBtnAnswer0);
21 when(AcaMock.findViewById(R.id.btn_answer1)).thenReturn(mockBtnAnswer1);
22 when(AcaMock.findViewById(R.id.btn_answer2)).thenReturn(mockBtnAnswer2);
23 when(AcaMock.findViewById(R.id.btn_answer3)).thenReturn(mockBtnAnswer3);
Tesina di IS2: Mock in Android 33
37. CAPITOLO 5. ANDROID ACTIVITY TEST
24 //Mock Question
25 Question questionMock = mock(Question.class);
26 //Mock ArrayList di question
27 ArrayList<Question> arrayListQustionMock = mock(ArrayList.class);
28 //Mock QuizLogic
29 QuizLogic quizLogicMock = mock(QuizLogic.class);
30 doNothing().when(quizLogicMock).setArrayListQuestion(any(ArrayList.class))
;
31 //Mock Static QuizLogic
32 PowerMockito.mockStatic(QuizLogic.class);
33 PowerMockito.when(QuizLogic.getIstance()).thenReturn(quizLogicMock);
34 //Mock Static Media Player
35 PowerMockito.mockStatic(MediaPlayer.class);
36 PowerMockito.when(QuizLogic.getIstance()).thenReturn(quizLogicMock);
37 PowerMockito.suppress(method(QuizActivity.class, "refreshQuestion"));
38 PowerMockito.suppress(method(QuizActivity.class, "setupSharedPreferences")
);
39 //mock savedInstanceState
40 Whitebox.setInternalState(quizActivity, "indexBtnHint", 0);
41 //Mock SensorEventListner
42 SensorEventListener sensorEventListenerMock = mock(SensorEventListener.
class);
43 Whitebox.setInternalState(quizActivity, "mSensorListener",
sensorEventListenerMock);
44 //Mock Sensor
45 Sensor sensorMock = mock(Sensor.class);
46 //Mock SensorManager
47 SensorManager sensorManagerMock = mock(SensorManager.class);
48 when(sensorManagerMock.getDefaultSensor(anyInt())).thenReturn(sensorMock);
49 when(AcaMock.getSystemService(Context.SENSOR_SERVICE)).thenReturn(
sensorManagerMock);
50 //verifica che getDefaultSensor è chiamato sul sensore accel
51 //settato alla fine data la creazione di diversi fake
52 Whitebox.setInternalState(quizActivity, "Aca", AcaMock);
53 //invoke
54 Whitebox.invokeMethod(quizActivity, "onCreate", bundleMock);
55 //VERIFICHE
56 verify(AcaMock, Mockito.times(1)).setContentView(R.layout.activity_quiz);
57 //verifico che sensorManagerMock.getDefaultSensor viene chiamato sull’
accelerometro
58 ArgumentCaptor<Integer> argumentCaptorInt1 = ArgumentCaptor.forClass(
Integer.class);
59 verify(sensorManagerMock, Mockito.times(1)).getDefaultSensor((int)
argumentCaptorInt1.capture()); Assert.assertEquals("sensor
sbagliato",Sensor.TYPE_ACCELEROMETER, (int)argumentCaptorInt1.getValue
());
60 float mAccel = Whitebox.getInternalState(quizActivity,"mAccel");
61 Assert.assertEquals("mAccel inizializzato con valore errato", mAccel, 0f,
0f);
Tesina di IS2: Mock in Android 34
38. CAPITOLO 5. ANDROID ACTIVITY TEST
62 float mAccelCurrent = Whitebox.getInternalState(quizActivity,"
mAccelCurrent");
63 Assert.assertEquals("mAccel inizializzato con valore errato",
mAccelCurrent, SensorManager.GRAVITY_EARTH, 0f);
64 float mAccelLast = Whitebox.getInternalState(quizActivity,"mAccelLast");
65 Assert.assertEquals("mAccel inizializzato con valore errato", mAccelLast,
SensorManager.GRAVITY_EARTH, 0f);
66 PowerMockito.verifyStatic();
67 MediaPlayer.create(AcaMock, R.raw.correct_answer);
68 PowerMockito.verifyStatic();
69 MediaPlayer.create(AcaMock, R.raw.wrong_answer);
70 }
Codice Componente 5.2: onCreateTest QuizActivity
Quindi per sopprimere l’invocazione del metodo super.onCreate(savedInstanceState) è stato
usato questo codice:
1 PowerMockito.suppress(PowerMockito.methodsDeclaredIn(AppCompatActivity.class
));
Codice Componente 5.3: PowerMockito Suppress
Con il metodo suppress PowerMockito sopprime tutte le chiamate fatte a metodi della classe
AppCompatActivity (incluso l’eventuale costruttore) e genitori di questa classe.
5.2.1 Mock dei metodi di AppCompatActivity
Quando usiamo dei metodi della classe AppCompatActivity spesso non esplicitiamo this (ri-
ferimento dell’oggetto che stiamo usando), ad esempio scriviamo setContentView(...) e non
this.setContentView(...). Per poter controllare il comportamento di questi metodi con il framework
PowerMock è stato necessario esplicitare this utilizzando l’attributo Aca che poi verrà settato con
l’oggetto Spy o Mock in base alle necessità di quel test.
1 private AppCompatActivity Aca = this;
Codice Componente 5.4: QuizActivity
In questo modo con:
1 AppCompatActivity AcaMock = mock(AppCompatActivity.class);
2 when(AcaMock.findViewById(R.id.tv_question)).thenReturn(mockTVQuestion);
3 when(AcaMock.findViewById(R.id.btn_answer0)).thenReturn(mockBtnAnswer0);
4 ...
5 when(AcaMock.getIntent()).thenReturn(intentMock);
6 Whitebox.setInternalState(quizActivity, "Aca", AcaMock);
Codice Componente 5.5: QuizActivity Test
Con la riga 1 creiamo un Mock della classe AppCompatActivity, con le altre righe generiamo
fake per i vari metodi, e.g. alla chiamata getIntent() verrà restituito il mock Intent creato da noi.
Infine inseriamo AcaMock all’interno della classe da testare con setInternalState.
Tesina di IS2: Mock in Android 35
39. CAPITOLO 5. ANDROID ACTIVITY TEST
Far ritornare il nostro intentMock può essere utile per restituire alla classe activity ora in
esecuzione i dati passati dalla precedente. Quindi creando il mock object di un Intent possiamo
passare dati fittizi all’activity, che ad esempio possono essere usati per testare un certo path del
codice.
Nel nostro caso questo approccio è stato utilizzato semplicemente per non far alzare l’eccezione
NullPointerException durante la linea di codice.
1 Intent mIntent = Aca.getIntent();
2 quizLogic.setArrayListQuestion((ArrayList<Question>)mIntent.
getSerializableExtra(Aca.getString(R.string.array_list_question_object))
);
Codice Componente 5.6: QuizActivity
Nel test case illustrato non era necessario, ma avremmo potuto restituire un ArrayList di
domande finte su cui fare delle verifiche. Ad esempio potremmo verificare che il testo dei Buttons
era settato con le stringhe delle possibili risposte fittizie da noi create.
Tipicamente nel metodo onCreate vengono associate all’activity molte delle risorse presen-
ti nella cartella e sottocartelle di “res”. Questa operazione quando fatta con findViewById(...)
oppure setContentView(...) non crea problemi e possiamo gestire il tutto con tecniche già vi-
ste, ma se questa operazione avviene mediante metodi statici di una classe, come ad esempio
MediaPlayer.create(AcaMock, R.raw.correct_answer), c’è bisogno di utilizzare altre tecniche.
1 //sound effect
2 mpCorrectAnswer = MediaPlayer.create(Aca, R.raw.correct_answer);
3 mpWrongAnswer = MediaPlayer.create(Aca, R.raw.wrong_answer);
4
5 PowerMockito.verifyStatic();
6 MediaPlayer.create(AcaMock, R.raw.correct_answer);
7 PowerMockito.verifyStatic();
8 MediaPlayer.create(AcaMock, R.raw.wrong_answer);
Codice Componente 5.7: MediaPlayer
Con il codice sovrastante è stato possibile verificare l’avvenuta e la corretta inizializzazione
degli oggetti MediaPlayer.
Un altro metodo di PowerMockito utilizzato è whenNew(Intent.class) che permette di intercet-
tare la chiamata al costruttore della classe e far ritornare il nostro oggetto mock. Questa tecnica
è stata utilizzata per testare il metodo privato goToLastQuizActivity(...).
Descrizione del caso di test:
Tesina di IS2: Mock in Android 36
40. CAPITOLO 5. ANDROID ACTIVITY TEST
Figura 5.3: Descrizione Test Case
Il codice di goToLastActivity(...) è il seguente:
1 private void goToLastQuizActivity(){
2 Class destinationActivity = LastQuizActivity.class;
3 Intent startChildActivityIntent = new Intent(Aca.getApplicationContext(),
destinationActivity);
4 startChildActivityIntent.putExtra(Aca.getString(R.string.
user_correct_answer), quizLogic.getUserCorrectAnswer());
5 startChildActivityIntent.putExtra(Aca.getString(R.string.user_hint_number)
, quizLogic.getUserHintNumber());
6 startChildActivityIntent.putExtra(Aca.getString(R.string.
quiz_number_questions), quizLogic.getNumberOfQuestions());
7 QuizLogic.destroyIstance();
8 mSensorManager.unregisterListener(mSensorListener);
9 Aca.startActivity(startChildActivityIntent);
10 }
Codice Componente 5.8: metodo: goToLastActivity
Con il codice sottostante, riga 1, diciamo a PowerMockito di iniettare l’oggetto intentMock
quando viene richiesto di creare un nuovo oggetto Intent. Implicitamente stiamo anche verificando
che l’Activity di destinazione sia LastQuizActivity.
1 PowerMockito.whenNew(Intent.class).withArguments(AcaMock.
getApplicationContext(), LastQuizActivity.class).thenReturn(intentMock);
2 verify(intentMock, Mockito.times(3)).putExtra((String)argumentCaptor1.
capture(), (int) argumentCaptor2.capture());
3 //1à chiamata
4 Assert.assertEquals("putExtra parametro errato",argumentCaptor1.getAllValues
().get(0),"1");
5 Assert.assertEquals("putExtra parametro errato",(int)argumentCaptor2.
getAllValues().get(0),5);
6 ...
7 verify(sensorManagerMock, Mockito.times(1)).unregisterListener(any(
SensorEventListener.class));
8 ...
Tesina di IS2: Mock in Android 37
41. CAPITOLO 5. ANDROID ACTIVITY TEST
9 verify(AcaMock, Mockito.times(1)).startActivity(any(intentMock));
Codice Componente 5.9: verifica goToLastQuizActivityTest
Con il primo verify contiamo che putExtra sia richiamato 3 volte e vengano catturati tutti
i parametri di input delle tre chiamate con il metodo capture() di ArgumentCaptor. La veri-
fica che il valore passato sia corretto avviene con Assert.assertEquals("putExtra parametro er-
rato",argumentCaptor1.getAllValues().get(0),"1"). Notiamo che in questo caso è preso il primo
valore get(0), con get(1) e get(2) prendiamo i restanti valori.
Infine si verifica che startActivity venga chiamato con l’intent giusto.
5.3 Coverage della classe QuizActivity
L’obiettivo della Test Suite di QuizActivityTest è quello di coprire il maggior numero di linee di co-
dice e in particolare le decisioni dei costrutti condizionali dei vari metodi della classe QuizActivity.
I risultati ottenuti sulla coverage della classe QuizActivity sono riportati nella fig. 5.4.
Figura 5.4: Coverage Quiz Activity
Tesina di IS2: Mock in Android 38
42. Capitolo 6
Conclusioni
Dallo studio empirico riportato nella sezione 1.1 si è notato che Mockito risulta essere un framework
molto utilizzato. Le potenzialità di questo framework sono elevate, ma vengono ampliate da
PowerMock che ha permesso di testare la classe QuizActivity con limitate modifiche al codice. Il
solo uso di Mockito richiede ad esempio di creare getter e setter aggiuntivi per poter sostituire una
dipendenza con un mock object, come visto nel caso della classe QuizLogic e FireBaseDB, dove di
proposito non è stato usato anche PowerMock così da poter valutare le differenze.
Il test di unità all’interno di Android si rivela vantaggioso se il nostro obiettivo è avere una
coverage molto elevata e se vogliamo analizzare più in “profondità” il codice, con ad esempio API
come verify di Mockito.
L’uso di questi framework è abbastanza flessibile per testare un’intera classe Activity di
Android, come visto nel capitolo 5, a patto di fare a volte qualche piccola modifica al codice.
Date le potenzialità di questi framework nel testing, si pensa che queste possano essere utilizzate
con successo nel Test driven development1
, dove la stesura dei test automatici avviene prima di
quella del software che deve essere sottoposto a test. Infatti con i framework possiamo entrare più
in profondità nel codice, come visto possiamo verificare che un programmatore inserisca una certa
risorsa nell’activity, che sia invocato un certo metodo con determinati parametri oppure recuperare
un attributo privato e valutarlo dopo l’esecuzione del test.
Si conclude dicendo che questi framework semplificano, velocizzano, e rendono più efficace la
stesura dei test. Infatti spesso permettono al tester di evitare modifiche al codice, e in alcuni casi
evitano di inserire nuove interfacce e classi che portano ad un’esplosione del diagramma delle classi.
Come abbiamo visto gli oggetti mock sono agganciati dinamicamente e nel caso di Powermock è
possibile farlo anche per variabili e metodi privati.
1
https://it.wikipedia.org/wiki/Test_driven_development
39
43. Capitolo 7
Riferimenti
Bibliografia
Sujoy Acharya, Mastering Unit Testing Using Mockito and JUnit, Packt Publishing
Deep Shah, Instant Mock Testing with PowerMock, Packt Publishing
Shaikh Mostafa, Xiaoyin Wang, An Empirical Study on the Usage of Mocking Fra-
meworks in Software Testing, Department of Computer Science, University of Texas at
San Antonio
Mockito Java Doc https://static.javadoc.io/org.mockito/mockito-
core/2.7.9/org/mockito/Mockito.html
PowerMock Doc https://github.com/powermock/powermock
Motion Sensors https://developer.android.com/guide/topics/sensors/sensors_motion.html#sensors-
motion-accel
Sensor https://developer.android.com/reference/android/hardware/Sensor.html
SensorEvent https://developer.android.com/reference/android/hardware/SensorEvent.html
SensorManager https://developer.android.com/reference/android/hardware/SensorManager.html
SensorEventListener https://developer.android.com/reference/android/hardware/SensorEventListener.h
40