Robolectric

開(kāi)始單元測(cè)試之前還是要先了解TDD

中文版:TDD 已死?讓我們?cè)倭牧?TDD

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


Robolectric概述

Android的單元測(cè)試可以分為兩部分:

Local unit tests:運(yùn)行于本地JVM
Instrumented test:運(yùn)行于真機(jī)或者模擬器

如果使用Local測(cè)試,需要保證測(cè)試過(guò)程中不會(huì)調(diào)用Android系統(tǒng)API,否則會(huì)拋出RuntimeException異常。
因?yàn)長(zhǎng)ocal測(cè)試是直接跑在本機(jī)JVM的,而之所以我們能使用Android系統(tǒng)API,是因?yàn)榫幾g的時(shí)候,我們依賴了一個(gè)名為“android.jar”的jar包,但是jar包里所有方法都是直接拋出了一個(gè)RuntimeException,是沒(méi)有任何任何實(shí)現(xiàn)的,這只是Android為了我們能通過(guò)編譯提供的一個(gè)Stub!

當(dāng)APP運(yùn)行在真實(shí)的Android系統(tǒng)的時(shí)候,由于類加載機(jī)制,會(huì)加載位于framework的具有真正實(shí)現(xiàn)的類。由于我們的Local是直接在PC上運(yùn)行的,所以調(diào)用這些系統(tǒng)API便會(huì)出錯(cuò)。

那么問(wèn)題來(lái)了,我們既要使用Local測(cè)試,但測(cè)試過(guò)程又難免遇到調(diào)用系統(tǒng)API那怎么辦?其中一個(gè)方法就是mock objects,比如借助Mockito,另外一種方式就是使用Robolectric

Robolectric就是為解決這個(gè)問(wèn)題而生的。它實(shí)現(xiàn)一套JVM能運(yùn)行的Android代碼,然后在unit test運(yùn)行的時(shí)候去截取android相關(guān)的代碼調(diào)用,然后轉(zhuǎn)到他們的他們實(shí)現(xiàn)的Shadow代碼去執(zhí)行這個(gè)調(diào)用的過(guò)程。


根據(jù)官網(wǎng)教程配置使用即可


本文配置環(huán)境:

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

配置過(guò)程異常:

1.1 unknown property 'includeAndroidResources:

testCompile "org.robolectric:robolectric:3.4.2"

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

==注意==

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

按照官網(wǎng)如上配置后 報(bào)錯(cuò),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解決問(wèn)題。

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

1.2 Cannot Resolve symbol RobolectricTestRunner

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

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

完整配置,Project build.gradle:

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

1.2.1

如果依然存在 Cannot Resolve symbol RobolectricTestRunner,并且測(cè)試類在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)

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

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);
    }

運(yùn)行官網(wǎng)的例子時(shí),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.

很明顯兩個(gè)Intent值相同但是對(duì)象引用不同導(dǎo)致的異常。 已經(jīng)提了Issue 等待回復(fù)。

改為使用Junit的assertEquals后測(cè)試通過(guò):

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

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

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

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

奇怪的是,robolectric github例子按照README.md,使用命令行gradlew test可以成功測(cè)試,但在IDEA中運(yùn)行DeckardActivityTestRun Test卻報(bào)錯(cuò):

!!! 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 運(yùn)行并且測(cè)試通過(guò)。


參考:

Android單元測(cè)試之Robolectric框架

用Robolectric來(lái)做Android unit testing

Android robolectric 入門

Robolectric使用教程

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

importing correct AssertThat method for Robolectric Test

assertj-android

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,828評(píng)論 25 709
  • 前言 春節(jié)后,事情比較多,沒(méi)太多寫作靈感。之前在《App組件化與業(yè)務(wù)拆分那些事》說(shuō)過(guò)要寫一篇怎么Android怎么...
    鍵盤男閱讀 4,867評(píng)論 14 22
  • 一.基本介紹 背景: 目前處于高速迭代開(kāi)發(fā)中的Android項(xiàng)目往往需要除黑盒測(cè)試外更加可靠的質(zhì)量保障,這正是單元...
    anmi7閱讀 2,159評(píng)論 0 6
  • 有霾一日 早上起來(lái),霧霾一片。從22樓望去,似乎到了仙境。朋友圈里各種吐槽。想到今天約了保潔打掃,把家里簡(jiǎn)單收拾了...
    漫素里閱讀 264評(píng)論 0 3
  • 故地重游總會(huì)有些感傷。 曾經(jīng)嬉戲、學(xué)習(xí)的地方被圍上了欄桿,售票參觀,游人請(qǐng)我?guī)兔o他們拍照,于是最熟悉的景致里面擠...
    踢車劉閱讀 255評(píng)論 0 0

友情鏈接更多精彩內(nèi)容