【问题标题】:Testing database on Android: ProviderTestCase2 or RenamingDelegatingContext?Android 上的测试数据库:ProviderTestCase2 还是 RenamingDelegatingContext?
【发布时间】:2010-06-22 19:18:04
【问题描述】:

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

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

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

谁能给我指点或给我一些提示或分享一些用于数据库测试的代码?!

欢呼! 乔治

【问题讨论】:

    标签: android database testing android-testing


    【解决方案1】:

    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 之后,您现在编写的每个测试都将与数据库的状态相反。

    【讨论】:

    • 我从一开始就因为没有走 ContentProvider 路线而自责,但我继续做了一个。现在回到测试的东西......
    • RenamingDelegatingContext 是什么意思?为什么我们要使用这个?它会为每种方法创建新的数据库吗?
    • 我发现当运行 AndroidTestCase / RenamingDelegatingContextActivityInstrumentationTestCase2 时,数据库在测试之间持续存在。每次只运行AndroidTestCase 都会重新创建数据库。
    【解决方案2】:

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

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

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

    在我的项目中,我有一个扩展 SQLiteHelper 的 DBHelper 类。如您所见,有标准方法。我只是添加了一个带有两个参数的构造函数。不同的是,当我调用超级构造函数时,我将 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.")
    

    在你的情况下。

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        【解决方案4】:
        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。

        【讨论】:

          【解决方案5】:

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

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

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

          【讨论】:

            猜你喜欢
            • 2016-06-15
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多