2010-06-22 9 views
29

Ho implementato l'accesso a un database utilizzando SQLiteOpenHelper dal pacchetto android.database all'interno di alcune classi (con pattern DAO).Test del database su Android: ProviderTestCase2 o RenamingDelegatingContext?

Ho scritto alcuni test di junit per queste classi utilizzando un AndroidTestCase ma questo fa sì che i test utilizzino lo stesso database dell'applicazione.

Ho letto che il ProviderTestCase2 o RenamingDelegatingContext può essere utilizzato per testare il database separatamente. Sfortunatamente non ho trovato nessun tutorial/esempio che mostra come testare un database con ProviderTestCase2/RenamingDelegatingContext.

Qualcuno può indicarmi da qualche parte O darmi qualche consiglio O condividere qualche codice per il test del database ?!

Cheeerrrrss !! Giorgio

+0

[Test database Android in stile JUnit4] (http://www.singhajit.com/testing-android-database/) –

risposta

21

Sia il numero ProviderTestCase e RenamingDelegatingContext distruggeranno il database se ne esiste già uno prima di aprirlo nel suo contesto, quindi in questo senso hanno entrambi lo stesso approccio di basso livello verso l'apertura di un database SQLite.

È possibile sfruttare questo vantaggio aprendo il database nell'attrezzatura in setUp(), che garantirà quindi il funzionamento con un nuovo database prima di ogni caso di test.

Suggerirei di andare a scrivere provider di contenuti invece di creare adattatori di database. È possibile utilizzare un'interfaccia comune per l'accesso ai dati, sia archiviata nel DB o da qualche parte sulla rete, la progettazione dei fornitori di contenuti può essere adattata per accedere a tali dati al costo di un po 'di spese generali IPC coinvolte che la maggior parte di noi non dovrebbe devo preoccuparmi di

Se l'accesso è stato eseguito per accedere a un database SQLite, il framework gestirà completamente la connessione al database in un processo separato. Come manzo aggiunto, lo ProviderTestCase2<ContentProvider> avvia automaticamente un contesto di test per il provider di contenuti senza dover scrivere una singola riga di codice.

Ma, questo non è detto che non è uno sforzo enorme per fare il bootstrap te stesso. Quindi supponendo di avere un adattatore per database come tale; ci limiteremo a concentriamo su open() per ottenere l'accesso in scrittura al nostro database, niente di speciale:

public class MyAdapter { 

    private static final String DATABASE_NAME = "my.db"; 
    private static final String DATABASE_TABLE = "table"; 
    private static final int DATABASE_VERSION = 1; 


    /** 
    * Database queries 
    */ 
    private static final String DATABASE_CREATE_STATEMENT = "some awesome create statement"; 

    private final Context mCtx; 
    private SQLiteDatabase mDb; 
    private DatabaseHelper mDbHelper; 

    private static class DatabaseHelper extends SQLiteOpenHelper { 

     public DatabaseHelper(Context context) { 
      super(context, DATABASE_NAME, null, DATABASE_VERSION); 
     } 

     @Override 
     public void onCreate(SQLiteDatabase db) { 
      db.execSQL(DATABASE_CREATE_STATEMENT); 
     } 

     @Override 
     public void onUpgrade(SQLiteDatabase db, int a, int b) { 
      // here to enable this code to compile 
     } 
    } 

    /** 
    * Constructor - takes the provided context to allow for the database to be 
    * opened/created. 
    * 
    * @param context the Context within which to work. 
    */ 
    public MyAdapter(Context context) { 
     mCtx = context; 
    } 

    /** 
     * Open the last.fm database. If it cannot be opened, try to create a new 
     * instance of the database. If it cannot be created, throw an exception to 
     * signal the failure. 
     * 
     * @return this (self reference, allowing this to be chained in an 
     *   initialization call) 
     * @throws SQLException if the database could be neither opened or created 
     */ 
    public MyAdapter open() throws SQLException { 
     mDbHelper = new DatabaseHelper(mCtx); 
     mDb = mDbHelper.getWritableDatabase(); 
     return this; 
    } 

    public void close() { 
      mDbHelper.close(); 
     } 

} 

allora si potrebbe scrivere il test in quanto tale:

public final class MyAdapterTests extends AndroidTestCase { 

    private static final String TEST_FILE_PREFIX = "test_"; 
private MyAdapter mMyAdapter; 

@Override 
protected void setUp() throws Exception { 
    super.setUp(); 

    RenamingDelegatingContext context 
     = new RenamingDelegatingContext(getContext(), TEST_FILE_PREFIX); 

    mMyAdapter = new MyAdapter(context); 
    mMyAdapter.open(); 
} 

@Override 
protected void tearDown() throws Exception { 
    super.tearDown(); 

    mMyAdapter.close(); 
    mMyAdapter = null; 
} 

public void testPreConditions() { 
    assertNotNull(mMyAdapter); 
} 

} 

Quindi, cosa sta succedendo qui è che l'attuazione contesto di RenamingDelegatingContext, una volta chiamato MyAdapter(context).open(), verrà sempre ricreato il database. Ogni test che scrivi ora andrà contro lo stato del database dopo che è stato chiamato lo MyAdapter.DATABASE_CREATE_STATEMENT.

+0

Mi stavo prendendo a calci per non aver guidato la rotta ContentProvider dall'inizio, ma sono andato avanti e ho fatto uno. Ora torniamo ai test ... – LostNomad311

+0

Cosa significa "RenamingDelegatingContext"? Perché stiamo usando questo? Creerà un nuovo database per ciascun metodo? –

+0

Trovo che quando si esegue un 'AndroidTestCase' /' RenamingDelegatingContext' insieme con un 'ActivityInstrumentationTestCase2' che il database persista dal test al test. L'esecuzione solo di 'AndroidTestCase' ricrea ogni volta il DB. – Tad

-1

Una possibile soluzione può essere quella di aprire il database usando questo metodo

myDataBase = SQLiteDatabase.openDatabase(DATABASE_NAME, null, SQLiteDatabase.OPEN_READWRITE); 

E cambiare il nome del database nei test. Here puoi trovare alcune informazioni su questo metodo.

1

Ho un'applicazione che utilizza un ContentProvider supportato da un database SQLite per fornire dati all'applicazione.

Consentire a PodcastDataProvider di essere il dataprovider effettivo utilizzato dall'applicazione.

Poi si può impostare un fornitore di test con qualcosa come il seguente:

public abstract class AbstractPodcastDataProvider extends ProviderTestCase2<PodcastDataProvider>{ 
    public AbstractPodcastDataProvider(){ 
     this(PodcastDataProvider.class, Feed.BASE_AUTH); 
    } 

    public AbstractPodcastDataProvider(Class<PodcastDataProvider> providerClass, 
      String providerAuthority) { 
     super(providerClass, providerAuthority); 
    } 

    public void setUp() throws Exception{ 
     super.setUp(); 

     //clear out all the old data. 
     PodcastDataProvider dataProvider = 
      (PodcastDataProvider)getMockContentResolver() 
      .acquireContentProviderClient(Feed.BASE_AUTH) 
      .getLocalContentProvider(); 
     dataProvider.deleteAll(); 
    } 
} 

di impostare un fornitore di dati di test che sarà supportato da un database diverso da quello della effettiva applicazione.

Per verificare il DAO, creare un'altra classe che estende AbstractPodcastDataProvider e impiegare il metodo

getMockContentResolver(); 

per ottenere un'istanza di un resolver contenuto che utilizzerà il database di test invece del database dell'applicazione.

0
private static String db_path = "/data/data/android.testdb/mydb"; 
private SQLiteDatabase sqliteDatabase = null; 
private Cursor cursor = null; 
private String[] fields; 

/* 
* (non-Javadoc) 
* 
* @see dinota.data.sqlite.IDataContext#getSQLiteDatabase() 
*/ 
public SQLiteDatabase getSQLiteDatabase() { 
    try { 

     sqliteDatabase = SQLiteDatabase.openDatabase(db_path, null, 
       SQLiteDatabase.OPEN_READWRITE); 
     sqliteDatabase.setVersion(1); 
     sqliteDatabase.setLocale(Locale.getDefault()); 
     sqliteDatabase.setLockingEnabled(true); 
     return sqliteDatabase; 
    } catch (Exception e) { 
     return null; 
    } 

} 

se si dà la posizione esatta del db sqlite (nel mio caso è db_path), utilizzando il metodo di cui sopra si possono trovare-out se si restituisce uno SQLiteDatabase o meno.

6

In realtà utilizzo il database con SQLiteOpenHelper e ho un trucco per il test. L'idea è di utilizzare il DB memorizzato su file standard durante il normale utilizzo dell'app e un DB in memoria durante i test. In questo modo è possibile utilizzare un DB chiaro per ciascun test senza inserire/eliminare/aggiornare i dati nel DB standard. Funziona bene per me.

Ricordare che è possibile utilizzare il database in memoria, passando semplicemente null come nome del file di database. Questo è chiaramente documentato nella documentazione dell'API.

I vantaggi di utilizzare in memoria DB durante i test è spiegato qui: https://attakornw.wordpress.com/2012/02/25/using-in-memory-sqlite-database-in-android-tests/

Nel mio progetto che ho la classe wich DBHelper estende SQLiteHelper. Come puoi vedere, ci sono i metodi standard. Ho semplicemente aggiunto un costruttore con due parametri. La differenza è che quando chiamo il super costruttore, passo null come nome DB.

public class DBHelper extends SQLiteOpenHelper { 

    public static final int DATABASE_VERSION = 1; 
    public static final String DATABASE_NAME = "mydatabase.db"; 

    public DBHelper(Context context) { 
     super(context, DATABASE_NAME, null, DATABASE_VERSION); 
    } 

    public DBHelper(Context context, boolean testMode) { 
     super(context, null, null, DATABASE_VERSION); 
    } 

    public void onCreate(SQLiteDatabase db) { 
     //create statements 
    } 

    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 
     //on upgrade policy 
    } 

    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { 
     //on downgrade policy 
    } 
} 

Ogni "modello" nel progetto estende DBModel che è una classe astratta.

public abstract class DBModel { 
    protected DBHelper dbhelper; 

    public DBModel(Context context) { 
     dbhelper = new DBHelper(context); 
    } 

    //other declarations and utility function omitted 

} 

come discusso qui: How can I find out if code is running inside a JUnit test or not? c'è un modo per stabilire se si esegue test JUnit, semplicemente alla ricerca di elementi in traccia dello stack. Come conseguence, ho modificato DBModel costruttore

public abstract class DBModel { 
    protected DBHelper dbhelper; 

    public DBModel(Context context) { 
     if(isJUnitTest()) { 
      dbhelper = new DBHelper(context, true); 
     } else { 
      dbhelper = new DBHelper(context); 
     } 
    } 

    private boolean isJUnitTest() { 
     StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); 
     List<StackTraceElement> list = Arrays.asList(stackTrace); 
     for (StackTraceElement element : list) { 
      if (element.getClassName().startsWith("junit.")) { 
       return true; 
      } 
     } 
     return false; 
    } 

    //other declarations and utility function omitted 

} 

Nota che

startsWith("junit.") 

possono essere

startsWith("org.junit.") 

nel tuo caso.

+0

Grazie per alcune idee e suggerimenti eccellenti. – JulianHarty