Android单元测试环境配置指南

本文介绍如何配置Android Studio下的单元测试环境,并重点讲解如何使用Robolectric进行AndroidUnitTest配置,包括解决国内下载速度慢的问题。

由于客户要求手机端APP要有单元测试并达到一定的覆盖率,本人花了几天研究一下AndroidStudio下单元测试的环境配置,经历了大大小小不少坑,这里写出来给需要的小伙伴一个参考。

1.概念

安卓测试分为AndroidInstrumentTest和AndroidUnitTest。区别是前者需要安卓模拟器或实机测试,后者只需JVM环境。
使用AndroidStudio创建Android项目后可以看到src下有如下目录
安卓工程目录结构
其中androidTest存在AndroidInstrumentTest代码,test存在AndroidUnitTest代码。
相比较来说,使用AndroidUnitTest更舒服一些。AndroidInstrumentTest的话大家可以想象一下在模拟器或实机上调试Android工程,速度是比较慢的。
所以这里配置的是AndroidUnitTest的环境。

2.配置

AndroidStudio自带JUnit,但是这个尚不足够。Android程序是运行在DVM上的,想要他在JVM环境下直接运行,就需要一个模拟环境。这就需要第三方测试框架Robolectric。具体就不介绍了,大家可以上官方网站自己看,配置方法也很简单:

修改build.gradle里的dependencies

dependencies {
    ...
    testCompile ('junit:junit:4.12')
    testCompile ('org.robolectric:robolectric:3.2.2')
    testCompile ("org.robolectric:shadows-support-v4:3.2")
    ...
}

其中shadows-support-v4提供了对应support-v4包的测试类,如果不需要删除。

测试类配置如下

@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 22)
public class ExampleUnitTest {
    @Before
    public void setup()
    {
        ...
    }
    @Test
    public void addition_isCorrect() throws Exception {
        assertEquals(4, 2 + 2);
    }
}

是不是很简单?然而如果仅仅是这样就没必要写这篇文章了,请大家往下看

3.遇到的坑

如果你在国内且没有VPN,在配置dependencies后,你就会发现gradle在自动build下载时很慢。就算你耐心等待它完成后,在运行时你会发现Robolectric会需要根据你的配置进一步下载一些包,包很大,下载速度却慢的令人发指。
这里推荐使用aliyun的国内maven镜像库。
修改工程目录下的build.gradle

buildscript {
    repositories {
        jcenter(){ url 'http://maven.aliyun.com/nexus/content/groups/public/'}
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.3'
    }
}
allprojects {
    repositories {
        jcenter(){ url 'http://maven.aliyun.com/nexus/content/groups/public/'}
    }
}
task clean(type: Delete) {
    delete rootProject.buildDir
}

Gradle的build问题就解决了。但是Robolectric呢?
官方网站上推荐设置systemProperty来手动修改库的url,修改方法如下

android {
  testOptions {
    unitTests.all {
      systemProperty 'robolectric.dependency.repo.url', 'https://local-mirror/repo'
      systemProperty 'robolectric.dependency.repo.id', 'local'
    }
  }
}

然而这不起作用,所以这里需要修改RobolectricTestRunner.class。
本人这里就直接在网上找个代码。当然到GitHub上找到Robolectric原代码自己重写也是可以的

public class CustomRepoRobolectricTestRunner extends RobolectricTestRunner {
    public static final String REP_URL = "http://maven.aliyun.com/nexus/content/groups/public/";
    public static final String REP_ID = "aliyun_mirror";

    public CustomRepoRobolectricTestRunner(Class<?> klass) throws InitializationError {
        super(klass);
    }

    //覆盖该方法 可以定制DependencyResolver
    @Override
    protected DependencyResolver getJarResolver() {
//        String repoUrl = System.getProperty("robolectric.dependency.repo.url", ".");
//        String repoId = System.getProperty("robolectric.dependency.repo.id", ".");
        String repoUrl = REP_URL;
        String repoId = REP_ID;
        if (StringUtils.isNotEmpty(repoUrl) && StringUtils.isNotEmpty(repoId)) {
            File cacheDir = new File(new File(System.getProperty("java.io.tmpdir")), "robolectric");
            cacheDir.mkdir();
            DependencyResolver dependencyResolver;
            //如果设置了robolectric.dependency.repo.url属性,则利用LocalNetDependencyResolver来解决依赖
            //这样便可以调整仓库地址
            if (cacheDir.exists()) {
                Logger.info("Dependency cache location: %s", cacheDir.getAbsolutePath());
                dependencyResolver = new CachedDependencyResolver(
                        new LocalNetDependencyResolver(repoUrl, repoId), cacheDir, 60 * 60 * 24 * 1000
                );
            } else {
                dependencyResolver = new LocalNetDependencyResolver(repoUrl, repoId);
            }
            return dependencyResolver;
        } else {
            return super.getJarResolver();
        }
    }

    private static class LocalNetDependencyResolver implements DependencyResolver {
        private String mRepoUrl;
        private String mRepoId;
        private final Project project = new Project();

        public LocalNetDependencyResolver(String repoUrl, String repoId) {
            mRepoId = repoId;
            mRepoUrl = repoUrl;
        }

        @Override
        public URL[] getLocalArtifactUrls(DependencyJar... dependencies) {
            DependenciesTask dependenciesTask = new DependenciesTask();
            configureMaven(dependenciesTask);
            RemoteRepository sonatypeRepository = new RemoteRepository();
            sonatypeRepository.setUrl(mRepoUrl);
            sonatypeRepository.setId(mRepoId);
            dependenciesTask.addConfiguredRemoteRepository(sonatypeRepository);
            dependenciesTask.setProject(project);
            for (DependencyJar dependencyJar : dependencies) {
                Dependency dependency = new Dependency();
                dependency.setArtifactId(dependencyJar.getArtifactId());
                dependency.setGroupId(dependencyJar.getGroupId());
                dependency.setType(dependencyJar.getType());
                dependency.setVersion(dependencyJar.getVersion());
                if (dependencyJar.getClassifier() != null) {
                    dependency.setClassifier(dependencyJar.getClassifier());
                }
                dependenciesTask.addDependency(dependency);
            }
            dependenciesTask.execute();

            @SuppressWarnings("unchecked")
            Hashtable<String, String> artifacts = project.getProperties();
            URL[] urls = new URL[dependencies.length];
            for (int i = 0; i < urls.length; i++) {
                try {
                    urls[i] = Util.url(artifacts.get(key(dependencies[i])));
                } catch (MalformedURLException e) {
                    throw new RuntimeException(e);
                }
            }
            return urls;
        }
        @Override
        public URL getLocalArtifactUrl(DependencyJar dependency) {
            URL[] urls = getLocalArtifactUrls(dependency);
            if (urls.length > 0) {
                return urls[0];
            }
            return null;
        }
        private String key(DependencyJar dependency) {
            String key = dependency.getGroupId() + ":" + dependency.getArtifactId() + ":"
                    + dependency.getType();
            if (dependency.getClassifier() != null) {
                key += ":" + dependency.getClassifier();
            }
            return key;
        }
        protected void configureMaven(DependenciesTask dependenciesTask) {
            // maybe you want to override this method and some settings?
        }
    }
}

这里我直接在代码中写了URL,其实通过Gradle配置SystemProperty更为合理。

修改测试代码

@RunWith(CustomRepoRobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 22)
public class ExampleUnitTest {
    @Before
    public void setup()
    {

    }
    @Test
    public void addition_isCorrect() throws Exception {
        assertEquals(4, 2 + 2);
    }
}

这样就配置后可以看到能跑起来了。

还有个注意点Robolectric的sdk版本推荐配置为22,不要试图在更高版本上运行。

参考:
1.http://blog.csdn.net/x356982611/article/details/21983267
2.http://robolectric.org/
3.https://github.com/robolectric/robolectric

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值