想要更好地使用Android的TDD,应用MOCK是必不可少的。那么MOCK又是什么呢?说白了MOCK就是一系列的模拟类,在TDD中使用这些MOCK的类来代替真实的类。那为什么要用MOCK呢?
为了更好的说明用MOCK的理由,请看下图:
在这里模块A依赖于模块B,C,D,而我们要测的就是模块A。但是它有依赖,而且这些依赖说不定现在根本都还没实现或者很复杂,在这种情况下,如果要测模块A,MOCK就十分有必要了。如下图所示,我们将模块B,C,D都使用MOCK来代替,这样就可以消除A的所有依赖,让我们专注于A的测试了。
说起来容易,但是怎么用MOCK来代替所依赖的部分呢?又怎么来实现这些MOCK呢?
打开android.jar的android.test.mock目录,可以看到里面有很多Mockxxx类,这些都是ANDROID提供给我们的可以直接继承来实现的MOCK类,比如说你想创建一个MOCK CURSOR,就可以直接继承MockCursor,然后再加上一些自己的实现就可以了,这个我在下文会做详细的说明。当然可以MOCK的不仅限于这几个类,基本所有的类都可以MOCK,只要继承这个类并且重写你会用到的方法就可以了。当然如果类中有private的方法想要mock就比较麻烦了。
假如说一个应用中有一个ListView,ListView的内容来自于查询content provider得到的cursor。在对这个list进行TDD的时候要怎么来MOCK这个cursor呢?现来看一下android程序是如何得到cursor的呢?没错,用的是getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder),这个方法是Context里面的,而Resolver又会找它注册的那个content provider来查询得到cursor,所以说要得到一个mock cursor,就要按照下面的顺序来:
MockContext->MockContentResolver->MockContentProvider->MockCursor。
所以说第一步要做的工作就是将系统的Content替换为我们mock的MockContext。这一步需要在程序的代码中添加一些额外的实现。一种实现方法是在intent中将mockContext的类名传进去,然后在代码实例化mockContext。
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- String contextWrapperClassName = null;
- Bundle extras = getIntent().getExtras();
- if (extras != null) {
- contextWrapperClassName = extras
- .getString(EXTRA_CONTEXT_WRAPPER);
-
- }
- if(contextWrapperClassName!=null){
- mContextWrapper = Class.forName(contextWrapperClassName).newInstance();
- }else{
- mContextWrapper = new ContextWrapper(this.getApplicationContext());
- }
-
-
- }
ok,这样在Test工程中启动Activity的时候将MockContext的类名加入到Intent中,程序运行起来的时候mContextWrapper将会被替换成我们的MockContext,在程序中需要使用getContentResolver()的时候都使用mContextWrapper.getContentResolver(),这样ContentResolver就会被替换成我们的MockContextResolver。当然要实现这个在MockContext中还要将MockContextProview给注册上。
- public class MyMockContext extends ContextWrapper {
-
- private MyMockContentResolver mContentResolver;
- public MyMockContext(Context targetContext) {
- super(targetContext);
- mContentResolver = new MyMockContentResolver();
- mContentResolver.addProvider(MYAUTHORITY, new MyMockContentProvider());
-
- }
- public ContentResolver getContentResolver() {
- return mContentResolver;
- }
- }
在这里面需要注意是MYAUTHORITY,这里应该写的是真实ContentProvider的上authority。这样程序在找Contentprovider的时候才会找到我们的MyMockContentProvider。当然MockContext的作用不仅仅是这样的,可以说MockContext是Mock的核心,通过MockContext可以实现很多Mock的东西。
对于MyMockContextResolver,不需要什么实现,只要创建这样一个类使其集成MockContextResolver就可以了。但是MyMockContextProvider就需要加上实现的代码了:
- public class MyMockContentProvider extends MockContentProvider {
- public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
- MyMockCursor cursor = new MyMockCursor(uri.toString());
- return cursor;
- }
- }
这样终于可以指向MockCursor了,当然在这里可以对query的uri, projection等做一下处理,如果有多个查询的话就可以返回不同的MockCursor了。
对于MockCursor,要实现的地方就很多了,具体要实现哪些方法也取决于程序中调用了哪些方法。一个简单的MockCursor可以这样;
- public class MyMockCursor extends MockCursor {
-
- public MyMockCursor(String uri) {
-
- }
- }
这个MockCursor内部没有任何数据,也没有实现任何方法,直接使用的话肯定会出错。至于内部的数据,我们可以使用一个ArrayList来实现,这样getCount()方法就可以返回ArrayList的长度,getString方法就可以返回ArrayList中的第n条数据等等。对于列名和列号可以分别存储在两个map中,这样getColumnIndex和getColumnName可以直接查询这两个map就可以了。
一定有人会问,为什么我要继承MockCursor这个类啊,我直接实现Cursor不行吗?其实也不是不可以,只是Cursor是一个接口,内部有N多的方法,如果直接继承Cursor的话就必须要实现所有的方法,而其中有一些你根本就用不着,但是如果继承MockCursor的话你只需要实现需要用的就可以了,一个词:方便。
阅读(9520) | 评论(2) | 转发(0) |