Android測試初體驗

前言

對於任何一個開發人員來說,測試必然是不可缺少的一部分。以前開發都是直接run代碼到模擬器或者實體機上面進行實體測試,雖然說這樣的測試,可以更加直觀的看出問題。但是進行單元測試的話,需要較大的代碼量來寫測試用例,而且實體測試耗時更長,費時費力。所以想要學習如何更高效的進行代碼測試。

介紹

稍微瞭解了一下(看了半天的樣子吧,這篇文章只是爲了暫時記錄一下遇到的坑,而不是個全方位系統的介紹,所以如果有錯或者需要補充的地方請在評論區指出),測試大致分爲三類:

  1. 小型測試是指單元測試,用於驗證應用的行爲,一次驗證一個類。
  2. 中型測試是指集成測試,用於驗證模塊內堆棧級別之間的互動或相關模塊之間的互動。
  3. 大型測試是指端到端測試,用於驗證跨越了應用的多個模塊的用戶操作流程。
    本文主要進行了單元測試,使用了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 }>

資料

  1. Android Developer Test
  2. AndroidX Test Document
  3. Github Android testing-samples
  4. Robolectric
  5. 用Robolectric來做Android unit testing
  6. Codelab
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章