SlideShare a Scribd company logo
1 of 43
Download to read offline
Tesina di Ingegneria del software 2
Progetto: Mock in Android
Studente: Lonardo Gioacchino
Mat: M63/000567
19 aprile 2017
Indice
1 Mocking Framework 1
1.1 L’uso dei Mocking framework . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 User Interface Test e Unit Test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.3 Unit Test in Android . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3.1 Local Unit test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2 Mockito 5
2.1 Configurazione con Android Studio . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.2 Utilizzo di Mockito insieme a JUnit4 . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.3 Mockito method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.4 Hello world! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.5 Mock di java.util.Random . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.6 Lanciare eccezioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.7 Vantaggi e svantaggi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3 PowerMock 16
3.1 Configurazione con Android Studio . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
3.2 Utilizzo di PowerMock . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
3.3 Whitebox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
3.4 Mock e Spy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.5 PowerMockito Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3.6 Vantaggi e svantaggi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
4 Accelerometer testing (Mock) 22
4.1 Accelerometer in Android . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
4.2 Testing del metodo onSensorChange . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
4.2.1 Mock della classe SensorEvent . . . . . . . . . . . . . . . . . . . . . . . . . . 26
4.2.2 Verifica del test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
4.3 Testing dei sensori in generale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
5 Android Activity Test 30
5.1 Obiettivi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
5.2 Testing delle Activity con i mock . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
5.2.1 Mock dei metodi di AppCompatActivity . . . . . . . . . . . . . . . . . . . . 35
5.3 Coverage della classe QuizActivity . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
6 Conclusioni 39
I
INDICE
7 Riferimenti 40
Tesina di IS2: Mock in Android II
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
CAPITOLO 5. ANDROID ACTIVITY TEST
24 refreshQuestion();
25 //per il sensore
26 mSensorManager = (SensorManager) Aca.getSystemService(Context.
SENSOR_SERVICE);
27 mSensorManager.registerListener(mSensorListener,mSensorManager.
getDefaultSensor(Sensor.TYPE_ACCELEROMETER),SensorManager.
SENSOR_DELAY_NORMAL);
28 mAccel = 0.00f;
29 mAccelCurrent = SensorManager.GRAVITY_EARTH;
30 mAccelLast = SensorManager.GRAVITY_EARTH;
31 //sound effect
32 mpCorrectAnswer = MediaPlayer.create(Aca, R.raw.correct_answer);
33 mpWrongAnswer = MediaPlayer.create(Aca, R.raw.wrong_answer);
34 //preference (dimensione testo) (Setting)
35 Context context = Aca.getApplicationContext();
36 SharedPreferences sharedPreferences = PreferenceManager.
getDefaultSharedPreferences(this);
37 setupSharedPreferences(context, sharedPreferences);
38
39 toastHint = Toast.makeText(Aca.getApplicationContext(), R.string.hint,
Toast.LENGTH_SHORT);
40 toastAns= Toast.makeText(Aca.getApplicationContext(), R.string.
correct_answer, Toast.LENGTH_SHORT);
41 }
Codice Componente 5.1: onCreate QuizActivity
Descrizione del caso di test (fig 5.2).
Tesina di IS2: Mock in Android 32
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
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
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
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
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
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
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
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

More Related Content

Similar to Software testing with mocking framework (Android App)

Openfisca Managing Tool: a tool to manage fiscal sistems
Openfisca Managing Tool: a tool to manage fiscal sistemsOpenfisca Managing Tool: a tool to manage fiscal sistems
Openfisca Managing Tool: a tool to manage fiscal sistemsLorenzo Stacchio
 
Realizzazione di un workflow integrato per la rilevazione di domini phishing
Realizzazione di un workflow integrato per la rilevazione di domini phishingRealizzazione di un workflow integrato per la rilevazione di domini phishing
Realizzazione di un workflow integrato per la rilevazione di domini phishingGiuliaMilan4
 
Studio e realizzazione di un sw per la gestione dei profili e delle versioni ...
Studio e realizzazione di un sw per la gestione dei profili e delle versioni ...Studio e realizzazione di un sw per la gestione dei profili e delle versioni ...
Studio e realizzazione di un sw per la gestione dei profili e delle versioni ...artemedea
 
Quiz Builder Executor (Web Application)
Quiz Builder Executor (Web Application)Quiz Builder Executor (Web Application)
Quiz Builder Executor (Web Application)gioacchinolonardo
 
Profilazione utente in ambienti virtualizzati
Profilazione utente in ambienti virtualizzatiProfilazione utente in ambienti virtualizzati
Profilazione utente in ambienti virtualizzatiPietro Corona
 
Progettazione e sviluppo di un'applicazione web per la gestione di dati di at...
Progettazione e sviluppo di un'applicazione web per la gestione di dati di at...Progettazione e sviluppo di un'applicazione web per la gestione di dati di at...
Progettazione e sviluppo di un'applicazione web per la gestione di dati di at...daniel_zotti
 
Uno studio sull'efficacia di checker automatici per la modernizzazione di cod...
Uno studio sull'efficacia di checker automatici per la modernizzazione di cod...Uno studio sull'efficacia di checker automatici per la modernizzazione di cod...
Uno studio sull'efficacia di checker automatici per la modernizzazione di cod...Idriss Riouak
 
Implementazione in Java di plugin Maven per algoritmi di addestramento per re...
Implementazione in Java di plugin Maven per algoritmi di addestramento per re...Implementazione in Java di plugin Maven per algoritmi di addestramento per re...
Implementazione in Java di plugin Maven per algoritmi di addestramento per re...Francesco Komauli
 
SESAMO (application login automator): evoluzioni applicative e considerazioni...
SESAMO (application login automator): evoluzioni applicative e considerazioni...SESAMO (application login automator): evoluzioni applicative e considerazioni...
SESAMO (application login automator): evoluzioni applicative e considerazioni...AndrijaCiric1
 
Analisi e sviluppo di un sistema collaborativo simultaneo per la modifica di ...
Analisi e sviluppo di un sistema collaborativo simultaneo per la modifica di ...Analisi e sviluppo di un sistema collaborativo simultaneo per la modifica di ...
Analisi e sviluppo di un sistema collaborativo simultaneo per la modifica di ...Filippo Muscolino
 
Montalti - "Context aware applications" (2011, master thesys ITA)
Montalti - "Context aware applications" (2011, master thesys ITA)Montalti - "Context aware applications" (2011, master thesys ITA)
Montalti - "Context aware applications" (2011, master thesys ITA)Alessandro Montalti
 
Progettazione e Sviluppo di un Sistema per Migliorare il Codice Generato da u...
Progettazione e Sviluppo di un Sistema per Migliorare il Codice Generato da u...Progettazione e Sviluppo di un Sistema per Migliorare il Codice Generato da u...
Progettazione e Sviluppo di un Sistema per Migliorare il Codice Generato da u...DamianoRavalico
 
Tesi Specialistica - L'ottimizzazione delle risorse della Grid di EGEE median...
Tesi Specialistica - L'ottimizzazione delle risorse della Grid di EGEE median...Tesi Specialistica - L'ottimizzazione delle risorse della Grid di EGEE median...
Tesi Specialistica - L'ottimizzazione delle risorse della Grid di EGEE median...Davide Ciambelli
 
Estrazione automatica di informazioni da documenti cartacei: progetto e reali...
Estrazione automatica di informazioni da documenti cartacei: progetto e reali...Estrazione automatica di informazioni da documenti cartacei: progetto e reali...
Estrazione automatica di informazioni da documenti cartacei: progetto e reali...Luca Bressan
 
Analisi e realizzazione di uno strumento per la verifica di conformità su sis...
Analisi e realizzazione di uno strumento per la verifica di conformità su sis...Analisi e realizzazione di uno strumento per la verifica di conformità su sis...
Analisi e realizzazione di uno strumento per la verifica di conformità su sis...Davide Bravin
 
Documentazione progetto software - IoSegnalo
Documentazione progetto software - IoSegnaloDocumentazione progetto software - IoSegnalo
Documentazione progetto software - IoSegnaloMarco Vaiano
 
Generazione automatica diagrammi di rete con template pptx
Generazione automatica diagrammi di rete con template pptxGenerazione automatica diagrammi di rete con template pptx
Generazione automatica diagrammi di rete con template pptxGiacomoZorzin
 
Studio e implementazione di uno strumento di configurazione e visualizzazione...
Studio e implementazione di uno strumento di configurazione e visualizzazione...Studio e implementazione di uno strumento di configurazione e visualizzazione...
Studio e implementazione di uno strumento di configurazione e visualizzazione...Matteo Miotto
 
Rilevamento di attacchi di rete tramite protocolli di monitoraggio per router...
Rilevamento di attacchi di rete tramite protocolli di monitoraggio per router...Rilevamento di attacchi di rete tramite protocolli di monitoraggio per router...
Rilevamento di attacchi di rete tramite protocolli di monitoraggio per router...Ce.Se.N.A. Security
 

Similar to Software testing with mocking framework (Android App) (20)

Openfisca Managing Tool: a tool to manage fiscal sistems
Openfisca Managing Tool: a tool to manage fiscal sistemsOpenfisca Managing Tool: a tool to manage fiscal sistems
Openfisca Managing Tool: a tool to manage fiscal sistems
 
Realizzazione di un workflow integrato per la rilevazione di domini phishing
Realizzazione di un workflow integrato per la rilevazione di domini phishingRealizzazione di un workflow integrato per la rilevazione di domini phishing
Realizzazione di un workflow integrato per la rilevazione di domini phishing
 
Studio e realizzazione di un sw per la gestione dei profili e delle versioni ...
Studio e realizzazione di un sw per la gestione dei profili e delle versioni ...Studio e realizzazione di un sw per la gestione dei profili e delle versioni ...
Studio e realizzazione di un sw per la gestione dei profili e delle versioni ...
 
Quiz Builder Executor (Web Application)
Quiz Builder Executor (Web Application)Quiz Builder Executor (Web Application)
Quiz Builder Executor (Web Application)
 
Profilazione utente in ambienti virtualizzati
Profilazione utente in ambienti virtualizzatiProfilazione utente in ambienti virtualizzati
Profilazione utente in ambienti virtualizzati
 
Progettazione e sviluppo di un'applicazione web per la gestione di dati di at...
Progettazione e sviluppo di un'applicazione web per la gestione di dati di at...Progettazione e sviluppo di un'applicazione web per la gestione di dati di at...
Progettazione e sviluppo di un'applicazione web per la gestione di dati di at...
 
Uno studio sull'efficacia di checker automatici per la modernizzazione di cod...
Uno studio sull'efficacia di checker automatici per la modernizzazione di cod...Uno studio sull'efficacia di checker automatici per la modernizzazione di cod...
Uno studio sull'efficacia di checker automatici per la modernizzazione di cod...
 
Implementazione in Java di plugin Maven per algoritmi di addestramento per re...
Implementazione in Java di plugin Maven per algoritmi di addestramento per re...Implementazione in Java di plugin Maven per algoritmi di addestramento per re...
Implementazione in Java di plugin Maven per algoritmi di addestramento per re...
 
SESAMO (application login automator): evoluzioni applicative e considerazioni...
SESAMO (application login automator): evoluzioni applicative e considerazioni...SESAMO (application login automator): evoluzioni applicative e considerazioni...
SESAMO (application login automator): evoluzioni applicative e considerazioni...
 
Analisi e sviluppo di un sistema collaborativo simultaneo per la modifica di ...
Analisi e sviluppo di un sistema collaborativo simultaneo per la modifica di ...Analisi e sviluppo di un sistema collaborativo simultaneo per la modifica di ...
Analisi e sviluppo di un sistema collaborativo simultaneo per la modifica di ...
 
Montalti - "Context aware applications" (2011, master thesys ITA)
Montalti - "Context aware applications" (2011, master thesys ITA)Montalti - "Context aware applications" (2011, master thesys ITA)
Montalti - "Context aware applications" (2011, master thesys ITA)
 
Progettazione e Sviluppo di un Sistema per Migliorare il Codice Generato da u...
Progettazione e Sviluppo di un Sistema per Migliorare il Codice Generato da u...Progettazione e Sviluppo di un Sistema per Migliorare il Codice Generato da u...
Progettazione e Sviluppo di un Sistema per Migliorare il Codice Generato da u...
 
Tesi Specialistica - L'ottimizzazione delle risorse della Grid di EGEE median...
Tesi Specialistica - L'ottimizzazione delle risorse della Grid di EGEE median...Tesi Specialistica - L'ottimizzazione delle risorse della Grid di EGEE median...
Tesi Specialistica - L'ottimizzazione delle risorse della Grid di EGEE median...
 
Estrazione automatica di informazioni da documenti cartacei: progetto e reali...
Estrazione automatica di informazioni da documenti cartacei: progetto e reali...Estrazione automatica di informazioni da documenti cartacei: progetto e reali...
Estrazione automatica di informazioni da documenti cartacei: progetto e reali...
 
Analisi e realizzazione di uno strumento per la verifica di conformità su sis...
Analisi e realizzazione di uno strumento per la verifica di conformità su sis...Analisi e realizzazione di uno strumento per la verifica di conformità su sis...
Analisi e realizzazione di uno strumento per la verifica di conformità su sis...
 
Documentazione progetto software - IoSegnalo
Documentazione progetto software - IoSegnaloDocumentazione progetto software - IoSegnalo
Documentazione progetto software - IoSegnalo
 
Generazione automatica diagrammi di rete con template pptx
Generazione automatica diagrammi di rete con template pptxGenerazione automatica diagrammi di rete con template pptx
Generazione automatica diagrammi di rete con template pptx
 
TesiEtta
TesiEttaTesiEtta
TesiEtta
 
Studio e implementazione di uno strumento di configurazione e visualizzazione...
Studio e implementazione di uno strumento di configurazione e visualizzazione...Studio e implementazione di uno strumento di configurazione e visualizzazione...
Studio e implementazione di uno strumento di configurazione e visualizzazione...
 
Rilevamento di attacchi di rete tramite protocolli di monitoraggio per router...
Rilevamento di attacchi di rete tramite protocolli di monitoraggio per router...Rilevamento di attacchi di rete tramite protocolli di monitoraggio per router...
Rilevamento di attacchi di rete tramite protocolli di monitoraggio per router...
 

Software testing with mocking framework (Android App)

  • 1. Tesina di Ingegneria del software 2 Progetto: Mock in Android Studente: Lonardo Gioacchino Mat: M63/000567 19 aprile 2017
  • 2. Indice 1 Mocking Framework 1 1.1 L’uso dei Mocking framework . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.2 User Interface Test e Unit Test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 1.3 Unit Test in Android . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.3.1 Local Unit test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 2 Mockito 5 2.1 Configurazione con Android Studio . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 2.2 Utilizzo di Mockito insieme a JUnit4 . . . . . . . . . . . . . . . . . . . . . . . . . . 6 2.3 Mockito method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 2.4 Hello world! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 2.5 Mock di java.util.Random . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 2.6 Lanciare eccezioni . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 2.7 Vantaggi e svantaggi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 3 PowerMock 16 3.1 Configurazione con Android Studio . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 3.2 Utilizzo di PowerMock . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 3.3 Whitebox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 3.4 Mock e Spy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 3.5 PowerMockito Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 3.6 Vantaggi e svantaggi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 4 Accelerometer testing (Mock) 22 4.1 Accelerometer in Android . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 4.2 Testing del metodo onSensorChange . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 4.2.1 Mock della classe SensorEvent . . . . . . . . . . . . . . . . . . . . . . . . . . 26 4.2.2 Verifica del test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 4.3 Testing dei sensori in generale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 5 Android Activity Test 30 5.1 Obiettivi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 5.2 Testing delle Activity con i mock . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 5.2.1 Mock dei metodi di AppCompatActivity . . . . . . . . . . . . . . . . . . . . 35 5.3 Coverage della classe QuizActivity . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 6 Conclusioni 39 I
  • 3. INDICE 7 Riferimenti 40 Tesina di IS2: Mock in Android II
  • 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
  • 35. CAPITOLO 5. ANDROID ACTIVITY TEST 24 refreshQuestion(); 25 //per il sensore 26 mSensorManager = (SensorManager) Aca.getSystemService(Context. SENSOR_SERVICE); 27 mSensorManager.registerListener(mSensorListener,mSensorManager. getDefaultSensor(Sensor.TYPE_ACCELEROMETER),SensorManager. SENSOR_DELAY_NORMAL); 28 mAccel = 0.00f; 29 mAccelCurrent = SensorManager.GRAVITY_EARTH; 30 mAccelLast = SensorManager.GRAVITY_EARTH; 31 //sound effect 32 mpCorrectAnswer = MediaPlayer.create(Aca, R.raw.correct_answer); 33 mpWrongAnswer = MediaPlayer.create(Aca, R.raw.wrong_answer); 34 //preference (dimensione testo) (Setting) 35 Context context = Aca.getApplicationContext(); 36 SharedPreferences sharedPreferences = PreferenceManager. getDefaultSharedPreferences(this); 37 setupSharedPreferences(context, sharedPreferences); 38 39 toastHint = Toast.makeText(Aca.getApplicationContext(), R.string.hint, Toast.LENGTH_SHORT); 40 toastAns= Toast.makeText(Aca.getApplicationContext(), R.string. correct_answer, Toast.LENGTH_SHORT); 41 } Codice Componente 5.1: onCreate QuizActivity Descrizione del caso di test (fig 5.2). Tesina di IS2: Mock in Android 32
  • 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