我一直在做一些练习来学习android。我放在一起的示例项目运行良好。但是,当我一起运行所有的Instrumented Tests时,我的内容提供程序的测试失败,因为在向数据库发出删除时数据库是只读的。当我单独运行测试类时,测试通过了飞行颜色。我的ContentProvider
测试看起来像这样:
public class TestProvider {
public static final String LOG_TAG = TestProvider.class.getSimpleName();
public void deleteAllRecordsFromProvider() {
InstrumentationRegistry.getTargetContext()
.getContentResolver().delete(
WeatherEntry.CONTENT_URI,
null,
null
);
InstrumentationRegistry.getTargetContext()
.getContentResolver().delete(
LocationEntry.CONTENT_URI,
null,
null
);
Cursor cursor = InstrumentationRegistry.getTargetContext()
.getContentResolver().query(
WeatherEntry.CONTENT_URI,
null,
null,
null,
null
);
assertEquals("Error: Records not deleted from Weather table during delete", 0, cursor.getCount());
cursor.close();
cursor = InstrumentationRegistry.getTargetContext()
.getContentResolver().query(
LocationEntry.CONTENT_URI,
null,
null,
null,
null
);
assertEquals("Error: Records not deleted from Location table during delete", 0, cursor.getCount());
cursor.close();
}
@Before
public void setUp() throws Exception {
deleteAllRecordsFromProvider();
}
@After
public void after() {
InstrumentationRegistry.getTargetContext()
.getContentResolver()
.acquireContentProviderClient(WeatherEntry.CONTENT_URI)
.getLocalContentProvider()
.shutdown();
}
@Test
public void testProviderRegistry() {
PackageManager pm = InstrumentationRegistry.getTargetContext().getPackageManager();
// We define the component name based on the package name from the context and the
// WeatherProvider class.
ComponentName componentName = new ComponentName(InstrumentationRegistry.getTargetContext().getPackageName(),
WeatherProvider.class.getName());
try {
// Fetch the provider info using the component name from the PackageManager
// This throws an exception if the provider isn't registered.
ProviderInfo providerInfo = pm.getProviderInfo(componentName, 0);
// Make sure that the registered authority matches the authority from the Contract.
assertEquals("Error: WeatherProvider registered with authority: " + providerInfo.authority +
" instead of authority: " + WeatherContract.CONTENT_AUTHORITY,
providerInfo.authority, WeatherContract.CONTENT_AUTHORITY);
} catch (PackageManager.NameNotFoundException e) {
// I guess the provider isn't registered correctly.
assertTrue("Error: WeatherProvider not registered at " + InstrumentationRegistry.getTargetContext().getPackageName(),
false);
}
}
@Test
public void testGetType() {
// content://com.example.android.sunshine.app/weather/
String type = InstrumentationRegistry.getTargetContext()
.getContentResolver()
.getType(WeatherEntry.CONTENT_URI);
// vnd.android.cursor.dir/com.example.android.sunshine.app/weather
assertEquals("Error: the WeatherEntry CONTENT_URI should return WeatherEntry.CONTENT_TYPE",
WeatherEntry.CONTENT_TYPE, type);
String testLocation = "94074";
// content://com.example.android.sunshine.app/weather/94074
type = InstrumentationRegistry.getTargetContext().getContentResolver().getType(
WeatherEntry.buildWeatherLocation(testLocation));
// vnd.android.cursor.dir/com.example.android.sunshine.app/weather
assertEquals("Error: the WeatherEntry CONTENT_URI with location should return WeatherEntry.CONTENT_TYPE",
WeatherEntry.CONTENT_TYPE, type);
long testDate = 1419120000L; // December 21st, 2014
// content://com.example.android.sunshine.app/weather/94074/20140612
type = InstrumentationRegistry.getTargetContext().getContentResolver().getType(
WeatherEntry.buildWeatherLocationWithDate(testLocation, testDate));
// vnd.android.cursor.item/com.example.android.sunshine.app/weather/1419120000
assertEquals("Error: the WeatherEntry CONTENT_URI with location and date should return WeatherEntry.CONTENT_ITEM_TYPE",
WeatherEntry.CONTENT_ITEM_TYPE, type);
// content://com.example.android.sunshine.app/location/
type = InstrumentationRegistry.getTargetContext().getContentResolver().getType(LocationEntry.CONTENT_URI);
// vnd.android.cursor.dir/com.example.android.sunshine.app/location
assertEquals("Error: the LocationEntry CONTENT_URI should return LocationEntry.CONTENT_TYPE",
LocationEntry.CONTENT_TYPE, type);
}
@Test
public void testBasicWeatherQuery() {
// insert our test records into the database
WeatherDbHelper dbHelper = new WeatherDbHelper(InstrumentationRegistry.getTargetContext());
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues testValues = TestUtilities.createNorthPoleLocationValues();
long locationRowId = TestUtilities.insertNorthPoleLocationValues(InstrumentationRegistry.getTargetContext());
// Fantastic. Now that we have a location, add some weather!
ContentValues weatherValues = TestUtilities.createWeatherValues(locationRowId);
long weatherRowId = db.insert(WeatherEntry.TABLE_NAME, null, weatherValues);
assertTrue("Unable to Insert WeatherEntry into the Database", weatherRowId != -1);
// Test the basic content provider query
Cursor weatherCursor = InstrumentationRegistry.getTargetContext().getContentResolver().query(
WeatherEntry.CONTENT_URI,
null,
null,
null,
null
);
// Make sure we get the correct cursor out of the database
TestUtilities.validateCursor("testBasicWeatherQuery", weatherCursor, weatherValues);
weatherCursor.close();
}
@Test
public void testBasicLocationQueries() {
// insert our test records into the database
WeatherDbHelper dbHelper = new WeatherDbHelper(InstrumentationRegistry.getTargetContext());
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues testValues = TestUtilities.createNorthPoleLocationValues();
long locationRowId = TestUtilities.insertNorthPoleLocationValues(InstrumentationRegistry.getTargetContext());
// Test the basic content provider query
Cursor locationCursor = InstrumentationRegistry.getTargetContext()
.getContentResolver()
.query(
LocationEntry.CONTENT_URI,
null,
null,
null,
null
);
// Make sure we get the correct cursor out of the database
TestUtilities.validateCursor("testBasicLocationQueries, location query", locationCursor, testValues);
// Has the NotificationUri been set correctly? --- we can only test this easily against API
// level 19 or greater because getNotificationUri was added in API level 19.
if ( Build.VERSION.SDK_INT >= 19 ) {
assertEquals("Error: Location Query did not properly set NotificationUri",
locationCursor.getNotificationUri(), LocationEntry.CONTENT_URI);
}
locationCursor.close();
}
@Test
public void testUpdateLocation() {
// Create a new map of values, where column names are the keys
ContentValues values = TestUtilities.createNorthPoleLocationValues();
Uri locationUri = InstrumentationRegistry.getTargetContext().getContentResolver().
insert(LocationEntry.CONTENT_URI, values);
long locationRowId = ContentUris.parseId(locationUri);
// Verify we got a row back.
assertTrue(locationRowId != -1);
Log.d(LOG_TAG, "New row id: " + locationRowId);
ContentValues updatedValues = new ContentValues(values);
updatedValues.put(LocationEntry._ID, locationRowId);
updatedValues.put(LocationEntry.COLUMN_CITY_NAME, "Santa's Village");
// Create a cursor with observer to make sure that the content provider is notifying
// the observers as expected
Cursor locationCursor = InstrumentationRegistry.getTargetContext().getContentResolver().query(LocationEntry.CONTENT_URI, null, null, null, null);
TestUtilities.TestContentObserver tco = TestUtilities.getTestContentObserver();
locationCursor.registerContentObserver(tco);
int count = InstrumentationRegistry.getTargetContext().getContentResolver().update(
LocationEntry.CONTENT_URI, updatedValues, LocationEntry._ID + "= ?",
new String[]{Long.toString(locationRowId)});
assertEquals(count, 1);
// Test to make sure our observer is called. If not, we throw an assertion.
//
// Students: If your code is failing here, it means that your content provider
// isn't calling getContext().getContentResolver().notifyChange(uri, null);
tco.waitForNotificationOrFail();
locationCursor.unregisterContentObserver(tco);
locationCursor.close();
// A cursor is your primary interface to the query results.
Cursor cursor = InstrumentationRegistry.getTargetContext().getContentResolver().query(
LocationEntry.CONTENT_URI,
null, // projection
LocationEntry._ID + " = " + locationRowId,
null, // Values for the "where" clause
null // sort order
);
TestUtilities.validateCursor("testUpdateLocation. Error validating location entry update.",
cursor, updatedValues);
cursor.close();
}
@Test
public void testInsertReadProvider() {
ContentValues testValues = TestUtilities.createNorthPoleLocationValues();
// Register a content observer for our insert. This time, directly with the content resolver
TestUtilities.TestContentObserver tco = TestUtilities.getTestContentObserver();
InstrumentationRegistry.getTargetContext().getContentResolver().registerContentObserver(LocationEntry.CONTENT_URI, true, tco);
Uri locationUri = InstrumentationRegistry.getTargetContext().getContentResolver().insert(LocationEntry.CONTENT_URI, testValues);
// Did our content observer get called? Students: If this fails, your insert location
// isn't calling getContext().getContentResolver().notifyChange(uri, null);
tco.waitForNotificationOrFail();
InstrumentationRegistry.getTargetContext().getContentResolver().unregisterContentObserver(tco);
long locationRowId = ContentUris.parseId(locationUri);
// Verify we got a row back.
assertTrue(locationRowId != -1);
// Data's inserted. IN THEORY. Now pull some out to stare at it and verify it made
// the round trip.
// A cursor is your primary interface to the query results.
Cursor cursor = InstrumentationRegistry.getTargetContext().getContentResolver().query(
LocationEntry.CONTENT_URI,
null, // leaving "columns" null just returns all the columns.
null, // cols for "where" clause
null, // values for "where" clause
null // sort order
);
TestUtilities.validateCursor("testInsertReadProvider. Error validating LocationEntry.",
cursor, testValues);
cursor.close();
// Fantastic. Now that we have a location, add some weather!
ContentValues weatherValues = TestUtilities.createWeatherValues(locationRowId);
// The TestContentObserver is a one-shot class
tco = TestUtilities.getTestContentObserver();
InstrumentationRegistry.getTargetContext().getContentResolver().registerContentObserver(WeatherEntry.CONTENT_URI, true, tco);
Uri weatherInsertUri = InstrumentationRegistry.getTargetContext().getContentResolver()
.insert(WeatherEntry.CONTENT_URI, weatherValues);
assertTrue(weatherInsertUri != null);
// Did our content observer get called? Students: If this fails, your insert weather
// in your ContentProvider isn't calling
// getContext().getContentResolver().notifyChange(uri, null);
tco.waitForNotificationOrFail();
InstrumentationRegistry.getTargetContext().getContentResolver().unregisterContentObserver(tco);
// A cursor is your primary interface to the query results.
Cursor weatherCursor = InstrumentationRegistry.getTargetContext().getContentResolver().query(
WeatherEntry.CONTENT_URI, // Table to Query
null, // leaving "columns" null just returns all the columns.
null, // cols for "where" clause
null, // values for "where" clause
null // columns to group by
);
TestUtilities.validateCursor("testInsertReadProvider. Error validating WeatherEntry insert.",
weatherCursor, weatherValues);
// Add the location values in with the weather data so that we can make
// sure that the join worked and we actually get all the values back
weatherValues.putAll(testValues);
weatherCursor.close();
// Get the joined Weather and Location data
weatherCursor = InstrumentationRegistry.getTargetContext().getContentResolver().query(
WeatherEntry.buildWeatherLocation(TestUtilities.TEST_LOCATION),
null, // leaving "columns" null just returns all the columns.
null, // cols for "where" clause
null, // values for "where" clause
null // sort order
);
TestUtilities.validateCursor("testInsertReadProvider. Error validating joined Weather and Location Data.",
weatherCursor, weatherValues);
weatherCursor.close();
// Get the joined Weather and Location data with a start date
weatherCursor = InstrumentationRegistry.getTargetContext().getContentResolver().query(
WeatherEntry.buildWeatherLocationWithStartDate(
TestUtilities.TEST_LOCATION, TestUtilities.TEST_DATE),
null, // leaving "columns" null just returns all the columns.
null, // cols for "where" clause
null, // values for "where" clause
null // sort order
);
TestUtilities.validateCursor("testInsertReadProvider. Error validating joined Weather and Location Data with start date.",
weatherCursor, weatherValues);
weatherCursor.close();
// Get the joined Weather data for a specific date
weatherCursor = InstrumentationRegistry.getTargetContext().getContentResolver().query(
WeatherEntry.buildWeatherLocationWithDate(TestUtilities.TEST_LOCATION, TestUtilities.TEST_DATE),
null,
null,
null,
null
);
TestUtilities.validateCursor("testInsertReadProvider. Error validating joined Weather and Location data for a specific date.",
weatherCursor, weatherValues);
weatherCursor.close();
}
@Test
public void testDeleteRecords() {
testInsertReadProvider();
// Register a content observer for our location delete.
TestUtilities.TestContentObserver locationObserver = TestUtilities.getTestContentObserver();
InstrumentationRegistry.getTargetContext().getContentResolver().registerContentObserver(LocationEntry.CONTENT_URI, true, locationObserver);
// Register a content observer for our weather delete.
TestUtilities.TestContentObserver weatherObserver = TestUtilities.getTestContentObserver();
InstrumentationRegistry.getTargetContext().getContentResolver().registerContentObserver(WeatherEntry.CONTENT_URI, true, weatherObserver);
deleteAllRecordsFromProvider();
// Students: If either of these fail, you most-likely are not calling the
// getContext().getContentResolver().notifyChange(uri, null); in the ContentProvider
// delete. (only if the insertReadProvider is succeeding)
locationObserver.waitForNotificationOrFail();
weatherObserver.waitForNotificationOrFail();
InstrumentationRegistry.getTargetContext().getContentResolver().unregisterContentObserver(locationObserver);
InstrumentationRegistry.getTargetContext().getContentResolver().unregisterContentObserver(weatherObserver);
}
static private final int BULK_INSERT_RECORDS_TO_INSERT = 10;
static ContentValues[] createBulkInsertWeatherValues(long locationRowId) {
long currentTestDate = TestUtilities.TEST_DATE;
long millisecondsInADay = 1000 * 60 * 60 * 24;
ContentValues[] returnContentValues = new ContentValues[BULK_INSERT_RECORDS_TO_INSERT];
for (int i = 0; i < BULK_INSERT_RECORDS_TO_INSERT; i++, currentTestDate += millisecondsInADay) {
ContentValues weatherValues = new ContentValues();
weatherValues.put(WeatherContract.WeatherEntry.COLUMN_LOC_KEY, locationRowId);
weatherValues.put(WeatherContract.WeatherEntry.COLUMN_DATE, currentTestDate);
weatherValues.put(WeatherContract.WeatherEntry.COLUMN_DEGREES, 1.1);
weatherValues.put(WeatherContract.WeatherEntry.COLUMN_HUMIDITY, 1.2 + 0.01 * (float) i);
weatherValues.put(WeatherContract.WeatherEntry.COLUMN_PRESSURE, 1.3 - 0.01 * (float) i);
weatherValues.put(WeatherContract.WeatherEntry.COLUMN_MAX_TEMP, 75 + i);
weatherValues.put(WeatherContract.WeatherEntry.COLUMN_MIN_TEMP, 65 - i);
weatherValues.put(WeatherContract.WeatherEntry.COLUMN_SHORT_DESC, "Asteroids");
weatherValues.put(WeatherContract.WeatherEntry.COLUMN_WIND_SPEED, 5.5 + 0.2 * (float) i);
weatherValues.put(WeatherContract.WeatherEntry.COLUMN_WEATHER_ID, 321);
returnContentValues[i] = weatherValues;
}
return returnContentValues;
}
我还有一个使用InstrumentationRegistry
的测试:
public class TestFetchWeatherTask {
static final String ADD_LOCATION_SETTING = "Sunnydale, CA";
static final String ADD_LOCATION_CITY = "Sunnydale";
static final Double ADD_LOCATION_LAT = 34.425833;
static final Double ADD_LOCATION_LON = -119.714167;
@Test
public void testAddLocation() {
// start from a clean state
InstrumentationRegistry.getTargetContext()
.getContentResolver()
.delete(WeatherContract.LocationEntry.CONTENT_URI,
WeatherContract.LocationEntry.COLUMN_LOCATION_SETTING + " = ?",
new String[]{ADD_LOCATION_SETTING});
FetchWeatherTask fwt = new FetchWeatherTask(InstrumentationRegistry.getTargetContext(), null);
long locationId = fwt.addLocation(ADD_LOCATION_SETTING, ADD_LOCATION_CITY,
ADD_LOCATION_LAT, ADD_LOCATION_LON);
// does addLocation return a valid record ID?
assertFalse("Error: addLocation returned an invalid ID on insert", locationId == -1);
// test all this twice
for ( int i = 0; i < 2; i++ ) {
// does the ID point to our location?
Cursor locationCursor = InstrumentationRegistry.getTargetContext()
.getContentResolver().query(WeatherContract.LocationEntry.CONTENT_URI,
new String[]{
WeatherContract.LocationEntry._ID,
WeatherContract.LocationEntry.COLUMN_LOCATION_SETTING,
WeatherContract.LocationEntry.COLUMN_CITY_NAME,
WeatherContract.LocationEntry.COLUMN_COORD_LAT,
WeatherContract.LocationEntry.COLUMN_COORD_LONG
},
WeatherContract.LocationEntry.COLUMN_LOCATION_SETTING + " = ?",
new String[]{ADD_LOCATION_SETTING},
null);
// these match the indices of the projection
if (locationCursor.moveToFirst()) {
assertEquals("Error: the queried value of locationId does not match the returned value" +
"from addLocation", locationCursor.getLong(0), locationId);
assertEquals("Error: the queried value of location setting is incorrect",
locationCursor.getString(1), ADD_LOCATION_SETTING);
assertEquals("Error: the queried value of location city is incorrect",
locationCursor.getString(2), ADD_LOCATION_CITY);
assertEquals("Error: the queried value of latitude is incorrect",
Double.valueOf(locationCursor.getDouble(3)), ADD_LOCATION_LAT);
assertEquals("Error: the queried value of longitude is incorrect",
Double.valueOf(locationCursor.getDouble(4)), ADD_LOCATION_LON);
} else {
fail("Error: the id you used to query returned an empty cursor");
}
// there should be no more records
assertFalse("Error: there should be only one record returned from a location query",
locationCursor.moveToNext());
// add the location again
long newLocationId = fwt.addLocation(ADD_LOCATION_SETTING, ADD_LOCATION_CITY,
ADD_LOCATION_LAT, ADD_LOCATION_LON);
assertEquals("Error: inserting a location again should return the same ID",
locationId, newLocationId);
locationCursor.close();
}
// reset our state back to normal
InstrumentationRegistry.getTargetContext()
.getContentResolver()
.delete(WeatherContract.LocationEntry.CONTENT_URI,
WeatherContract.LocationEntry.COLUMN_LOCATION_SETTING + " = ?",
new String[]{ADD_LOCATION_SETTING});
InstrumentationRegistry.getTargetContext()
.getContentResolver()
.acquireContentProviderClient(WeatherContract.LocationEntry.CONTENT_URI)
.getLocalContentProvider()
.shutdown();
}
}
如果我注释掉这个测试,那么TestProvider
类就会通过。如果不这样做,则所有TestProvider
测试都会失败并出现相同的错误:
I / TestRunner:android.database.sqlite.SQLiteReadOnlyDatabaseException: 尝试编写只读数据库(代码1032)
任何人都可以帮我弄清楚我的测试是怎么回事吗?为什么我的数据库神奇地变为只读?我没有运气就google了。
答案 0 :(得分:0)
要测试ContentProvider,您应该创建一个扩展ProviderTestCase2的测试,在测试类定义的开头添加@RunWith(AndroidJUnit4.class)
注释,将测试运行器指定为AndroidJUnitRunner
并注释每个使用@Test
进行测试。
然后,注入Context
@Override
protected void setUp() throws Exception {
setContext(InstrumentationRegistry.getTargetContext());
super.setUp();
}
并从Studio运行测试。
您可以在此lesson了解详情。