Тестирование базы данных на Android: ProviderTestCase2 или RenamingDelegatingContext? - PullRequest
29 голосов
/ 22 июня 2010

Я реализовал доступ к базе данных, используя SQLiteOpenHelper из пакета android.database в некоторых классах (с шаблоном DAO).

Я написал несколько тестов junit для этих классов, используя AndroidTestCase, ноиз-за этого тесты используют ту же базу данных, что и приложение.

Я прочитал, что ProviderTestCase2 или RenamingDelegatingContext можно использовать для отдельного тестирования базы данных.К сожалению, я не смог найти ни одного хорошего учебника / примера, который показывает, как протестировать базу данных с помощью ProviderTestCase2 / RenamingDelegatingContext.

Может кто-нибудь указать мне где-нибудь ИЛИ дать мне какой-нибудь совет ИЛИ поделиться кодом для тестирования базы данных?*

Cheeerrrrsss !!Giorgio

Ответы [ 5 ]

21 голосов
/ 11 октября 2011

И ProviderTestCase, и RenamingDelegatingContext уничтожат базу данных, если она уже существует, прежде чем открывать ее в своем контексте, поэтому в этом смысле они оба используют один и тот же низкоуровневый подход к открытию базы данных SQLite.

Вы используете это, открывая базу данных в своем приборе в setUp(), что обеспечит вашу работу со свежей базой данных перед каждым тестовым примером.

Я бы посоветовал вам пойти на написание поставщиков контента, а нечем создание адаптеров базы данных.Вы можете использовать общий интерфейс для доступа к данным, независимо от того, хранятся ли они в БД или где-то по сети, дизайн поставщиков контента может быть приспособлен для доступа к таким данным за счет небольшого количества накладных расходов IPC, что большинство из нас не должно 'не нужно заботиться.

Если бы вы сделали это для доступа к базе данных SQLite, среда полностью управляла бы для вас подключением к базе данных в отдельном процессе.Как добавлено, ProviderTestCase2<ContentProvider> полностью загружает тестовый контекст для вашего контент-провайдера без необходимости написания одной строки кода.

Но это не значит, что это не такая уж большая работаСамозагрузка себя.Итак, предположим, что у вас был адаптер базы данных как таковой;мы просто сосредоточимся на open() для получения доступа на запись в нашу базу данных, ничего особенного:

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();
        }

}

Тогда вы можете написать свой тест следующим образом:

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);
}

}

Итак, что происходитздесь то, что контекстная реализация RenamingDelegatingContext, после вызова MyAdapter(context).open(), всегда будет воссоздавать базу данных.Каждый тест, который вы пишете сейчас, будет идти против состояния базы данных после вызова MyAdapter.DATABASE_CREATE_STATEMENT.

6 голосов
/ 17 ноября 2015

Я фактически использую базу данных с SQLiteOpenHelper, и у меня есть хитрость для тестирования. Идея состоит в том, чтобы использовать стандартные хранящиеся в файле БД при обычном использовании приложения и БД в памяти во время тестов. Таким образом, вы можете использовать чистую БД для каждого теста без вставки / удаления / обновления данных в вашей стандартной БД. У меня отлично работает.

Имейте в виду, что вы можете использовать базу данных в памяти, просто передавая null в качестве имени файла базы данных. Это четко задокументировано в документации API.

Преимущества использования БД в памяти во время тестов объясняются здесь: https://attakornw.wordpress.com/2012/02/25/using-in-memory-sqlite-database-in-android-tests/

В моем проекте у меня есть класс DBHelper, который расширяет SQLiteHelper. Как видите, есть стандартные методы. Я просто добавил конструктор с двумя параметрами. Разница в том, что когда я вызываю супер-конструктор, я передаю ноль в качестве имени БД.

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
    }
}

Каждая «модель» в проекте расширяет DBModel, который является абстрактным классом.

public abstract class DBModel {
    protected DBHelper dbhelper;

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

    //other declarations and utility function omitted

}

Как обсуждено здесь: Как я могу узнать, выполняется ли код внутри теста JUnit или нет? есть способ установить, выполняете ли вы тесты JUnit, просто выполняя поиск в элементах трассировки стека. Как следствие, я изменил конструктор 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

}

Обратите внимание, что

startsWith("junit.")

может быть

startsWith("org.junit.")

в вашем случае.

1 голос
/ 27 мая 2011

У меня есть приложение, которое использует ContentProvider, поддерживаемый базой данных sqlite, для предоставления данных приложению.

Пусть PodcastDataProvider будет фактическим провайдером данных, используемым приложением.

Затем вы можете настроить тестового провайдера с чем-то вроде следующего:

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();
    }
}

для настройки поставщика тестовых данных, который будет поддерживаться базой данных, отличной от фактического приложения.

Чтобы протестировать DAO, создайте другой класс, который расширяет AbstractPodcastDataProvider и используйте

getMockContentResolver();

метод для получения экземпляра распознавателя контента, который будет использовать тестовую базу данных вместо базы данных приложения.

0 голосов
/ 10 июня 2011
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;
    }

}

если вы укажете точное местоположение базы данных sqlite (в моем случае это db_path), с помощью описанного выше метода вы можете узнать, возвращает ли она базу данных sqlitedatabase или нет.

0 голосов
/ 14 января 2011

Возможным решением может быть открытие базы данных с использованием этого метода

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

и изменение имени базы данных в ваших тестах. Здесь вы можете найти информацию об этом методе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...