1. 程式人生 > >Android單元測試(九):查漏補缺篇

Android單元測試(九):查漏補缺篇

最近抽時間檢查了一下年前有關單元測試的八篇部落格,查漏補缺了一下。後面如果有更多關於單元測試的心得收穫,也會繼續補充。

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並補充了Kotlin版。希望大家多多點贊支援!