Android单元测试框架是在Java单元测试框架的基础上扩展的,Android四大组件中的Activity、Service、ContentProvider都可以作为单元测试的Target进行测试。本文主要介绍了ContentProvider的单元测试。

1.建立测试环境

在前面的一篇文章《Android单元测试》中介绍的测试环境的建立是与Android工程独立的一种形式,本文的测试环境建立是在Android工程的AndroidManifest.xml中直接注册相关的测试环境(当然这种形式是不建议使用的,测试工程和被测试工程最好是分离的)。

在AndroidManifest.xml文件中注册测试环境如下:

<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"    package="com.steven.hu.cpt"    android:versionCode="1"    android:versionName="1.0" >    <uses-sdk android:minSdkVersion="15" />     <instrumentation        android:name="android.test.InstrumentationTestRunner"        android:targetPackage="com.steven.hu.cpt" />         <application        android:icon="@drawable/ic_launcher"        android:label="@string/app_name" >        <uses-library android:name="android.test.runner" />        <provider             android:name="com.steven.hu.cpt.ProviderToBeTest"            android:authorities="com.steven.hu.provider.test"            android:exported="true"></provider>    </application></manifest>

ProviderToBeTest为被测试的ContentProvider组件。在ProviderToBeTest的Android工程中建立两个不同的包名,分别存放被测试代码文件和测试代码文件。如下结构:

2.创建待测试的ContentProvider组件

1).Family.java文件中的代码主要定义相关的数据库表列字段名和Uri,代码如下:

package com.steven.hu.cpt;import android.net.Uri;import android.provider.BaseColumns;public class Family{    //定义Authority,与AndroidManifest.xml文件中定义的一致    public static final String AUTHRITY = "com.steven.hu.provider.test";        public static final class Member implements BaseColumns    {                //定义表的数据列字段        public static final String _ID = "_id";        public static final String NAME = "name";        public static final String AGE = "age";        public static final String GENDER = "gender";                //面向整个表的uri        public static final Uri FAMILY_CONTENT_URI =             Uri.parse("content://" + AUTHRITY + "/family");                //面向单条记录数据的uri        public static final Uri MEMBER_CONTENT_URI =             Uri.parse("content://" + AUTHRITY + "/member/");                //表名        public static final String TABLE_NAME = "genealogy";    }}

2).ProviderToBeTest.java中的代码实现为待测试的ContentProvider组件,代码如下:

package com.steven.hu.cpt;import android.content.ContentProvider;import android.content.ContentUris;import android.content.ContentValues;import android.content.Context;import android.content.UriMatcher;import android.database.Cursor;import android.database.sqlite.SQLiteDatabase;import android.database.sqlite.SQLiteOpenHelper;import android.net.Uri;import android.util.Log;public class ProviderToBeTest extends ContentProvider{    private static final int DATABASE_VERSION = 2;    private static final String DATABASE_NAME = "family_info.db";    private static final String TAG = "ProviderToBeTest";        private DatabaseHelper mOpenHelper;    private static UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);    private static final int FAMILY = 1;    private static final int MEMBER = 2;    static    {        sMatcher.addURI(Family.AUTHRITY, "family", FAMILY);        sMatcher.addURI(Family.AUTHRITY, "member/#", MEMBER);    }    @Override    public boolean onCreate()    {        // TODO Auto-generated method stub        mOpenHelper = new DatabaseHelper(getContext());        return true;    }    // 插入数据    @Override    public Uri insert(Uri uri, ContentValues values)    {        SQLiteDatabase db = mOpenHelper.getWritableDatabase();        switch (sMatcher.match(uri))        {            // 如果Uri参数代表操作全部数据项            case FAMILY:                // 插入数据,返回插入记录的ID                long rowId = db.insert(Family.Member.TABLE_NAME, Family.Member._ID, values);                // 如果插入成功返回uri                if (rowId > 0)                {                    // 在已有的 Uri的后面追加ID                    Uri memberUri = ContentUris.withAppendedId(uri, rowId);                    // 通知数据已经改变                    getContext().getContentResolver().notifyChange(memberUri, null);                    return memberUri;                }                break;            default:                throw new IllegalArgumentException("未知Uri:" + uri);        }        return null;    }    // 删除数据    @Override    public int delete(Uri uri, String where, String[] whereArgs)    {        SQLiteDatabase db = mOpenHelper.getReadableDatabase();        // 记录所删除的记录数        int num = 0;        // 对于uri进行匹配。        switch (sMatcher.match(uri))        {            // 如果Uri参数代表操作全部数据项            case FAMILY:                num = db.delete(Family.Member.TABLE_NAME, where, whereArgs);                break;            // 如果Uri参数代表操作指定数据项            case MEMBER:                // 解析出所需要删除的记录ID                long id = ContentUris.parseId(uri);                String whereClause = Family.Member._ID + "=" + id;                // 如果原来的where子句存在,拼接where子句                if (where != null && !where.equals(""))                {                    whereClause = whereClause + " and " + where;                }                num = db.delete(Family.Member.TABLE_NAME, whereClause, whereArgs);                break;            default:                throw new IllegalArgumentException("未知Uri:" + uri);        }        // 通知数据已经改变        getContext().getContentResolver().notifyChange(uri, null);        return num;    }    // 查询数据    @Override    public Cursor query(Uri uri, String[] projection, String where, String[] whereArgs,            String sortOrder)    {        SQLiteDatabase db = mOpenHelper.getReadableDatabase();        switch (sMatcher.match(uri))        {            // 如果Uri参数代表操作全部数据项            case FAMILY:                // 执行查询                return db.query(Family.Member.TABLE_NAME, projection, where, whereArgs, null, null, sortOrder);                // 如果Uri参数代表操作指定数据项            case MEMBER:                // 解析出想查询的记录ID                long id = ContentUris.parseId(uri);                String whereClause = Family.Member._ID + "=" + id;                // 如果原来的where子句存在,拼接where子句                if (where != null && !"".equals(where))                {                    whereClause = whereClause + " and " + where;                }                return db.query(Family.Member.TABLE_NAME, projection, whereClause, whereArgs, null, null,                        sortOrder);            default:                throw new IllegalArgumentException("未知Uri:" + uri);        }    }    // 获取uri中的MIME类型    @Override    public String getType(Uri uri)    {        // TODO Auto-generated method stub        switch (sMatcher.match(uri))        {            case FAMILY:                return "famliy/com.steven.hu.genealogy";            case MEMBER:                return "member/com.steven.hu.genealogy";            default:                throw new IllegalArgumentException("未知Uri:" + uri);        }    }    // 更新数据    @Override    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)    {        // TODO Auto-generated method stub        return 0;    }    public static class DatabaseHelper extends SQLiteOpenHelper    {        // 定义创建表的SQL语句        //final String CREATE_TABLE_SQL = "create table family(_id integer primary key autoincrement , name , age, gender)";        final String CREATE_TABLE_SQL = "CREATE TABLE genealogy"  + " ("        + Family.Member._ID + " INTEGER PRIMARY KEY,"        + Family.Member.NAME + " TEXT,"        + Family.Member.AGE + " INTEGER,"        + Family.Member.GENDER + " TEXT"        + ");";                public DatabaseHelper(Context context)        {            super(context, DATABASE_NAME, null, DATABASE_VERSION);            // TODO Auto-generated constructor stub        }        @Override        public void onCreate(SQLiteDatabase db)        {            // TODO Auto-generated method stub            // 建表            db.execSQL(CREATE_TABLE_SQL);        }        @Override        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)        {            // TODO Auto-generated method stub            Log.w(TAG, "Upgrading database from version " + oldVersion + " to "                    + newVersion + ", which will destroy all old data");            // Kills the table and existing data            db.execSQL("DROP TABLE IF EXISTS genealogy");            // Recreates the database with a new version            onCreate(db);        }    }       public DatabaseHelper getOpenHelperForTest()    {        return mOpenHelper;    }}

3.创建测试ContentProvider组件的测试代码

ProviderTestCase2<T extends ContentProvider>为Android测试框架提供的用于测试ContentProvider的测试类,T即为待测试的Target,该Target必须为ContentProvider类型(继承ContentProvider)。

TestProvider.java文件中的代码实现对ProviderToBeTest的单元测试,代码如下:

package com.steven.hu.cpt.test;import com.steven.hu.cpt.Family;import com.steven.hu.cpt.ProviderToBeTest;import android.content.ContentUris;import android.content.ContentValues;import android.database.Cursor;import android.database.sqlite.SQLiteDatabase;import android.net.Uri;import android.test.ProviderTestCase2;import android.test.mock.MockContentResolver;import android.util.Log;import android.webkit.WebChromeClient.CustomViewCallback;public class TestProvider extends ProviderTestCase2<ProviderToBeTest>{    // 测试框架提供的虚拟ContentResolver,该类继承ContentResolver    private MockContentResolver mMockResolver;    private SQLiteDatabase mDatabase;    private final MemberInfo[] TEST_MEMBERS =    {            new MemberInfo("name0", 0, "gender0"),            new MemberInfo("name1", 1, "gender1"),            new MemberInfo("name2", 2, "gender2"),             new MemberInfo("name3", 3, "gender3"),            new MemberInfo("name4", 4, "gender4"),             new MemberInfo("name5", 5, "gender5"),            new MemberInfo("name6", 6, "gender6"),             new MemberInfo("name7", 7, "gender7"),            new MemberInfo("name8", 8, "gender8"),            new MemberInfo("name9", 9, "gender9")    };    public TestProvider()    {        super(ProviderToBeTest.class, Family.AUTHRITY);    }    @Override    protected void setUp() throws Exception    {        // TODO Auto-generated method stub        super.setUp();        mMockResolver = getMockContentResolver();        mDatabase = getProvider().getOpenHelperForTest().getWritableDatabase();    }    public void testUriAndGetType()    {        String mimeType = mMockResolver.getType(Family.Member.FAMILY_CONTENT_URI);        assertEquals("famliy/com.steven.hu.genealogy", mimeType);        // mimeType为空,因为MEMBER_CONTENT_URI没有适配member/#        /*         * mimeType = mMockResolver.getType(Family.Member.MEMBER_CONTENT_URI);         * assertEquals("member/com.steven.hu.genealogy", mimeType);         */        // 测试成功,MEMBER_CONTENT_URI后追加1,为member/1适配member/#,#代码任意值        Uri noteIdUri = ContentUris.withAppendedId(Family.Member.MEMBER_CONTENT_URI, 1);        mimeType = mMockResolver.getType(noteIdUri);        assertEquals("member/com.steven.hu.genealogy", mimeType);    }    private class MemberInfo    {        String name;        int age;        String gender;        public MemberInfo(String n, int a, String g)        {            name = n;            age = a;            gender = g;        }        public ContentValues getContentValues()        {            ContentValues values = new ContentValues();            values.put(Family.Member.NAME, name);            values.put(Family.Member.AGE, age);            values.put(Family.Member.GENDER, gender);            return values;        }    }    // 测试数据插入(注:每一个测试用例执行完后,对应的测试数据库会自动清空。)    public void testInsert()    {        MemberInfo memberInfo = new MemberInfo("steven", 100, "man");        // 返回表中一行数据对应的uri        Uri rowUri = mMockResolver.insert(                Family.Member.FAMILY_CONTENT_URI,                memberInfo.getContentValues());        // 查询返回插入的数据        Cursor cursor = mMockResolver.query(                Family.Member.FAMILY_CONTENT_URI,                null,                null,                 null,                null);        // 测试是否只是插入一行数据        assertEquals(1, cursor.getCount());        // 测试表中的第一个条数据是否存在,若cursor为空,则cursor.moveToFirst()返回fase        assertTrue(cursor.moveToFirst());        // 获取列字段的index        int nameIndex = cursor.getColumnIndex(Family.Member.NAME);        int ageIndex = cursor.getColumnIndex(Family.Member.AGE);        int genderIndex = cursor.getColumnIndex(Family.Member.GENDER);        // 测试数据库表中的信息是否和原信息一致        assertEquals(memberInfo.name, cursor.getString(nameIndex));        assertEquals(memberInfo.age, cursor.getInt(ageIndex));        assertEquals(memberInfo.gender, cursor.getString(genderIndex));    }    // 建立测试数据-->向数据表中插入数据,以便进行测试    private void insertData()    {        for (int i = 0; i < TEST_MEMBERS.length; i++)        {            mDatabase.insertOrThrow(Family.Member.TABLE_NAME, null, TEST_MEMBERS[i]                    .getContentValues());        }    }    // 测试查询多条记录    public void testQueryRecords()    {        Cursor cursor = mMockResolver.query(                Family.Member.FAMILY_CONTENT_URI,                null,                 null,                 null,                null);        // 测试数据库中的内容是否为空        assertEquals(0, cursor.getCount());        // 建立测试数据        insertData();        // 再次查询        cursor = mMockResolver.query(                Family.Member.FAMILY_CONTENT_URI,                 null,                null,                null,                 null);        assertEquals(TEST_MEMBERS.length, cursor.getCount());        /**         * 测试通过SQL条件语句查询出的数据         */               // 定义查询name字段内容的SQL语句        final String NAME_SELECTION = Family.Member.NAME + " = " + "?";        //条件语句-->查询name列字段值分别为name0,name2,name7的三行数据        final String SELECTION_COLUMNS_NAME = NAME_SELECTION + " OR " + NAME_SELECTION + " OR "                + NAME_SELECTION;        final String[] SELECTION_ARGS =        {                "name0", "name2", "name7"        };                // 查询出的三行数据中包含name,age,gender列字段对应的值(顺序不能颠倒)        final String[] TEST_PROJECTION =        {                Family.Member.NAME, Family.Member.AGE, Family.Member.GENDER        };        // 定义查询排序顺序(ASC为升序,DESC为降序)        final String SORT_ORDER = Family.Member.NAME + " ASC";        // 查询列字段        Cursor projectionCursor = mMockResolver.query(                Family.Member.FAMILY_CONTENT_URI,                TEST_PROJECTION,                null,                 null,                 null);        // 测试查询所得的列字段数        assertEquals(TEST_PROJECTION.length, projectionCursor.getColumnCount());        // 测试查询所得列字段名        assertEquals(TEST_PROJECTION[0], projectionCursor.getColumnName(0));        assertEquals(TEST_PROJECTION[1], projectionCursor.getColumnName(1));        assertEquals(TEST_PROJECTION[2], projectionCursor.getColumnName(2));        //根据SQL条件语句查询数据        projectionCursor = mMockResolver.query(                Family.Member.FAMILY_CONTENT_URI,                 TEST_PROJECTION,  //get the name, age, and gender columns                SELECTION_COLUMNS_NAME, //select on the name column                SELECTION_ARGS, // select names "name0", "name2", or "name7"                SORT_ORDER // sort ascending on the name column                );        //测试条件查询出的name列字段内容数量        assertEquals(SELECTION_ARGS.length, projectionCursor.getCount());        int index = 0;        while (projectionCursor.moveToNext())        {            //测试查询出的name列字段内容            assertEquals(SELECTION_ARGS[index], projectionCursor.getString(0));            index++;        }        assertEquals(SELECTION_ARGS.length, index);    }    // 测试查询单条记录    public void testQueryRecord()    {    //查询出gender字段值为gender2的一行数据    final String SELECTION_COLUMNS_GENDER = Family.Member.GENDER + " = " + "?";    final String[] SELECTION_ARGS = { "gender2"};        //查询顺序为升序    final String SORT_ORDER = Family.Member.GENDER + " ASC";        //查询出的一行数据中只有_id和gender字段对应的值    final String[] TEST_PROJECTION = {Family.Member._ID, Family.Member.GENDER};        //给MEMBER_CONTENT_URI追加id为1    Uri memberIdUri = ContentUris.withAppendedId(Family.Member.MEMBER_CONTENT_URI, 1);        Cursor cursor = mMockResolver.query(      memberIdUri,               null,                    null,                    null,                  null                 );        assertEquals(0,cursor.getCount());        //建立测试数据    insertData();        //使用面向表格全部数据的uri查询    cursor = mMockResolver.query(              Family.Member.FAMILY_CONTENT_URI,               TEST_PROJECTION,                     SELECTION_COLUMNS_GENDER,                  SELECTION_ARGS,                         SORT_ORDER                          );        assertEquals(1, cursor.getCount());    //移至第一行,测试数据存在    assertTrue(cursor.moveToFirst());        //获取查询得到数据对应的id    int inputMemberId = cursor.getInt(0);        //给MEMBER_CONTENT_URI追加id为查询所得数据对应的id    memberIdUri = ContentUris.withAppendedId(Family.Member.MEMBER_CONTENT_URI, inputMemberId);        //使用单条记录对应的uri查询    cursor = mMockResolver.query(      memberIdUri,       TEST_PROJECTION,                       SELECTION_COLUMNS_GENDER,                               SELECTION_ARGS,                                   SORT_ORDER                                    );        assertEquals(1, cursor.getCount());           assertTrue(cursor.moveToFirst());        //测试追加到uri中的id与uri查询出的数据所对应的id是否一致    assertEquals(inputMemberId, cursor.getInt(0));        //注:查询字段对应值顺序不能颠倒    assertEquals("gender2", cursor.getString(1));        /*for (int i = 0; i < cursor.getCount(); i ++)    {    Log.d("TestProvider", "Id-->" + cursor.getInt(0));        Log.d("TestProvider", "name-->" + cursor.getString(1));        Log.d("TestProvider", "age-->" + cursor.getInt(2));        Log.d("TestProvider", "gender-->" + cursor.getString(3));        cursor.moveToNext();    }*/    }    //测试删除    public void testDeletes()    {    final String SELECTION_COLUMNS_AGE = Family.Member.AGE + " = " + "?";        final String[] SELECTION_ARGS = { "0" };        int rowsDeleted = mMockResolver.delete(                Family.Member.FAMILY_CONTENT_URI,                 SELECTION_COLUMNS_AGE,                         SELECTION_ARGS                         );        //在执行删除数据前还未向表中插入数据,所以删除行数为0    assertEquals(0, rowsDeleted);        //建立数据    insertData();        //执行删除    rowsDeleted = mMockResolver.delete(                Family.Member.FAMILY_CONTENT_URI,                 SELECTION_COLUMNS_AGE,                         SELECTION_ARGS                         );    //测试是否删除一行数据    assertEquals(1, rowsDeleted);        //查询表中已被删除的数据    Cursor cursor = mMockResolver.query(    Family.Member.FAMILY_CONTENT_URI,                 null,                                      SELECTION_COLUMNS_AGE,                        SELECTION_ARGS,                            null                                  );        //测试数据是否被成功删除    assertEquals(0, cursor.getCount());    }        // 测试结束后会自动清空数据库中的数据,此处为了验证结束后insertData方法是否正确插入数据    @Override    protected void tearDown() throws Exception    {        // TODO Auto-generated method stub        super.tearDown();        insertData();    }}

最后,工程全部建立完后,点击TestProvider.java--->鼠标右键--->Run As--->Android Junit Test即可完成测试。
相关代码下载链接:
http://download.csdn.net/detail/stevenhu_223/6491553

更多相关文章

  1. android 数据传输之JSON
  2. Android(安卓)ContentProvider的介绍
  3. 林志华_Android
  4. 在 Android(安卓)应用中使用数据库
  5. Android(安卓)与 JS 交互数据上限问题【Refusing to load URL as
  6. Android(安卓)输入系统
  7. 做了一个手机上的直播系统
  8. android sqlite数据库封装 实现crud
  9. mybatisplus的坑 insert标签insert into select无参数问题的解决

随机推荐

  1. 对比Vue2中Options API 和 Vue3中Composi
  2. 在Vue3中使用Provide / Inject
  3. 巧用状态伪类:让你的链接不尴尬
  4. JavaScript初学习之常量、变量、函数、匿
  5. Detach RDM Luns from single ESXi host
  6. Java与php的一些关联
  7. Druid源码分析与架构介绍
  8. 全息电力行业解决方案
  9. 1209表格
  10. 1209列表元素