Robolectric 探索之路


layout: post
title: Roboletric探索之路,從抗拒到依賴
description: Roboletric Android Unit Testing
category: blog
---html

我爲何之前抗拒Android Unit Testing

  • 一、懶,人類最大的天敵;java

  • 二、不是不知道什麼是單元測試,只是需求太多了,哪有時間~;android

  • 三、須要學習單元測試的語言或者框架,不熟悉,因此從沒嘗試過;git

  • 四、沒見到單元測試的好處,一想到要花時間就望而卻步;github

  • 五、至少只是我我的以前的感覺,我相信有不少的程序猿同胞們都跟我有相似的感覺;web

既然抗拒,爲何如今要嘗試Android Unit Testing呢

大勢所趨,bug量的增多不得不讓咱們提升代碼的質量,不是咱們完不成功能,只是咱們驗證功能的成本實在過高,隨着工程的複雜度的增長,run一次模擬器或者真機,在window上的花費至少是一分鐘以上,甚至三四分鐘,因此有些人偷懶,包括我,有時候把那些看上去「沒有問題的代碼」提交到了主幹上,隨之產生了bug,而後進入修復bug-》run-》修復bug->run;花費了更多的時間和資源;api

咱們的燃眉之急是要儘快改善這個問題,從根源着手,就是【加強自測】緩存

測試手段

如今是個講究效率的時代,咱們但願可以快速高效的驗證咱們的代碼邏輯是否有問題,咱們不但願驗證一個簡單的邏輯或者一個方法是否有效,是經過run一次模擬器或者整個工程實現的,這樣花費的時間太長了,下降了開發效率;app

  • 一、因此咱們要解決的第一個痛點是,如何快速驗證;框架

咱們選擇了Robolectric單元測試框架,緣由有好幾個,最大的緣由是:

Robolectric
Test-Drive Your Android Code
Running tests on an Android emulator or device is slow! Building, deploying, and launching the app often takes a minute or more. That's no way to do TDD. There must be a better way.

Wouldn't it be nice to run your Android tests directly from inside your IDE? Perhaps you've tried, and been thwarted by the dreaded java.lang.RuntimeException: Stub!?

它不須要Run你的模擬器,直接在jvm上運行你的測試代碼,能在幾秒鐘以內快速驗證,經過體驗以後,它確實很是高效,編寫測試代碼反而加速了開發效率。
具體的原理描述可參見:

Robolectic官網

Robolectic介紹

Talk is cheap ,show me the code

環境配置

Android Stuido 1.5.1

junit:junit:4.12

Robolectric 3.0(不要用3.0-r3,有不少bug,踩了不少坑)

具體配置:

一、在app的build.gradle依賴添加以下:

testCompile 'junit:junit:4.12'
testCompile ’org.robolectric:robolectric:3.0’

二、在android studio左下角的Build Variants->TestArtifact,選擇爲Unit Tests;

如圖

三、編譯;

編譯經過以後就已經集成了Robolectic單元測試框架了。

個人第一個單元測試

先描述如下踩過的坑:

1.使用3.0-r3版本,緣由是google搜到的例子是3.0-r3,傻傻的掉進了坑裏;
找不到合併後的mainifest;ContextWraper爲空,webview初始化異常,沒法加載so庫

2.使用了3.0版本後,依然沒法加載so庫,現象是啓動application的時候若是調用so庫會出現閃退,目前robolectic還不支持這個東西,做者在github的issue已經說明;

3.這一點致使沒法robolectic沒法直接集成到咱們的主工程;爲了避免影響正常項目開發,咱們建了一個AppTest的空項目,集成了Robolectic框架;將全部用例分類寫在裏邊,各個模塊要測試的時候,把依賴寫入build.gradle便可;

第一單元測試

建立測試類
日曆分析頁面測試demo
參數解釋:

@RunWith(RobolectricGradleTestRunner.class);聲明使用哪一個Runner,使用GradleTestRunner會自動幫咱們加載所須要的插件,通常咱們配這個就能夠;

@Config(constants = BuildConfig.class,sdk=18);配置測試項目的BuildConfig和sdk版本,BuildConfig是編譯器自動生成;@Config還能夠配置不少其餘的熟悉,好比Mainfest,Applciation,assert資源等等,具體瞭解可參見
http://robolectric.org/configuring/

extend TestCase:這個必須繼承的類;

@Before是前置條件,也就是在執行@Test以前會執行的方法,這個很好理解,@After同理;

@Test具體的單元測試方法;

例子解釋:

@Before

@Before
public void setUp() {
     //獲取當前運行環境的Context;
    Context context =  RuntimeEnvironment.application.getApplicationContext();
    //初始化BeanManager
    BeanManager.getUtilSaver().setContext(context);
    //初始化日曆模塊
    CalendarController.getInstance().init(context, new OnCalendarListener(){});
}

每次執行test的時候,Robolectic執行順序是:
模擬啓動執行你的application,
執行@Before
執行@Test,
執行@After
因此若是沒有沒有執行初始化邏輯,@test頗有可能會失敗;
或者appliation裏調用了so或者初始化了webview,也會失敗;
通常在@Before咱們作的是初始化的工做和構造一些模擬數據的操做;

@Test

@Test
public void doTestMainUI(){
     //啓動AnalysisMainActivity,並獲取activity對象
    Activity activity = Robolectric.setupActivity(AnalysisMainActivity.class);
    //獲取裏邊的控件
    RelativeLayout linearLayout = (RelativeLayout)activity.findViewById(R.id.calendarNodataLayout);
    int visible = linearLayout.getVisibility();
    //判斷是否可見
    assertEquals(visible, View.VISIBLE);
}

@Test
public void doTestCurrentIdentify(){
      //獲取當前身份,能夠在setup設置身份
    int value = CalendarController.getInstance().getIdentifyManager().getIdentifyModelValue();
    //驗證身份
    assertEquals(value, IdentifyModel. NORMAL);
}

@Test
public void testDomain(){
            List<HttpDnsModel> list = mHttpDnsCacheManager.getHttpDnsModels();
            assertEquals(list.size(), 2);
            //驗證解析格式
            String domain = mHttpDnsCacheManager.getDomian("http://api.myms.meiyou.com/configs");
            assertEquals(domain,"api.myms.meiyou.com");
            //驗證解析格式
            domain = mHttpDnsCacheManager.getDomian("https://api.myms.meiyou.com/configs");
            assertEquals(domain, "api.myms.meiyou.com");
            //驗證命中緩存
            HttpDnsModel model =  mHttpDnsCacheManager.getFromCache("api.myms.meiyou.com/configs");
            assertEquals(model!=null,true);
            assertEquals(model.getIp(), "211.151.209.71");
            //驗證替換
            String result = mHttpDnsCacheManager.replaceDomianToIp("http://api.myms.meiyou.com/configs", domain, model.getIp());
            assertEquals("http://211.151.209.71/configs",result);

    }
          
@Test
public void testGetPhoto(){
                //記錄器
            final Transcript transcript = new Transcript();
            //建立一個activity
            Activity activity = new Activity() {
                    @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {
                            //記錄收到的結果
                            transcript.add("onActivityResult called with requestCode " + requestCode + ", resultCode " + resultCode + ", intent data " + data.getData());
                    }
            };
            //啓動去相冊選擇相冊
            activity.startActivity(new Intent().setType("image/*"));
               //獲取影子類,模擬設置ActivityResult結果
            Shadows.shadowOf(activity).receiveResult(new Intent().setType("image/*"), Activity.RESULT_OK,
                    new Intent().setData(Uri.parse("content:foo")));
            //驗證收到的結果       
            transcript.assertEventsSoFar("onActivityResult called with requestCode -1, resultCode -1, intent data content:foo");
    }

強大的影子類:

影子類的詳細瞭解
影子類是對安卓原生api類的一種拓展,通俗的解釋是增長了一些用於測試的很方便的方法;3.0有不少影子類,獲取方法是:Shadows.shadowOf();
上邊的 Shadows.shadowOf(activity).receiveResult的方法;
影子類還能夠自定義,遇到比較複雜的功能或者須要的功能可能會頗有用;

單元測試的例子

大量的測試例子都在github的源碼裏邊,能夠詳細參照;
Robolectric Github 源碼地址


Done

----------

QQ:452825089


mail:452825089@qq.com


wechat:ice3897315


blog:http://iceAnson.github.io

相關文章
相關標籤/搜索