Android單元測試--基礎
為什麼要使用單元測試
使用單元測試我們可以很容易的發現程式碼的缺陷同時在你重構程式碼的時候可以很方便的幫你驗證重構是否成功。在實際的開發過程中我經常會遇到面對前輩留下的一大坨過時的程式碼,維護起來很吃力,想重構又不敢,因為你剛接手這個業務肯定不熟悉,一些複雜的業務還設計到不同模組之間的交叉呼叫(就算是原作者也不敢拍胸脯保證的),萬一改出問題來,造成生產事故那肯定是吃不了兜著走。如果業務模組配有完善的單元測,那這個問題就很好解決了,放手大膽的去重構,重構完成後執行一下單元測試就ok了,所以寫單元測試是為自己也為別人。
基礎知識
在Android中單元測試有一下幾種型別:
- Local tests
直接在你本地電腦JVM環境下執行,執行過程中不依賴Android framework或者可以用mocking framework解除依賴,執行速度快。
-
Instrumented tests
執行在真機或模擬器,執行過程中需要訪問真機(或模擬器)資料,這些資料不能或者不容易被mocking framework模擬,例如Context等。
配置環境
在編寫我們的測試用例之前我們還需要配置下我們的測試環境,我們需要將 *JUnit4 framework*
新增到我們的工程,在專案的 build.gradle
中新增依賴 testImplementation 'junit:junit:4.12'
。
dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support.constraint:constraint-layout:1.1.3' //JUnit4 framework testImplementation 'junit:junit:4.12' }
在Android Studio 中寫單元測試是很方便的,標準的android工程已經幫我們把目錄結構規劃好,我們要做的只需要在指定的目錄寫我們的測試用例就可以了。如下圖所示
[圖片上傳失敗...(image-c769c2-1539222083949)]
- test
我們單元測試相關的程式碼就應該寫在這裡,當我們呼叫gradle命令 *./gradlew test*
的時候該目錄下的所有的單元測試用例會打包執行並生成測試報告存放在 ../build/reports/
目錄下。 *test*
目錄所對應的路徑為 *project_name/src/test/java/*
預設情況下這個目錄是存在的,如果不存在我們可以手動建立就可以了。
- androidTest
這個目錄對應的是Instrumented tests,也就是說Instrumented tests測試用例相關的程式碼應該寫在這裡。
在編寫測試用例的時候需要注意,我們應該保證測試用例的目錄結構和原始碼的目錄結構保持一致,當然這不是一個強制要求,不過這樣做可以讓你的測試用例結構清晰,方便後期的維護和查閱。
編寫單元測試用例
[圖片上傳失敗...(image-8b8aa6-1539222083949)]
為了方便說明問題這裡我建立了一個Demo模擬一個很常見的功能“登陸”,介面如圖很簡單,兩個輸入框分別用來輸入使用者名稱和密碼,一個登陸按鈕用於執行登陸驗證邏輯,程式碼已上傳 ofollow,noindex">github 。
- 使用者名稱
允許輸入字母,如果輸入其他非法字元會提示“User name is not valid.”
- 密碼
只允許輸入數字,沒有做合法校驗。
- 登陸成功
輸入使用者名稱“admin”,密碼"123456"會跳轉到登陸成功頁面,其他輸入會顯示登陸失敗。
核心業務邏輯程式碼如下:
/** * * @author 王強 on 2018/10/10 [email protected] */ class MainModel { private val charRegex: Regex = Regex("[a-zA-Z]") /** * 合法校驗,只允許輸入小寫字母 * @name 使用者名稱 * @return true 驗證通過 */ private fun checkUserName(name: String): Boolean = name.toCharArray().map { it.toString() }.filter { it.matches(charRegex) }.toList().isEmpty() /** * 模擬登陸, * 使用者名稱:admin * 密碼:123456 */ fun doLogin(name: String, psw: String): LoginResult { return if (checkUserName(name)) { if (name == "admin" && psw == "123456") { LoginResult(true, "Success") } else { LoginResult(false, "Fail") } } else { return LoginResult(false, "User name is not valid.") } } }
MainModel
類裡面實現了我們登陸相關的所有業務邏輯,並對外暴露了 doLogin(...)
方法,下面我們為“登陸”邏輯編寫我們的測試用例程式碼。單元測試的主要工作就是針對我們要測試的業務邏輯去設計測試用例,測試用例設計的好壞直接影響我們單元測試的質量,概括來說就是測試用例要覆蓋所有的業務場景和邊界。這裡我們主要從以下幾個方面去設計我們的測試用例:
- 合法校驗
檢測使用者名稱合法校驗功能的實現是否符合預期,這裡我們需要設計N組合法資料和N組非法資料。
- 登陸結果
檢測登陸功能的實現是否符合預期,這裡我們需要設計N組非法資料和一組合法資料(因為只有admin 123456能登陸,所以一組就行了,正常情況下應該是N組的)。
當然在生產環境下對登陸模組的測試要比這個複雜的多,這裡我們只是為了說明問題,不對其他情況進行過多的考慮。
設計完成的測試用例如下:
/** * * @author 王強 on 2018/10/10 [email protected] */ class MainModelTest { private lateinit var mainModel: MainModel //用於驗證使用者名稱的合法校驗 private lateinit var errorCheckAccounts: List<String> //用於驗證非法登陸賬戶 private lateinit var errorLoginAccounts: List<String> //用於驗證合法登陸賬戶 private lateinit var rightLoginAccounts: List<String> @Before fun setUp() { errorCheckAccounts = listOf( "!@#", "123", "123", "123", "admin2", "admin", "使用者", "123456" ) errorLoginAccounts = listOf( "123", "123", "admin", "1234", "使用者", "123456") rightLoginAccounts = listOf( "admin", "123456" ) mainModel = MainModel() } @Test fun doLoginTest() { for (i in errorCheckAccounts.indices step 2) { mainModel.doLogin(errorCheckAccounts[i], errorCheckAccounts[i + 1]).apply { Assert.assertFalse(this.state) } } for (i in errorLoginAccounts.indices step 2) { mainModel.doLogin(errorLoginAccounts[i], errorLoginAccounts[i + 1]).apply { Assert.assertFalse(this.state) } } for (i in rightLoginAccounts.indices step 2) { mainModel.doLogin(rightLoginAccounts[i], rightLoginAccounts[i + 1]).apply { Assert.assertTrue(this.state) } } } }
這裡我們用到了@Test 和 @Before註解,其實還有@After @BeforeClass @AfterClass @Ignore等只不過這裡我們沒有用到,這裡我們來解釋下這些註解的作用
-
@Test 目標測試方法,我們的測試用例方法都應該使用該註解標註
-
@Before 初始化方法,在呼叫每一個目標測試方法前都要執行一次,在這裡我們可以進行資源初始化等操作
-
@After 釋放資源,在每一個目標測試方法執行完成後呼叫
-
@BeforeClass 針對所有測試,只執行一次,且必須為static void
-
@AfterClass 針對所有測試,只執行一次,且必須為static void
-
@Ignore:忽略的測試方法,這個不常用
它們的執行順序是 @BeforeClass -> @Before -> @Test --> @After --> @AfterClass
執行測試用例
我們可以通過以下幾種途徑執行我們的測試用例:
- GUI(圖形介面)

clip-2.png
如圖我們直接在我們要執行的測試方法上滑鼠右鍵選擇“Run doLoginTest()
”就可以了,執行結果如下:

clip.png
-
Command line(命令列)
直接在命令列中輸入
./gradlew test
就可以了,執行結果在專案工程的../build/reports/
目錄下,結果如圖:

image.png
參考:
[1] https://developer.android.com/training/testing/unit-testing/local-unit-tests