Android单元测试(九):查漏补缺篇

1,113 阅读4分钟

最近抽时间检查了一下年前有关单元测试的八篇博客,查漏补缺了一下。后面如果有更多关于单元测试的心得收获,也会继续补充。

1.AssertJ

Android单元测试(一):JUnit框架的使用中,我们介绍了如何使用JUnit来进行断言。不多说实话JUnit使用起来还是不太友好,不是很直观。所以补充介绍一下AssertJ

AseertJ: JAVA 流式断言器,什么是流式,常见的断言器一条断言语句只能对实际值断言一个校验点,而流式断言器,支持一条断言语句对实际值同时断言多个校验点。

    //AssertJ 2.x 对应 Java 7 以上.
    testImplementation 'org.assertj:assertj-core:2.9.1'
    //AssertJ 3.x 对应 Java 8 以上. 
    testImplementation 'org.assertj:assertj-core:3.10.0'

类中导入:

    // 静态导入所有方法
    import static org.assertj.core.api.Assertions.*;
    //android使用这个静态导入
    import static org.assertj.core.api.Java6Assertions.*;

部分使用方法:

    @Test
    public void test(){
        // 断言空字符串
        assertThat("").isEmpty();
        // as指定错误信息
        assertThat("weilu").as("没有a或者b").contains("a").contains("b");

        // 断言相等  
        assertThat(42).isEqualTo(42);
        // 断言大于 大于等于  
        assertThat(42).isGreaterThan(38).isGreaterThanOrEqualTo(38);

        // 断言时间再指定范围内 不在指定范围内  
        assertThat(parse("2018-05-15"))
                .isBetween("2018-04-01", "2018-06-01")
                .isNotBetween("2019-01-01", "2019-12-31");

        // 断言 列表是空的  
        assertThat(newArrayList()).isEmpty();
        // 断言 列表的开始 结束元素  
        assertThat(newArrayList(1, 2, 3)).startsWith(1).endsWith(3);

        Map<String, Integer> foo = Maps.newHashMap("A", 1);
        foo.put("B", 2);
        foo.put("C", 3);
        // 断言 map 不为空 size  
        assertThat(foo).isNotEmpty().hasSize(3);
        // 断言 map 包含元素  
        assertThat(foo).contains(entry("A", 1), entry("B", 2));
    }

当然对于Android还有 AssertJ-Android,它是AssertJ的拓展,更加便于我们断言Android View。

常用的有以下五个,当然官方还提供了更多。

    androidTestCompile 'com.squareup.assertj:assertj-android:1.2.0'
    androidTestCompile 'com.squareup.assertj:assertj-android-support-v4:1.2.0'
    androidTestCompile 'com.squareup.assertj:assertj-android-appcompat-v7:1.2.0'
    androidTestCompile 'com.squareup.assertj:assertj-android-design:1.2.0'
    androidTestCompile 'com.squareup.assertj:assertj-android-recyclerview-v7:1.2.0'

静态导入:

    //android
    import static org.assertj.android.api.Assertions.assertThat;  
    //support-v4
    import static org.assertj.android.support.v4.api.Assertions.assertThat; 
    //appcompat-v7
    import static org.assertj.android.appcompat.v7.api.Assertions.assertThat; 
    //design
    import static org.assertj.android.design.api.Assertions.assertThat; 
    //recyclerview-v7
    import static org.assertj.android.recyclerview.v7.api.Assertions.assertThat; 

部分使用方法:

    @Test
    public void testView() {
        // Button是否可见
        assertThat(mJumpBtn).isVisible();
        // LinearLayout 方向,子View数量
        assertThat(mRoot)
                .isVertical()
                .hasChildCount(4);
        // CheckBox是否未选中
        assertThat(checkBox).isNotChecked();
    }

查看isNotChecked源码可以的看到,就是利用AssertJ做了一层封装。

这里写图片描述

2.Robolectric相关

Android单元测试(四):Robolectric框架的使用中,我说到了在gradle版本为3.0以上使用Robolectric会报错Resources$NotFoundException,如果你真的需要在3.0以上使用(本人现使用3.1.2,亲测有效), 可以这样

在gradle中添加(推荐):

    testOptions {
        unitTests {
            includeAndroidResources = true
        }
    }

或者在gradle.properties中添加:

    android.enableAapt2=false

最近将Robolectric版本从3.5.1升到3.8版本,出现错误:

java.lang.VerifyError: class org.robolectric.android.fakes.RoboMonitoringInstrumentation overrides final method specifyDexMakerCacheProperty.()V

解决方法:

testImplementation "com.android.support.test:monitor:1.0.2"

3.网络接口测试相关

Android单元测试(五):网络接口测试中我们有说到使用MockWebServer来模拟网络请求并返回数据,使用起来还是有些繁琐。下面我推荐一个简单方法:使用框架RESTMock

直接上代码,测试正常情况(可以设置响应时间):

@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 23)
public class RESTMockTest {

    private GithubApi mockGithubService;

    @Rule
    public RxJavaRule rule = new RxJavaRule();

    @Before
    public void setUp(){
        ShadowLog.stream = System.out;
        // 启动服务
        RESTMockServerStarter.startSync(new JVMFileParser());

        //定义Http Client,并添加拦截器
        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .addInterceptor(new LoggingInterceptor())
                .build();

        //设置Http Client
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(RESTMockServer.getUrl())  //<--注意这里
                .client(okHttpClient)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();

        mockGithubService = retrofit.create(GithubApi.class);
    }

    @Test
    public void getUserTest() throws Exception {
        RESTMockServer.whenGET(pathContains("users"))
//                .delay(TimeUnit.SECONDS, 5) // 模拟响应时长
                .thenReturnFile(200, "json/users.json");

        mockGithubService.getUser("weilu")
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<User>() {
                    @Override
                    public void onSubscribe(Disposable d) {
                    }

                    @Override
                    public void onNext(User user) {
                        assertEquals("唯鹿", user.name);
                        assertEquals("http://blog.csdn.net/qq_17766199", user.blog);
                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.e("Test", e.toString());
                    }

                    @Override
                    public void onComplete() {
                    }
                });
    }

}

这里写图片描述

测试异常情况:

    @Test
    public void testNotFound() throws Exception {
        RESTMockServer.whenGET(pathEndsWith("weilu")).thenReturnString(404, "{message : \"服务器异常\"}");
        mockGithubService.getUser("weilu")
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<User>() {
                    @Override
                    public void onSubscribe(Disposable d) {}

                    @Override
                    public void onNext(User user) {
                        assertEquals("唯鹿", user.name);
                        assertEquals("http://blog.csdn.net/qq_17766199", user.blog);
                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.e("Test", e.toString());
                    }

                    @Override
                    public void onComplete() {}
                });
    }

这里写图片描述

4.数据库的单元测试

数据库这部分的单元测试我之前没有提到,毕竟还是需要多思考多练习举一反三,不可能将方方面面都说到。其实关于它的测试与之前没有什么区别。比如我们常使用的greenDAODBFlowRealm,在它的Github首页就有单元测试的用例,不信你们去看看(手动滑稽)

这里写图片描述

5.参考

基本我们用到的热门框架与项目(例如RxJava/谷歌官方应用架构等),都会有相关的单元测试,我们可以参考他们的测试用例来学习。当然所使用的测试框架基本都是我们之前提到的。不得不说国外还是很重视这方面的。

所有代码已上传至Github。希望大家多多点赞支持!