studio - sqlite room android
Prueba de base de datos en Android: ProviderTestCase2 o RenamingDelegatingContext? (5)
Implementé el acceso a una base de datos usando SQLiteOpenHelper
del paquete android.database dentro de algunas clases (con el patrón DAO).
Escribí algunas pruebas junit para estas clases usando una AndroidTestCase
pero esto hace que las pruebas utilicen la misma base de datos que la aplicación.
Leí que ProviderTestCase2
o RenamingDelegatingContext
se pueden usar para probar la base de datos por separado. Desgraciadamente, no pude encontrar ningún buen tutorial / ejemplo que muestre cómo probar una base de datos con ProviderTestCase2 / RenamingDelegatingContext.
¿Alguien puede señalarme en algún lugar O darme un consejo O compartir un código para la prueba de base de datos ?!
Cheeerrrrsss !! Giorgio
De hecho, uso la base de datos con SQLiteOpenHelper y tengo un truco para probar. La idea es utilizar el DB almacenado en el archivo estándar durante el uso normal de la aplicación y un DB en memoria durante las pruebas. De esta forma, puede usar una base de datos clara para cada prueba sin insertar / eliminar / actualizar datos en su base de datos estándar. Funciona bien para mí.
Tenga en cuenta que puede usar la base de datos en memoria, simplemente pasando nulo como nombre del archivo de la base de datos. Esto está claramente documentado en la documentación de la API.
Aquí se explican las ventajas de utilizar DB en memoria durante las pruebas: https://attakornw.wordpress.com/2012/02/25/using-in-memory-sqlite-database-in-android-tests/
En mi proyecto tengo la clase DBHelper que extiende SQLiteHelper. Como puede ver, están los métodos estándar. Simplemente agregué un constructor con dos parámetros. La diferencia es que cuando llamo al super constructor, paso nulo como nombre de 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
}
}
Cada "modelo" en el proyecto extiende DBModel que es una clase abstracta.
public abstract class DBModel {
protected DBHelper dbhelper;
public DBModel(Context context) {
dbhelper = new DBHelper(context);
}
//other declarations and utility function omitted
}
Como se discutió aquí: ¿Cómo puedo averiguar si el código se ejecuta dentro de una prueba JUnit o no? hay una manera de establecer si está ejecutando pruebas JUnit, simplemente buscando en los elementos traza de la pila. Como consecuencia, modifiqué el constructor de DBModel
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
}
Tenga en cuenta que
startsWith("junit.")
tal vez
startsWith("org.junit.")
en tu caso.
Tanto ProviderTestCase
como RenamingDelegatingContext
destruirán la base de datos si ya existe antes de abrirla dentro de su contexto, por lo que en ese sentido ambos tienen el mismo enfoque de bajo nivel para abrir una base de datos SQLite.
Aproveche esto abriendo la base de datos en su dispositivo en setUp()
, que luego garantizará que trabaje con una nueva base de datos antes de cada caso de prueba.
Sugiero que vaya a escribir proveedores de contenido en lugar de crear adaptadores de bases de datos. Puede usar una interfaz común para acceder a los datos, ya sea almacenada en el DB o en alguna parte de la red, el diseño de los proveedores de contenido puede ser acomodado para acceder a tales datos a costa de un poco de gastos generales de IPC que la mayoría de nosotros no debería tengo que preocuparme.
Si hizo esto para acceder a una base de datos SQLite, el marco gestionaría completamente la conexión de la base de datos para usted en un proceso separado. Como agregado de carne, el ProviderTestCase2<ContentProvider>
completamente un contexto de prueba para su proveedor de contenido sin tener que escribir una sola línea de código.
Pero, no se dice que no sea un esfuerzo tan grande hacer la iniciación usted mismo. Entonces, supongamos que tiene un adaptador de base de datos como tal; nos centraremos en open()
para obtener acceso de escritura a nuestra base de datos, nada extravagante:
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();
}
}
Entonces podrías escribir tu prueba como tal:
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);
}
}
Entonces, lo que sucede aquí es que la implementación de contexto de MyAdapter(context).open()
, una vez que se MyAdapter(context).open()
, siempre recreará la base de datos. Cada prueba que escriba ahora irá en contra del estado de la base de datos después de que se MyAdapter.DATABASE_CREATE_STATEMENT
.
Tengo una aplicación que utiliza ContentProvider respaldado por una base de datos sqlite para proporcionar datos a la aplicación.
Deje que PodcastDataProvider sea el proveedor de datos real utilizado por la aplicación.
Luego puede configurar un proveedor de prueba con algo como lo siguiente:
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();
}
}
para configurar un proveedor de datos de prueba que estará respaldado por una base de datos diferente a la aplicación real.
Para probar el DAO, cree otra clase que extienda AbstractPodcastDataProvider y use el
getMockContentResolver();
método para obtener una instancia de un solucionador de contenido que usará la base de datos de prueba en lugar de la base de datos de la aplicación.
Una posible solución puede ser abrir la base de datos usando este método
myDataBase = SQLiteDatabase.openDatabase(DATABASE_NAME, null, SQLiteDatabase.OPEN_READWRITE);
Y cambie el nombre de la base de datos en sus pruebas. Here puedes encontrar información sobre este método.
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;
}
}
si proporciona la ubicación exacta de sqlite db (en mi caso es db_path), utilizando el método anterior puede averiguar si devuelve una sqlitedatabase o no.