開始單元測試之前還是要先了解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不是junit
和 hamcrest
庫方法,而是Android AssertJ
或 AssertJ
庫,引入庫即可。
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中運行DeckardActivityTest
的Run 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
運行并且測試通過。
參考:
用Robolectric來做Android unit testing
Android單元測試框架Robolectric3.0介紹(一)