Android 單元測試實踐

若是想看更多文章能夠到個人博客,博客會先更新:
Android 單元測試實踐javascript

單元測試是什麼

單元測試 是針對 程序的最小單元 來進行正確性檢驗的測試工做。程序單元是應用的最小可測試部件。一個單元多是單個程序、類、對象、方法等。 ——維基百科複製代碼

爲何要作單元測試

賣個關子,看完文章天然就知道了複製代碼

原來和不少人同樣並無寫單元測試的習慣,寫好一個功能模塊以後直接在真機上作自測,看看剛寫的功能是否和預期一致,若是不一致,從頭debug找問題出在哪兒,沒問題就提交測試,測試測出問題,再從頭debug找問題出在哪兒。這個過程通常會比較費時,但一直以來都這麼幹,也沒發現有什麼問題。java

後來看到一些安利單元測試的文章,被洗腦似的決定開始寫單元測試。android

而後,就沒有而後了。安全

原來全部的代碼邏輯都在Activity裏,如何寫單元測試?瞬間懵逼了。框架

不少公司或我的不肯意寫單元測試的緣由多是以爲寫單元測試並沒用有什麼卵用,項目比較趕根本沒有時間寫單元測試,不知道從何下手,特別是 Android 應用更是比較難寫單元測試。ide

後來看到有文章說使用MVP模式能夠方便的寫單元測試,並且可使用Junit寫單元測試,直接運行在JVM上,而不須要運行在Android環境中。單元測試

而後就有了這篇文章《如何將原項目重構成MVP模式》測試

開始實踐

寫了這麼多鋪墊,終於能夠開始操刀寫單元測試了,各位看官是否是已經急不可耐了。spa

就拿這個類開始寫單元測試吧:debug

public class CreditCardPresenter extends BasePresenter<CreditCardContract.View, CreditCardContract.Model> implements CreditCardContract.Presenter {

    //其餘代碼略
    public void getCreditCards() {
        getModel().getCreditCards()
                .subscribe(new Subscriber<List<CreditCard>>(){
                    @Overridec
                    public void onNext(List<CreditCard> creditCards) {
                        getView().showCreditCards(creditCards);
                    }

                    @Override
                    public void onCompleted() {
                        getView().loadCompleted();
                    }

                    @Override
                    public void onError(Throwable e) {
                        getView().showError(e);
                    }

                });
    }
}複製代碼

功能很簡單,就是獲取信用卡列表,若是獲取成功就經過下面的代碼顯示:

getView().showCreditCards(creditCards);
getView().loadCompleted();複製代碼

若是出錯,則通知頁面顯示錯誤信息:

getView().showError(e);複製代碼

又懵逼了,getModel() 和 getView() 裏面仍是會調用安卓的代碼,怎麼使用Junit作測試呢?

引入一個強大的測試框架:Mockito,接下來就能夠開始使用Junit & Mockito作Java代碼的單元測試了,這種方式的單元測試能夠直接運行與JVM上,使用Mockito隔離Android相關代碼。

而後就能夠爲CreditCardPresenter 寫單元測試了,爲了方便,靜態導入了Mockito的全部方法:

import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class CreditCardPresenterTest {

    CreditCardPresenter creditCardPresenter;
    @Mock
    CreditCardContract.View creditCardView;
    @Mock
    CreditCardContract.Model creditCardModel;

    List<CreditCard> creditCards;

    @Before
    public void setUp() throws Exception {
        creditCardPresenter = new CreditCardPresenter();
        creditCardPresenter.attachView(creditCardView);
        creditCardPresenter.setModel(creditCardModel);
        creditCards = new ArrayList<>();
    }

    public void testGetCreditCards() {
        when(creditCardModel.getCreditCards()).thenReturn(Observable.create(new Observable.OnSubscribe<List<CreditCard>>() {
            @Override
            public void call(Subscriber<? super List<CreditCard>> subscriber) {
                subscriber.onNext(creditCards);
                subscriber.onCompleted();
            }
        }));

        creditCardPresenter.getCreditCards();

        verify(creditCardView).showCreditCards(creditCards);
        verify(creditCardView).loadCompleted();
    }

    public void testGetCreditCardsOnError() {
        final RuntimeException exception = new RuntimeException();
        when(creditCardModel.getCreditCards()).thenReturn(Observable.create(new Observable.OnSubscribe<List<CreditCard>>() {
            @Override
            public void call(Subscriber<? super List<CreditCard>> subscriber) {
                throw exception;
            }
        }));

        creditCardPresenter.getCreditCards();

        verify(creditCardView).showError(exception);
    }

}複製代碼

這樣就爲上述兩種狀況寫了兩個單元測試。
其中使用@Mock註解來生成mock對象,也能夠setUp方法中使用Mockito.mock()來生成mock對象,當使用註解的時候在類上必須加上註解@RunWith(MockitoJUnitRunner.class)
mock出來的對象的方法都是空實現,void方法聲明也不作,有返回值的方法返回null(int 類型返回0,boolean類型返回false等)。

而後咱們能夠經過when(...).thenReturn(...)來爲mock對象實現方法返回值。

when(creditCardModel.getCreditCards()).thenReturn(Observable.create(new Observable.OnSubscribe<List<CreditCard>>() {
            @Override
            public void call(Subscriber<? super List<CreditCard>> subscriber) {
                subscriber.onNext(creditCards);
                subscriber.onCompleted();
            }
        }));複製代碼

上面代碼的意思就是說當調用creditCardModel.getCreditCards()的時候返回值是:

Observable.create(new Observable.OnSubscribe<List<CreditCard>>() {
            @Override
            public void call(Subscriber<? super List<CreditCard>> subscriber) {
                subscriber.onNext(creditCards);
                subscriber.onCompleted();
            }
        })複製代碼

最後使用verify()方法來校驗某個方法是否被執行:

verify(creditCardView).showCreditCards(creditCards);複製代碼

上面的代碼意思就是說 creditCardView.showCreditCards(creditCards)方法被執行了,而且參數是creditCards,而且只執行了一次。若是有一個條件不符合就會報測試失敗。
verify()還有不少重載方法,默認實際上是這樣的 verfy(creditCardView, times(1)).showCreditCards(creditCards); 校驗只執行了一次,times(1) 能夠傳入不一樣的參數來校驗方法被執行了幾回。還能夠替換了nerver(),表示某方法一次也不執行。
固然Mockito的功能遠不止這麼點,還有不少高級用法就不繼續介紹了。
Mockito也有一些美中不足之處,不能mock靜態方法,final方法等,好比項目中會有這樣的方法 SelfApplication.getContext() 來獲取自定義的Application,若是在測試代碼中出現這類代碼確定會測試失敗,由於JVM環境中沒有Application,怎麼辦呢?
再引入一個配合Mockito使用的庫:PowerMock
他彌補了Mockito的不足,能夠mock靜態方法和final方法,可使用PowerMock來mock出SelfApplication.getContext(),從而不會調用到真正的Application對象:

PowerMockito.mockStatic(SelfApplication.class);
PowerMockito.when(SelfApplication.getContext()).thenReturn(mock(SelfApplication.class));複製代碼

另外,在方法上要聲明@PrepareForTest(SelfApplication.class), 在類上要聲明 @RunWith(PowerMockRunner.class) 來支持上述mock。這樣當 調用SelfApplication.getContext()的時候將拿到一個mock對象,咱們就能夠繼續使用when().thenReturn()方法來處理方法返回值了。具體關於Mockito 和 PowerMock 的更多用法這裏就不作過多介紹了,官網纔是最好的教程。這只是一個簡單的例子,實際項目中會出現好的複雜的狀況。這就要求我寫的代碼方法要短,耦合要低。寫單元測試逼迫咱們寫更優雅的代碼,也爲咱們下次修改需求或者重構代碼提供了一道安全保障。還有其餘更多的好處你們本身在實踐中體會吧。

相關文章
相關標籤/搜索