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

发布于 2024-09-06 11:54:12 字数 389 浏览 5 评论 0原文

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

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

我读到 ProviderTestCase2RenamingDelegatingContext 可用于单独测试数据库。不幸的是,我找不到任何不错的教程/示例来展示如何使用 ProviderTestCase2/RenamingDelegatingContext 测试数据库。

谁能指出我的某个地方或给我一些提示或分享一些数据库测试代码?!

干杯!! 乔治奥

I've implemented access to a database using SQLiteOpenHelper from the android.database package within some classes (with pattern DAO).

I wrote some junit tests for these classes using an AndroidTestCase but this causes the tests to use the same database as the application.

I read that the ProviderTestCase2 or RenamingDelegatingContext can be used to test the database separately. Unluckily I couldn't find any nice tutorial/example that shows how to test a database with ProviderTestCase2/RenamingDelegatingContext.

Can anyone point me somewhere OR give me some tip OR share some code for database testing?!

Cheeerrrrsss!!
Giorgio

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

小帐篷 2024-09-13 11:54:12

如果在上下文中打开数据库之前已经存在数据库,则 ProviderTestCaseRenamingDelegatingContext 都会销毁数据库,因此从这个意义上说,它们都具有相同的低级方法来打开数据库SQLite 数据库。

您可以通过在 setUp() 中打开固定装置中的数据库来利用这一点,这将确保您在每个测试用例之前使用新的数据库。

我建议您编写内容提供程序而不是创建数据库适配器。您可以使用通用接口来访问数据,无论数据存储在数据库中还是网络上的某个位置,内容提供商的设计都可以适应访问此类数据,但代价是我们大多数人不应该承担一些 IPC 开销。不必关心。

如果您这样做是为了访问 SQLite 数据库,框架将在单独的进程中为您完全管理数据库连接。作为补充,ProviderTestCase2 完全引导您的内容提供程序的测试上下文,而无需您编写一行代码。

但是,这并不是说您自己进行引导就不需要付出巨大的努力。假设您有一个这样的数据库适配器;我们将只关注 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_STATMENT 后与数据库的状态发生冲突。

Both the ProviderTestCase and RenamingDelegatingContext will destroy the database if one already exists before opening it within it's context, so in that sense they both have the same low-level approach towards opening a SQLite database.

You leverage this by opening the database in your fixture in setUp(), which will then ensure that your working with a fresh database before each test case.

I would suggest that you go for writing content providers rather than creating database adapters. You can use a common interface for accessing data, be it stored in the DB or somewhere over the network, the design of content providers can be accommodated to access such data at the cost of a bit of IPC overhead involved that most of us shouldn't have to care about.

If you did this for accessing a SQLite database, the framework would completely manage the database connection for you in a separate process. As added beef, the ProviderTestCase2<ContentProvider> completely bootstraps a test context for your content provider without you having to a write a single line of code.

But, that's not said it isn't such a huge effort to do the bootstrapping yourself. So supposing you had a database adapter as such; we'll just focus on open() for getting write access to our database, nothing fancy:

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

}

Then you could write your test as such:

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

}

So what's happening here is that the context implementation of RenamingDelegatingContext, once MyAdapter(context).open() is called, will always recreate the database. Each test you write now will be going against the state of the database after MyAdapter.DATABASE_CREATE_STATEMENT is called.

抽个烟儿 2024-09-13 11:54:12

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

请记住,您可以使用内存数据库,只需传递 null 作为数据库文件的名称。 API 文档中清楚地记录了这一点。

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

在我的项目中,我有扩展 SQLiteHelper 的 DBHelper 类。正如您所看到的,有标准方法。我只是添加了一个带有两个参数的构造函数。不同之处在于,当我调用超级构造函数时,我传递 null 作为数据库名称。

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

}

正如这里所讨论的: 如何确定代码是否在 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

}

请注意,这

startsWith("junit.")

可能

startsWith("org.junit.")

适合您的情况。

I actually use database with SQLiteOpenHelper and I have a trick for testing.
The idea is to use standard on-file stored DB during the normal use of the app and an in-memory DB during tests. In this way you can use a clear DB for each test without insert/delete/update data in your standard DB. It works fine for me.

Keep in mind you can use in-memory database, just passing null as name of database file. This is clearly documented in the API documentation.

Advantages of using in-memory DB during tests is explained here:
https://attakornw.wordpress.com/2012/02/25/using-in-memory-sqlite-database-in-android-tests/

In my project I have the DBHelper class wich extends SQLiteHelper. As you can see, there are the standard methods. I simply added a constructor with two parameters. The difference is that when I call the super constructor, I pass null as DB name.

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

Every "model" in the project extends DBModel that is an abstract class.

public abstract class DBModel {
    protected DBHelper dbhelper;

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

    //other declarations and utility function omitted

}

As discussed here: How can I find out if code is running inside a JUnit test or not?
there is a way to establish if you are running JUnit tests, simply searching in stack trace elements.
As a conseguence, I modified DBModel constructor

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

}

Note that

startsWith("junit.")

may be

startsWith("org.junit.")

in your case.

佞臣 2024-09-13 11:54:12

我有一个应用程序,它使用由 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();

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

I have an application that uses a ContentProvider backed by an sqlite database to provide data to the application.

Let PodcastDataProvider be the actual dataprovider used by the application.

Then you can set up a test provider with something like the following:

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

to setup a test data provider that will be backed by a different database than the actual application.

To test the DAO, create another class which extends AbstractPodcastDataProvider and use the

getMockContentResolver();

method to get an instance of a content resolver that will use the test database instead of the application database.

吹泡泡o 2024-09-13 11:54:12
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。

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

}

if you give the exact location of the sqlite db(in my case it's db_path), using the above method you can find-out whether it returns an sqlitedatabase or not.

所谓喜欢 2024-09-13 11:54:12

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

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

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

A possible solution can be to open database using this method

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

And change database name in your tests. Here you can find some info about this method.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文