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

Gio*_*gio 29 database testing android android-testing

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

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

我读过ProviderTestCase2或者RenamingDelegatingContext可以用来分别测试数据库.不幸的是,我找不到任何很好的教程/示例,展示了如何使用ProviderTestCase2/RenamingDelegatingContext测试数据库.

任何人都可以指点我或给我一些提示或分享一些代码进行数据库测试?!

Cheeerrrrsss!乔治

Fil*_*vić 21

如果数据库已经存在,ProviderTestCase并且RenamingDelegatingContext将在它的上下文中打开之前销毁数据库,那么在这种意义上它们都具有相同的低级方法来打开SQLite数据库.

您可以通过在fixture中打开数据库来利用它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();
        }

}
Run Code Online (Sandbox Code Playgroud)

然后你可以这样写你的测试:

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);
}

}
Run Code Online (Sandbox Code Playgroud)

所以这里发生的事情是RenamingDelegatingContext,一旦MyAdapter(context).open()被调用,上下文实现将始终重新创建数据库.您现在编写的每个测试都会在MyAdapter.DATABASE_CREATE_STATEMENT调用后违反数据库的状态.


Lip*_*yor 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
    }
}
Run Code Online (Sandbox Code Playgroud)

项目中的每个"模型"都扩展了作为抽象类的DBModel.

public abstract class DBModel {
    protected DBHelper dbhelper;

    public DBModel(Context context) {
        dbhelper = new DBHelper(context);
    }

    //other declarations and utility function omitted

}
Run Code Online (Sandbox Code Playgroud)

如下所述:如何判断代码是否在JUnit测试中运行? 有一种方法可以确定您是否正在运行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

}
Run Code Online (Sandbox Code Playgroud)

注意

startsWith("junit.")
Run Code Online (Sandbox Code Playgroud)

也许

startsWith("org.junit.")
Run Code Online (Sandbox Code Playgroud)

在你的情况下.