Android Instrumented Test Database在@Before中神奇地变为只读

时间:2016-10-15 21:56:59

标签: android testing junit

我一直在做一些练习来学习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了。

1 个答案:

答案 0 :(得分:0)

要测试ContentProvider,您应该创建一个扩展ProviderTestCase2的测试,在测试类定义的开头添加@RunWith(AndroidJUnit4.class)注释,将测试运行器指定为AndroidJUnitRunner并注释每个使用@Test进行测试。

然后,注入Context

@Override
protected void setUp() throws Exception {
    setContext(InstrumentationRegistry.getTargetContext());
    super.setUp();
}

并从Studio运行测试。

您可以在此lesson了解详情。