layout: post
title: Roboletric探索之路,從抗拒到依賴
description: Roboletric Android Unit Testing
category: blog
---html
一、懶,人類最大的天敵;java
二、不是不知道什麼是單元測試,只是需求太多了,哪有時間~;android
三、須要學習單元測試的語言或者框架,不熟悉,因此從沒嘗試過;git
四、沒見到單元測試的好處,一想到要花時間就望而卻步;github
五、至少只是我我的以前的感覺,我相信有不少的程序猿同胞們都跟我有相似的感覺;web
大勢所趨,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上運行你的測試代碼,能在幾秒鐘以內快速驗證,經過體驗以後,它確實很是高效,編寫測試代碼反而加速了開發效率。
具體的原理描述可參見:
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便可;
建立測試類
參數解釋:
@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