Ошибки при тестировании хранилища базы данных - SQLBrite, SQLDelight
При тестировании моей БД я получаю следующие ошибки:
- SQLiteDiskIOException: ошибка ввода-вывода диска (HTC Desire 620)
- SQLiteReadOnlyDatabaseException: попытка написать базу данных только для чтения (Moto g2)
В зависимости, видимо, от устройства, на котором я тестирую. Ошибка не происходит, когда я запускаю приложение. Тем не менее, если я не могу протестировать приложение, возможно, что-то не так с моим кодом.
Приложение использует две библиотеки, которые должны хорошо сочетаться с SQLDelight и SQLBrite, что может сделать этот вопрос несколько конкретным.
Для лучшего понимания происходящего я приведу краткое описание файлов в моем пакете данных.
-+-data-+
| |-manager-+
| | |-LocationManager
| | |-RunManager
| |-model-+
| | |-Location
| | |-Run
|-DatabaseContract
|-DataRepository
|-MyDBHelper
Файлы Location
а также Run
являются моделями строк, сгенерированными SQLDelight. LocationManager
а также RunManager
включить генерацию sqlStatements для вставки или удаления данных из соответствующей таблицы. Ниже RunManager
LocationMangager
выглядит похожим.
public class RunManager {
public final Run.InsertRun insertRace;
public final Run.DeleteRun deleteRun;
public final Run.DeleteRunWhereTimeSmallerThan deleteRunWhereTimeSmallerThan;
public RunManager(SQLiteDatabase db) {
insertRace = new Run.InsertRun(db);
deleteRun = new Run.DeleteRun(db);
deleteRunWhereTimeSmallerThan = new Run.DeleteRunWhereTimeSmallerThan(db);
}
}
MyDBHelper расширяет SQLiteOpenHelper стандартным способом.
public class MyDbHelper extends SQLiteOpenHelper {
private static final int DATABASE_VERSION = 1;
public static final String DATABASE_NAME = "runner.db";
private static MyDbHelper INSTANCE = null;
private MyDbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
public static MyDbHelper getInstance(Context context) {
if (INSTANCE == null) {
INSTANCE = new MyDbHelper(context);
}
return INSTANCE;
}
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
// create table (omitted)
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
// upgrade table (omitted)
}
}
Хранилище данных объединяет различные операции вставки и запроса.
public class DataRepository implements DatabaseContract {
private static DataRepository INSTANCE = null;
BriteDatabase briteDatabase;
LocationManger locationManger;
RunManager runManager;
private DataRepository(Context context) {
SqlBrite sqlBrite = new SqlBrite.Builder().build();
MyDbHelper helper = MyDbHelper.getInstance(context);
briteDatabase = sqlBrite.wrapDatabaseHelper(helper, Schedulers.io());
locationManger = new LocationManger(briteDatabase.getWritableDatabase());
runManager = new RunManager(briteDatabase.getWritableDatabase());
}
public static DataRepository getInstance(Context context) {
if (null == INSTANCE) {
INSTANCE = new DataRepository(context);
}
return INSTANCE;
}
@Override
public Observable<List<Run>> getAllRun() {
return briteDatabase.createQuery(
Run.TABLE_NAME,
Run.SELECT_ALL
).mapToList(Run.MAPPER::map);
}
@Override
public Observable<List<Location>> getLocationsForRun(long id) {
return briteDatabase.createQuery(
Location.TABLE_NAME, Location.FACTORY.selectAllByRace(id).statement
).mapToList(Location.MAPPER::map);
}
@Override
public long insertRun(double distance, long duration, double avgSpeed, long timestamp) {
runManager.insertRace.bind(distance, duration, avgSpeed, timestamp);
return briteDatabase.executeInsert(Run.TABLE_NAME, runManager.insertRace.program);
}
@Override
public void deleteRun(long id) {
runManager.deleteRun.bind(id);
briteDatabase.executeUpdateDelete(Run.TABLE_NAME, runManager.deleteRun.program);
}
@Override
public void deleteRunWhereTimestampSmallerThan(long timestamp) {
runManager.deleteRunWhereTimeSmallerThan.bind(timestamp);
briteDatabase.executeUpdateDelete(Run.TABLE_NAME, runManager.deleteRunWhereTimeSmallerThan.program);
}
@Override
public long insertLocation(long raceId, double lat, double lng, double alt, long timestamp) {
locationManger.insertLocation.bind(raceId, lat, lng, alt, timestamp);
return briteDatabase.executeInsert(Location.TABLE_NAME, locationManger.insertLocation.program);
}
public Observable<List<SingleRun>> getAllSingleRunModels() {
return briteDatabase.createQuery(
Run.TABLE_NAME,
Run.SELECT_ALL
).mapToList(Run.MAPPER::map)
// omitted
}
Теперь к основной части вопроса. На данный момент я написал следующие тестовые примеры и столкнулся с ошибками, перечисленными вверху. Интересно, что когда я запускаю тесты отдельно, я не получаю никаких ошибок, все тесты проходят.
@RunWith(AndroidJUnit4.class)
@LargeTest
public class DataRepositoryTest {
private Context context;
private DataRepository mDataRepository;
@Before
public void setUp() {
context = InstrumentationRegistry.getTargetContext();
context.deleteDatabase(MyDbHelper.DATABASE_NAME);
mDataRepository = DataRepository.getInstance(InstrumentationRegistry.getTargetContext());
}
@Test
public void testPreConditions() {
Assert.assertNotNull(context);
Assert.assertNotNull(mDataRepository);
}
@Test
public void testInsertRace() { // this test failes when all tests are run.
long raceID1 = mDataRepository.insertRun(5.0, 35, 3.5, 1000);
Assert.assertEquals(1, raceID1);
long raceID2 = mDataRepository.insertRun(10.0, 70, 3.5, 2000);
Assert.assertEquals(2, raceID2);
long locationID1 = mDataRepository.insertLocation(raceID1, 0.5, 0.5, 0, 1000);
Assert.assertEquals(1, locationID1);
long locationID2 = mDataRepository.insertLocation(raceID1, 0.5, 0.5, 0, 1001);
Assert.assertEquals(2, locationID2);
long locationID3 = mDataRepository.insertLocation(raceID1, 0.5, 0.5, 0, 1002);
Assert.assertEquals(3, locationID3);
long locationID4 = mDataRepository.insertLocation(raceID1, 0.5, 0.5, 0, 1003);
Assert.assertEquals(4, locationID4);
long locationID5 = mDataRepository.insertLocation(raceID2, 0.5, 0.5, 0, 2000);
Assert.assertEquals(5, locationID5);
long locationID6 = mDataRepository.insertLocation(raceID2, 0.5, 0.5, 0, 2001);
Assert.assertEquals(6, locationID6);
long locationID7 = mDataRepository.insertLocation(raceID2, 0.5, 0.5, 0, 2002);
Assert.assertEquals(7, locationID7);
long locationID8 = mDataRepository.insertLocation(raceID2, 0.5, 0.5, 0, 2003);
Assert.assertEquals(8, locationID8);
}
@Test
public void testRaceObservable() {
long raceID1 = mDataRepository.insertRun(5.0, 35, 3.5, 1000);
Run run1 = Run.FACTORY.creator.create(raceID1, 5.0, 35l, 3.5, 1000l);
Assert.assertEquals(1, raceID1);
long raceID2 = mDataRepository.insertRun(10.0, 70, 3.5, 2000);
Run run2 = Run.FACTORY.creator.create(raceID2, 10.0, 70l, 3.5, 2000l);
Assert.assertEquals(2, raceID2);
List<Run> expectedResult = Arrays.asList(run1, run2);
Assert.assertEquals(expectedResult, mDataRepository.getAllRun().blockingFirst());
}
}
Я предполагаю, что это как-то связано с доступом к БД из разных потоков, но я не знаю, как решить проблему.
1 ответ
Ваша проблема в том, что когда setUp
работает во второй раз, DataRepository.getInstance
возвращает старый репозиторий данных, то есть он не создает новый SQLiteOpenHelper
, При удалении базы данных вам также необходимо очистить свои синглтоны для DataRepository и MyDbHelper.
В качестве альтернативы вообще не используйте синглтоны:
@Before
public void setUp() {
context = InstrumentationRegistry.getTargetContext();
context.deleteDatabase(MyDbHelper.DATABASE_NAME);
mDataRepository = new DataRepository(InstrumentationRegistry.getTargetContext());
}
// In DataRepository.java
DataRepository(Context context) {
SqlBrite sqlBrite = new SqlBrite.Builder().build();
MyDbHelper helper = new MyDbHelper(context);
briteDatabase = sqlBrite.wrapDatabaseHelper(helper, Schedulers.io());
locationManger = new LocationManger(briteDatabase.getWritableDatabase());
runManager = new RunManager(briteDatabase.getWritableDatabase());
}
// In MyDbHelper.java
MyDbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}