2015-04-08 14 views
10

Voglio testare all'interno di un test dell'unità se viene attivato o meno un allarme programmato utilizzando AlarmManager e, in caso affermativo, se viene attivato entro il periodo corretto.È possibile registrare un ricevitore in un caso di test?

Ecco la classe del ricevitore da testare. L'ho creato all'interno del mio progetto di test. (NOTA: non è registrata nel manifesto)

public class MockBroadcastReceiver extends BroadcastReceiver { 

    private static int numTimesCalled = 0; 

    MockBroadcastReceiver(){ 
     numTimesCalled = 0; 
    } 

    @Override 
    public void onReceive(Context context, Intent intent) { 
     numTimesCalled++;   
    } 

    public static int getNumTimesCalled() { 
     return numTimesCalled; 
    } 

    public static void setNumTimesCalled(int numTimesCalled) { 
     MockBroadcastReceiver.numTimesCalled = numTimesCalled; 
    } 
} 

Ed ecco la prova di unità. Il metodo programReceiver appartiene effettivamente a una classe nel progetto principale, ma l'ho incluso all'interno del test in modo che non sia necessario leggere così tanto codice.

public class ATest extends AndroidTestCase { 

    MockBroadcastReceiver mockReceiver; 

    @Override 
    protected void setUp() throws Exception { 
     mockReceiver = new MockBroadcastReceiver(); 
     getContext().registerReceiver(mockReceiver, new IntentFilter()); 
    } 

    @Override 
    protected void tearDown() {  
     getContext().unregisterReceiver(mockReceiver); 
     mockReceiver = null; 
    } 


    public void test(){ 
     //We're going to program twice and check that only the last 
     //programmed alarm should remain active. 

     final Object flag = new Object(); 
     MockBroadcastReceiver.setNumTimesCalled(0); 

     new Thread(){ 
      @Override 
      public void run(){ 
       programReceiver(getContext(), MockBroadcastReceiver.class, 60000, 60000); 

       SystemClock.sleep(20000); 

       programReceiver(getContext(), MockBroadcastReceiver.class, 60000, 60000); 

       SystemClock.sleep(90000); 

       synchronized(flag){ 
        flag.notifyAll(); 
       } 
      } 
     }.start(); 

     synchronized(flag){ 
      try { 
       flag.wait(); 
      } catch (InterruptedException e) { 
      } 
     } 

     assertEquals(1, MockBroadcastReceiver.getNumTimesCalled()); //should have been called at least once, but its 0. 
    } 


    private static void programReceiver(Context context, Class<? extends BroadcastReceiver> receiverClass, long initialDelay, long period){ 
     Intent intent = new Intent(context, receiverClass); 
     PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); 

     AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);  

     alarmManager.cancel(pendingIntent); //Cancel any previous alarm 

     alarmManager.setInexactRepeating (
      AlarmManager.RTC_WAKEUP, 
      System.currentTimeMillis() + initialDelay, 
      period, 
      pendingIntent 
     ); 
    } 
} 

Quando eseguo il metodo test, il ricevitore avrebbe dovuto essere registrato dinamicamente nel setUp. Quindi programmo lo stesso allarme due volte. Il mio intento era di verificare che solo l'ultimo allarme rimanesse attivo, ma ho problemi a far chiamare il ricevitore. Il test fallisce in quanto dovrebbe essere chiamato una volta (o almeno un numero di volte> = 1), ma il contatore nel ricevitore fittizio è 0. Ho impostato un punto di interruzione nel metodo onReceive e non viene mai colpito . Ho anche aggiunto la registrazione e nulla viene mostrato nel logcat. Quindi sono sicuro al 100% che il ricevitore non venga chiamato. Ho anche provato ad aumentare il tempo di sospensione nel thread, perché setInexactRepeating si attiva in modo inesatto, ma posso aspettare per anni e non viene ancora chiamato.

Ho anche provato a registrarlo nel manifest del progetto di test anziché a livello di programmazione e i risultati sono gli stessi.

Perché il ricevitore non viene chiamato?


UPDATE
posso confermare il AlarmManager non è il problema. Gli allarmi sono registrati correttamente in base all'allarme dump di adb.

Ora sto provando a far funzionare il ricevitore chiamando sendBroadcast, ma sono in un vicolo cieco. Il ricevitore non verrà chiamato. Ho provato il contesto dell'app principale, il contesto del test case, anche ActivityInstrumentationTestCase2. Ho provato anche ad aggiungere WakeLock e niente. Non c'è proprio modo di farlo chiamare. Penso che questo potrebbe essere causato da alcune bandiere nel filtro intent o intent (Android sembra essere davvero pignolo con le bandiere).

risposta

0

Quindi la risposta è sì, è possibile, MA i ricevitori funzionano solo con Intenti e IntentFilters impliciti (basati su azioni).

Gli intent espliciti (basati su classi, senza filtri) non funzionano con i ricevitori registrati dinamicamente. Affinché gli intent espliciti funzionino, è necessario registrare il ricevitore nel manifest e non in modo dinamico come stavo cercando di fare.

Questa è una delle funzionalità più oscure e meno documentate di Android. Nella mia esperienza di intenti, non puoi mai essere sicuro. Ed è anche molto difficile da diagnosticare, in quanto non vi è alcun avviso stampato nel logcat che possa aiutare a capire il problema. Complimenti per la risposta di @ d2vid qui: https://stackoverflow.com/a/19482865

Quindi per risolvere il mio codice dovrei aggiungere l'elemento ricevitore all'interno del tag dell'applicazione nel manifest. Dato che il ricevitore è una classe di ricevitore fittizio creata all'interno del progetto di test, dovrei modificare il manifest del progetto di test, non quello principale del progetto. Ma d'altra parte, nei progetti di test Eclipse, i tag del ricevitore aggiunti al manifest non sembrano funzionare. Android Studio sembra più adatto per la fusione manifest, ma questo è un progetto precedente avviato in Eclipse e non lo stiamo effettuando.

In conclusione: il test di intent esplicito è interrotto in Eclipse per quei ricevitori che non esistono nel progetto principale. La classe che dovevo testare utilizza solo intenti espliciti, quindi il mio test non può essere scritto in Eclipse.

0

Non sono sicuro del motivo per cui il test non funziona come previsto. Mi viene in mente un paio di suggerimenti:

  1. Gran parte del documento che ho visto, ad es. [1], suggerisce che Object.wait() debba sempre essere chiamato all'interno di un ciclo basato su una condizione che non regge. Il tuo codice non lo fa. Forse potresti provare a rielaborarlo in modo che lo faccia.
  2. Per completezza, forse dovresti produrre qualcosa per lo InterruptedException che può essere lanciato da flag.wait() per ogni evenienza.

[1] https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#wait--

+0

Mentre sono d'accordo sul fatto che il codice di sincronizzazione potrebbe essere migliore, funzionerà nel 99,99% dei casi ed è ok per un test dell'unità sporca. –

2

Nel codice sorgente android c'è un alarmManagerTest che esegue alarmManager.setInexactRepeating con un broadcastreceiver.

La differenza principale è che il test di Android funziona con un ritardo di 15 minuti mentre il test utilizza un ritardo di 1 minuto.

La documentazione Android per AlarmManager dice: intervallo

public void setInexactRepeating (int type, long triggerAtMillis, long intervalMillis, PendingIntent operation) 

... intervalMillis in millisecondi tra le successive ripetizioni della sveglia. Prima dell'API 19, se questo è INTERVAL_FIFTEEN_MINUTES, INTERVAL_HALF_HOUR, INTERVAL_HOUR, INTERVAL_HALF_DAY o INTERVAL_DAY, l'allarme sarà allineato in fase con altri allarmi per ridurre il numero di wakeup. In caso contrario, l'allarme verrà impostato come se l'applicazione avesse chiamato setRepeating (int, long, long, PendingIntent). A partire dall'API 19, tutti gli allarmi ripetuti saranno inesatti e soggetti a batch con altri allarmi indipendentemente dal loro intervallo di ripetizione dichiarato.

Non sono sicuro se questo significa che sono consentiti solo i multipli di 15 minuti.

Sul mio telefono Android 2.2 il timer inesatto funziona solo se è un multiplo di 15 minuti.

+0

AlarmManager non è un problema. C'è qualcosa a un livello più basso che impedisce al ricevitore di essere chiamato. Leggi il mio aggiornamento. –

+0

Nonostante non sia la risposta corretta, la taglia è ben meritata in quanto il test che hai collegato mi ha mostrato che l'unica differenza dal mio codice era l'uso di intenti impliciti e filtri di intent. –

Problemi correlati