在Android上测试数据库:ProviderTestCase2或RenamingDelegatingContext?

时间:2010-06-22 19:18:04

标签: android database testing android-testing

我已经使用某些类中的android.database包中的SQLiteOpenHelper实现了对数据库的访问(使用模式DAO)。

我使用AndroidTestCase为这些类编写了一些junit测试,但这导致测试使用与应用程序相同的数据库。

我读到ProviderTestCase2RenamingDelegatingContext可用于分别测试数据库。不幸的是,我找不到任何很好的教程/示例,展示如何使用ProviderTestCase2 / RenamingDelegatingContext测试数据库。

有人能指点我或给我一些提示或分享一些数据库测试代码吗?!

Cheeerrrrsss !! 乔治

5 个答案:

答案 0 :(得分:21)

ProviderTestCaseRenamingDelegatingContext都会破坏数据库(如果已经存在),然后在其上下文中打开它,因此从这个意义上讲,它们都有相同的低级方法来打开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之后,您现在编写的每个测试都将违反数据库的状态。

答案 1 :(得分:6)

我实际上使用SQLiteOpenHelper数据库,我有一个测试技巧。 我们的想法是在正常使用应用程序期间使用标准的文件存储数据库,并在测试期间使用内存数据库。通过这种方式,您可以在每个测试中使用清除DB,而无需在标准数据库中插入/删除/更新数据。它对我来说很好。

请记住,您可以使用内存数据库,只需将null作为数据库文件的名称传递。这在API文档中有明确说明。

此处解释了在测试期间使用内存数据库的优点: https://attakornw.wordpress.com/2012/02/25/using-in-memory-sqlite-database-in-android-tests/

在我的项目中,我有DBHelper类,它扩展了SQLiteHelper。如您所见,有标准方法。我只是添加了一个带有两个参数的构造函数。区别在于,当我调用超级构造函数时,我将null作为DB名称传递。

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

}

如此处所述:How can I find out if code is running inside a JUnit test or not? 有一种方法可以确定您是否正在运行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.")

在你的情况下。

答案 2 :(得分:1)

我有一个应用程序,它使用由sqlite数据库支持的ContentProvider为应用程序提供数据。

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

获取将使用测试数据库而不是应用程序数据库的内容解析程序实例的方法。

答案 3 :(得分:0)

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的确切位置(在我的例子中是db_path),使用上面的方法你可以找出它是否返回sqlitedatabase。

答案 4 :(得分:-1)

可能的解决方案是使用此方法打开数据库

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

并在测试中更改数据库名称。 Here您可以找到有关此方法的一些信息。