開(kāi)始單元測(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不是junit 和 hamcrest庫(kù)方法,而是Android AssertJ 或 AssertJ庫(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)行DeckardActivityTest的Run 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ò)。
參考:
用Robolectric來(lái)做Android unit testing
Android單元測(cè)試框架Robolectric3.0介紹(一)