我一直在做一些练习来学习android。 我放在一起的示例项目运行良好。 但是,当我将所有Instrumented测试一起运行时,我的内容提供者的测试失败,因为数据库在向数据库发出删除操作时处于只读状态。 当我单独运行测试类时,测试通过了飞行颜色。 我的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)
任何人都可以帮助我弄清楚我的测试发生了什么? 为什么我的数据库魔法变成只读? 我没有运气搜索。
I have been working through some exercises to learn android. The sample project I put together runs fine. But, when I run all of the Instrumented Tests together, the tests for my content provider fail because the database is read-only when deletes are issued to the database. When I run the test class separately, the tests pass with flying colors. My ContentProvider test looks like so:
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; }I have one other test that uses the 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(); } }If I comment out this test, the TestProvider class passes. If I don't, all of the TestProvider tests fail with the same error:
I/TestRunner: android.database.sqlite.SQLiteReadOnlyDatabaseException: attempt to write a readonly database (code 1032)
Can anyone help me figure out what is going on with my tests? Why is my database magically becoming read-only? I have googled around with no luck.
最满意答案
要测试一个ContentProvider,你应该创建一个扩展ProviderTestCase2的测试,在测试类定义的开始处添加@RunWith(AndroidJUnit4.class)注释,指定测试运行器为AndroidJUnitRunner并用@Test注释每个测试。
然后,注入Context
@Override protected void setUp() throws Exception { setContext(InstrumentationRegistry.getTargetContext()); super.setUp(); }并从Studio运行测试。
你可以在本课中学到更多。
To test a ContentProvider you should create a test that extends ProviderTestCase2, add the @RunWith(AndroidJUnit4.class) annotation at the beginning of the test class definition, specify the test runner as AndroidJUnitRunner and annotate every test with @Test.
Then, inject the Context
@Override protected void setUp() throws Exception { setContext(InstrumentationRegistry.getTargetContext()); super.setUp(); }and run your tests from Studio.
You can learn more in this lesson.
更多推荐
发布评论