Robolectric

開始單元測試之前還是要先了解TDD

中文版:TDD 已死?讓我們再聊聊 TDD

英文版:Introduction to Test Driven Development (TDD)


Robolectric概述

Android的單元測試可以分為兩部分:

Local unit tests:運行于本地JVM
Instrumented test:運行于真機或者模擬器

如果使用Local測試,需要保證測試過程中不會調用Android系統API,否則會拋出RuntimeException異常。
因為Local測試是直接跑在本機JVM的,而之所以我們能使用Android系統API,是因為編譯的時候,我們依賴了一個名為“android.jar”的jar包,但是jar包里所有方法都是直接拋出了一個RuntimeException,是沒有任何任何實現的,這只是Android為了我們能通過編譯提供的一個Stub!

當APP運行在真實的Android系統的時候,由于類加載機制,會加載位于framework的具有真正實現的類。由于我們的Local是直接在PC上運行的,所以調用這些系統API便會出錯。

那么問題來了,我們既要使用Local測試,但測試過程又難免遇到調用系統API那怎么辦?其中一個方法就是mock objects,比如借助Mockito,另外一種方式就是使用Robolectric

Robolectric就是為解決這個問題而生的。它實現一套JVM能運行的Android代碼,然后在unit test運行的時候去截取android相關的代碼調用,然后轉到他們的他們實現的Shadow代碼去執行這個調用的過程。


根據官網教程配置使用即可


本文配置環境:

Android Studio 3.0 Beta 5
Gradle Warpper: gradle-4.1-all
Gradle 插件:gradle:3.0.0-beta5
Android SDK:25
Robolectric:3.4.2

配置過程異常:

1.1 unknown property 'includeAndroidResources:

testCompile "org.robolectric:robolectric:3.4.2"

android {
  testOptions {
    unitTests {
      includeAndroidResources = true
    }
  }
} 

==注意==

  • 若寫成testCompile,則測試代碼放在 test 文件夾中
  • 若寫成androidTest,則測試代碼放在 androidTest 文件夾中

按照官網如上配置后 報錯,Gradle Sync Error:

Error:(22, 0) Could not set unknown property 'includeAndroidResources' for object of type com.android.build.gradle.internal.dsl.TestOptions$UnitTestOptions.
<a href="openFile:G:\android\Samples\Tests\robolectric\app\build.gradle">Open File</a>

未知的屬性includeAndroidResources,gradle版本由3.0.0-alpha8修改至3.0.0-beta5解決問題。

參考自: 'includeAndroidResources' is not working in AS3.0 Beta 2

1.2 Cannot Resolve symbol RobolectricTestRunner

@RunWith(RobolectricTestRunner.class)
public class SandwichTest {
}

嘗試寫一個 Robolectric Test的時候提示:cannot resolve robolectrictestrunner
參考robolectric github例子,將gradle版本修改為3.0.0-beta3,下載失敗時添加maven { url 'https://maven.google.com' }

完整配置,Project build.gradle:

repositories {
        maven {
            url 'https://maven.google.com'
        }
        jcenter()
    }

1.2.1

如果依然存在 Cannot Resolve symbol RobolectricTestRunner,并且測試類在src/androidTest 目錄下,移至src/test目錄下,參考自: cannot resolve symbol RobolectricTestRunner

1.3 java.lang.NullPointerException at sun.nio.fs.WindowsPathParser.parse

Error:Cause: java.lang.NullPointerException 
at sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:98)

Clear Project --> Gradle sync后解決

參考自:gradle-error-upgrading-to-android-studio-3-0-beta-1

1.4 Cannot resolve method 'assertThat(android.content.Intent)

參考官方例子時,遇到Cannot resolve method 'assertThat(android.content.Intent)的問題,該assertThat不是junithamcrest庫方法,而是Android AssertJAssertJ庫,引入庫即可。

testCompile 'org.assertj:assertj-core:1.7.1'

參考自:importing correct AssertThat method for Robolectric Test

1.5 java.lang.AssertionError: Expecting: <"Intent { cmp=*** } (Intent@4e96cb04)"> to be equal to: <"Intent { cmp=*** } (Intent@70ecf57b)"> but was not.

    @Test
    public void clickingLogin_shouldStartLoginActivity() {
        WelcomeActivity activity = Robolectric.setupActivity(WelcomeActivity.class);
        activity.findViewById(R.id.login).performClick();

        Intent expectedIntent = new Intent(activity, LoginActivity.class);
        AbstractObjectAssert<?, Intent> actual = assertThat(shadowOf(activity).getNextStartedActivity());
        actual.isEqualTo(expectedIntent);
    }

運行官網的例子時,test failed:

java.lang.AssertionError: 
Expecting:
 <"Intent { cmp=robolectirc.example.andrew.robolectric/.LoginActivity } (Intent@4e96cb04)">
to be equal to:
 <"Intent { cmp=robolectirc.example.andrew.robolectric/.LoginActivity } (Intent@70ecf57b)">
but was not.

很明顯兩個Intent值相同但是對象引用不同導致的異常。 已經提了Issue 等待回復。

改為使用Junit的assertEquals后測試通過:

    @Test
    public void clickingLogin_shouldStartLoginActivity() {
        WelcomeActivity activity = Robolectric.setupActivity(WelcomeActivity.class);
        activity.findViewById(R.id.login).performClick();

        // 期望意圖
        Intent expectedIntent = new Intent(activity, LoginActivity.class);

        // 獲取跳轉的意圖
        Intent actual = ShadowApplication.getInstance().getNextStartedActivity();

        // 假設一致
        assertEquals(expectedIntent.getComponent(), actual.getComponent());
    }

奇怪的是,robolectric github例子按照README.md,使用命令行gradlew test可以成功測試,但在IDEA中運行DeckardActivityTestRun Test卻報錯:

!!! JUnit version 3.8 or later expected: 

java.lang.RuntimeException: Stub!
    at junit.runner.BaseTestRunner.<init>(BaseTestRunner.java:5)
    at junit.textui.TestRunner.<init>(TestRunner.java:54)

DeckardEspressoTest 運行并且測試通過。


參考:

Android單元測試之Robolectric框架

用Robolectric來做Android unit testing

Android robolectric 入門

Robolectric使用教程

Android單元測試框架Robolectric3.0介紹(一)

importing correct AssertThat method for Robolectric Test

assertj-android

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,264評論 25 708
  • 前言 春節后,事情比較多,沒太多寫作靈感。之前在《App組件化與業務拆分那些事》說過要寫一篇怎么Android怎么...
    鍵盤男閱讀 4,746評論 14 22
  • 一.基本介紹 背景: 目前處于高速迭代開發中的Android項目往往需要除黑盒測試外更加可靠的質量保障,這正是單元...
    anmi7閱讀 2,057評論 0 6
  • 有霾一日 早上起來,霧霾一片。從22樓望去,似乎到了仙境。朋友圈里各種吐槽。想到今天約了保潔打掃,把家里簡單收拾了...
    漫素里閱讀 242評論 0 3
  • 故地重游總會有些感傷。 曾經嬉戲、學習的地方被圍上了欄桿,售票參觀,游人請我幫忙給他們拍照,于是最熟悉的景致里面擠...
    踢車劉閱讀 221評論 0 0