測試 之Java單元測試、Android單元測試

學術袁發表於2018-06-23

這裡寫圖片描述
我的目的,旨在介紹一個不一樣的“單元測試” !
其實對於單元測試這一塊,我很早已經開始關注了,也蒐羅了好多這方面技術的部落格。要麼只有文字性的,要麼都是程式碼的邏輯類,籠統、沒有什麼脈絡可循。之後依然半解,放到專案中,用起來還是不方便,就是覺得這樣比我直接執行在模擬器(真機)之後的過程列印、除錯要慢好多!
因此,就導致後來的放棄。以及今天的再次拾起,並做一個系統點的介紹,希望特別想要使用單元測試的朋友能夠用得著。

不一樣 、不一樣的單元測試

這裡寫圖片描述
上面,我擷取了一個專案中module的目錄結構圖。看完之後,映入眼簾的是紅色框中那兩個括弧中的內容

androidTest
test

她們倆是做什麼的?我們就只從JUit出發,進行詳細無死角的介紹


JUnit 測試

一般而言,使用Android Studio做安卓開發的我們對專案做測試,無論專案搭建的準備工作或是專案開發進行中的功能性測試。都無非用到兩種環境下的測試:

test androidTest
Java環境下的測試 Android環境下的測試

區別

環境配置上的區別

module下的build.gradle檔案中那行行程式碼
1,支援能夠Java虛擬機器裝置環境下的預設環境配置

/** build.gradle/dependencies{}中 **/
testCompile 'junit:junit:4.12'

2,支援能夠Android裝置環境下的預設環境配置

/** build.gradle/dependencies{}中 **/
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
/** 且android/defaultConfig{}節點要加上 **/
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

程式碼類配置的區別

她們之間有明眼的區別,class註解上 的@RunWith(AndroidJUnit4.class)
即可看到,IDE在兩個包內也為我們生成的測試程式碼類
在Android裝置上執行的範例(androidTest)包下 && 在Java虛擬機器裝置上執行的範例(test)包

好的,我們該如何使用呢?這是個關鍵問題!接下來看兩種使用方式


test包下的Java環境下的測試

針對的是安卓專案。在基於MVP架構的基礎上,使用OkHttp作網路資料請求,並對其做簡單封裝,以使用見最簡單方式來實現與後臺的資料互動。
測試目的:測試get和post兩種請求方式是否成功
構建測試方式一 在要測試的類中 右擊滑鼠/Go To/Test/Create New Test..
之後,你會發現新彈出的頁面

Testing library Class Superclass Destination Package Generate Generate test methods for member
JUnit4 測試的類名 測試類的父類名 包名 勾選 不勾選 選擇性勾選

隨便展示下圖片效果,如圖<圖片很隨便,內容真誠>
這裡寫圖片描述
點選確定進入新的頁面框,走入目錄
這裡寫圖片描述
當然是選擇java虛擬機器裝置下的測試方式哦!
然後測試類則生成,但是需要注意的是。你使用了這種方式並不會一定會生成你想要的效果。為什麼?
答案很簡單,使用單元測試,測試的程式碼邏輯塊必須是獨立的。
拿此例來說,使用okhttp的post方法做測試是否與後臺能聯通?那麼,就必須讓測試的程式碼邏輯塊與無關的類解耦。比如在測試方法中以簡潔的程式碼邏輯塊 實現。或者,寫一個管理類能直接管理post呼叫的操作。
好,我上程式碼表示,以簡潔的程式碼邏輯塊 實現post請求

    /**
     * 功能:使用post方式進行http請求的測試
     */
    @Test
    public void post() {
        MediaType JSON
                = MediaType.parse("application/json; charset=utf-8");

        OkHttpClient client = new OkHttpClient();
        String url = "http://httpbin.org/post";
        RequestBody body = RequestBody.create(JSON, "{\"name\":\"xueshuyuan\"}");
        Request request = new Request.Builder()
                .url(url)
                .post(body)
                .build();
        Response response = null;
        try {
            response = client.newCall(request).execute();
            System.out.println("輸出get方式請求的結果:==>>" + response.body().string());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

當然也能使用一個類先對上面的程式碼邏輯塊 封裝,然後在測試中一行程式碼邏輯搞定。

一般的測試結構,就是生成了這樣的效果。這種結構基本是固定的,所以自己完全可以在(test)包中手動建立。

public class xxxTest {

    ...

    /**
     * 功能:在get()/post()方法執行之前優先執行
     * @throws Exception
     */
    @Before
    public void setUp() throws Exception {
        ..
    }


    @Test
    public void post() throws Exception {
        ..
    }
    @Test
    public void XXMethod() throws Exception {
        ..
    }
     /**
     * 功能:在加註解@Before的方法setUp()執行之後立即執行
     * @throws Exception
     */
    @after
    public void TearDown() throws Exception {
        ..
    }

}

其中,上標註了@Before @after 方法名是固定的,並且執行順序也是固定的。

比如對方法post進行測試,右擊滑鼠,選中執行Run 'post()'
這裡寫圖片描述
列印結果:
這裡寫圖片描述

如果我換一個單元測試類,這時用到了Rxjava進行了執行緒的排程,但依然是基於Java環境的單元測試

/**
 * @Title:RxjavaTestInJava 
 * @Auther:YJH
 * @Email:yuannunhua@gmail.com
 * @Date:2018/6/23 20:13
 */
public class RxjavaTestInJava {

    @Before
    public void setUp() throws Exception {
        Thread.currentThread().setName("currentThread");
    }

    @Test
    public void schedulerTest() {
        //觀察者(訂閱者)
        final Subscriber<String> subscriber = new Subscriber<String>() {
            @Override
            public void onCompleted() {

                System.out.println("onCompleted=" + Thread.currentThread().getName());
            }

            @Override
            public void onError(Throwable e) {
                System.out.println("onError=" + Thread.currentThread().getName());
                e.printStackTrace();
            }

            @Override
            public void onNext(String result) {
                System.out.println("onNext=" + Thread.currentThread().getName());
                System.out.println("onNext=" + result);

            }
        };

        //被觀察者
        final Observable observable = Observable.create(new Observable.OnSubscribe<Subscriber>() {

            @Override
            public void call(Subscriber subscriber1) {

                System.out.println("Observable-call=" + Thread.currentThread().getName());
                subscriber1.onStart();
                subscriber1.onNext("hello world");
                subscriber1.onCompleted();
            }
        });


        observable.subscribeOn(Schedulers.io()) //指生產事件在當前的執行緒中進行
                .observeOn(AndroidSchedulers.mainThread()) //指消費事件在主執行緒中進行
                .subscribe(subscriber);

    }

    public class User {
        private String name;


        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }

}

接下來你會看到報錯、報錯
這裡寫圖片描述

但如果,我把第56行程式碼換掉,換作.observeOn(Schedulers.newThread())結果又會不同且正常執行。那是為什麼呢?


androidTest 包下的Android環境下的測試

原因是,在第56行程式碼使用了Android的API,已經不是能在Java環境下正常執行的了。所以為了執行該程式碼邏輯塊,看看是否是我們想要的邏輯程式碼。這時就需要執行在Android環境下。即在(androidTest)包下新建一個同樣的類邏輯,只做些許的必要修改 (改變參照兩種測試環境的不同而做改變)
1,類名上新增註解 @RunWith(AndroidJUnit4.class)
2,增加一些Android平臺的註釋 Log.e 以做日誌列印

/**
 * @Title:RxjavaTestInJava 
 * @Auther:YJH
 * @Email:yuannunhua@gmail.com
 * @Date:2018/6/23 20:13
 */

@RunWith(AndroidJUnit4.class)
public class RxjavaTestInJava {
 @Before
    public void setUp() throws Exception {
        Thread.currentThread().setName("currentThread");
    }


    @Test
    public void schedulerTest() {
        final String tag = "test";
        //觀察者(訂閱者)
        final Subscriber<String> subscriber = new Subscriber<String>() {
            @Override
            public void onCompleted() {

                System.out.println("onCompleted=" + Thread.currentThread().getName());
                Log.e(tag, "onCompleted=" + Thread.currentThread().getName());
            }

            @Override
            public void onError(Throwable e) {
                System.out.println("onError=" + Thread.currentThread().getName());
                e.printStackTrace();
            }

            @Override
            public void onNext(String result) {
                System.out.println("onNext=" + Thread.currentThread().getName());
                System.out.println("onNext=" + result);
                Log.e(tag, "onNext=" + result);

            }
        };

        //被觀察者
        final Observable observable = Observable.create(new Observable.OnSubscribe<Subscriber>() {

            @Override
            public void call(Subscriber subscriber1) {

                System.out.println("Observable-call=" + Thread.currentThread().getName());
                Log.e(tag, "Observable-call=" + Thread.currentThread().getName());
                subscriber1.onStart();
                subscriber1.onNext("hello world");
                Log.e(tag, "hello world");
                subscriber1.onCompleted();
            }
        });


        observable.subscribeOn(Schedulers.io()) //指生產事件在當前的執行緒中進行
                .observeOn(AndroidSchedulers.mainThread()) //指消費事件在主UI執行緒中進行
                .subscribe(subscriber);


    }


    public class User {
        private String name;


        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }
}

然後同樣的執行方式,在方法schedulerTest右擊滑鼠run ‘schedulerTest()’執行測試!
這裡寫圖片描述
然後你會看到彈窗
這裡寫圖片描述
然後你會發現該指示是讓你啟動一個Android模擬器裝置,顯然是要執行到上面了。然而結果卻是app並不會到模擬器上啟動,而是”後臺執行”。以這種方式執行了我們的單元測試。
欣賞下Run下的執行結果
這裡寫圖片描述
然後欣賞下Android Monitor下的執行結果
這裡寫圖片描述
說明一切都在我們的掌握之中,能夠讓我看到該單元測試結果,並能夠確認該邏輯是否是我們想要的正確邏輯,從而起到單元測試的作用!

相關文章