前言
對於任何一個開發人員來說,測試必然是不可缺少的一部分。以前開發都是直接run代碼到模擬器或者實體機上面進行實體測試,雖然說這樣的測試,可以更加直觀的看出問題。但是進行單元測試的話,需要較大的代碼量來寫測試用例,而且實體測試耗時更長,費時費力。所以想要學習如何更高效的進行代碼測試。
介紹
稍微瞭解了一下(看了半天的樣子吧,這篇文章只是爲了暫時記錄一下遇到的坑,而不是個全方位系統的介紹,所以如果有錯或者需要補充的地方請在評論區指出),測試大致分爲三類:
- 小型測試是指單元測試,用於驗證應用的行爲,一次驗證一個類。
- 中型測試是指集成測試,用於驗證模塊內堆棧級別之間的互動或相關模塊之間的互動。
- 大型測試是指端到端測試,用於驗證跨越了應用的多個模塊的用戶操作流程。
本文主要進行了單元測試,使用了Junit4和 Robolectric。主要是跟着資源5的內容進行了代碼的編寫,但是由於資源5的年代久遠,所以有一些內容發生了改變。這裏主要是爲了記錄和資源5所描述的不一樣的地方。
編碼
gradle配置
向app的build.gradle中加入
android {
testOptions {
unitTests {
includeAndroidResources = true
}
}
}
dependencies {
testImplementation 'junit:junit:4.12'
testImplementation 'org.robolectric:robolectric:4.2.1'
testImplementation 'androidx.test:core:1.2.0'
}
編寫普通類
這裏寫了兩個Activity,在MainActivity裏面使用一個TextView,點擊TextView跳轉到SecondActivity
1.MainActivity
package com.example.androidtestlearning
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tv_turn_new_activity.setOnClickListener {
startActivity(Intent(this,SecondActivity::class.java))
}
}
}
2.SecondActivity(就是一個空的什麼都沒寫的activity)
package com.example.androidtestlearning
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
class SecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
}
}
編寫測試類
用來測試是否點擊了MainActivity裏的tv_turn_new_activity能跳轉到SecondActivity
package com.example.androidtestlearning
import android.content.Intent
import androidx.test.core.app.ActivityScenario
import org.junit.Assert.*
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import kotlinx.android.synthetic.main.activity_main.*
import org.robolectric.Shadows
@RunWith(RobolectricTestRunner::class)
class MainActivityTest {
@Test
@Throws(Exception::class)
fun clickText() {
val scenario = ActivityScenario.launch(
MainActivity::class.java
)
scenario.onActivity { activity ->
//點擊tv
activity.tv_turn_new_activity.performClick()
//預期的Intent
val expectedIntent = Intent(activity, SecondActivity::class.java)
//用來獲取MainActivity對應的ShadowActivity的instance
val shadowActivity = Shadows.shadowOf(activity)
//實際獲得的Intent
val actualIntent = shadowActivity.nextStartedActivity
//判斷兩個Intent是否一樣
assertEquals(expectedIntent, actualIntent)
}
}
}
注意點
Point one
舊的資料使用Robolectric.setupActivity(MainActivity.class); 來獲取activity,然後發現setupActivity方法已經被棄用了,文檔上寫的
-
Use {@link androidx.test.core.app.ActivityScenario} instead, which works with instrumentation tests too.
於是就直接跳轉到ActivityScenario類裏去看,是這樣推薦使用的
* <pre>{@code
* Before:
* MyActivity activity = Robolectric.setupActivity(MyActivity.class);
* assertThat(activity.getSomething()).isEqualTo("something");
*
* After:
* try(ActivityScenario<MyActivity> scenario = ActivityScenario.launch(MyActivity.class)) {
* scenario.onActivity(activity -> {
* assertThat(activity.getSomething()).isEqualTo("something");
* });
* }
那麼我就直接把這一段代碼ctrl+c ctrl+v了上去,然後發現找不到ActivityScenario。我就google了一下,android developer是這樣配置的gradle
dependencies {
// Core library
androidTestImplementation 'androidx.test:core:1.0.0'
// AndroidJUnitRunner and JUnit Rules
androidTestImplementation 'androidx.test:runner:1.1.0'
androidTestImplementation 'androidx.test:rules:1.1.0'
// Assertions
androidTestImplementation 'androidx.test.ext:junit:1.0.0'
androidTestImplementation 'androidx.test.ext:truth:1.0.0'
androidTestImplementation 'com.google.truth:truth:0.42'
// Espresso dependencies
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-accessibility:3.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-web:3.1.0'
androidTestImplementation 'androidx.test.espresso.idling:idling-concurrent:3.1.0'
// The following Espresso dependency can be either "implementation"
// or "androidTestImplementation", depending on whether you want the
// dependency to appear on your APK's compile classpath or the test APK
// classpath.
androidTestImplementation 'androidx.test.espresso:espresso-idling-resource:3.1.0'
}
然後發現這樣添加之後還是找不到ActivityScenario,我的測試類是寫在test文件夾而不是androidtest裏面的。而androidTestImplementation是爲androidTest裏面的文件所依賴的,所以改爲testImplementation,即可找到此類。
Point two
我之前根據資料沒有添加這幾行代碼
android {
testOptions {
unitTests {
includeAndroidResources = true
}
}
}
導致一直報錯
No such manifest file: .\AndroidManifest.xml
Point Three
雖然我的程序運行沒有錯誤了,但是它的expectedIntent和actualIntent判斷出來不一樣emmmm我也不知道爲什麼(有大佬知道的話,能告訴我是爲什麼嗎)
java.lang.AssertionError: expected: android.content.Intent<Intent { cmp=com.example.androidtestlearning/.SecondActivity }> but was: android.content.Intent<Intent { cmp=com.example.androidtestlearning/.SecondActivity }>
Expected :android.content.Intent<Intent { cmp=com.example.androidtestlearning/.SecondActivity }>
Actual :android.content.Intent<Intent { cmp=com.example.androidtestlearning/.SecondActivity }>