Android Unit Test學習
@(單元測試)[Android|Markdown]
- 單元測試 這幾天接觸了下android的單元測試,寫了一些小DEMO,自己總結下來,覺得android的單元測試可以有以下優缺點:
- 優點
- 減少bug率
- 明確方法的輸入輸出
- 自測程序,使自己對代碼更有自信
- 缺點
- 耗時
- 有些時候測試環境于真實環境有差異,導致做了很多適配的工作。
- 不適合一個硬性指標。如代碼覆蓋率等。
[TOC]
Android Test 框架
流行的android測試框架很多,可以參照這個網頁:Android 測試工具。我主要調研過以下幾種:
- 原生框架 :ActivityInstrumentationTestCase2
- Android Test Lib (谷歌官方提供)Espresso等
- Appium (可以編寫測試腳本)
- Monkey 和Monkey Runner(隨機測試,谷歌官方提供)
- Robolectric (第三方測試框架,不需要真機或者模擬器)
- Robotium (封裝官方框架)
上述幾種也可以組合使用,基本上想對于一個項目做完整的單元測試,即包括UI,業務,數據,協議等,一般都要自己再<font color=red>封裝一些適合項目的框架和工具類</font>。
原生框架
先上圖~
- AndroidTestCase類:
提供系統對象(如Context)的方法。使用Context,你可以瀏覽資源,文件,數據庫等等。不能測試涉及UI的方法。 - InstrumentationTestCase類:
繼承TestCase類,并可以使用Instrumentation框架,用于測試Activity。使用Instrumentation,Android可以向程序發送事件進行自動UI測試,并可以精確控制Activity的啟動,監測Activity生命周期的狀態
ActivityUnitTestCase設計用于單元測試,它在一個孤立的系統環境中測試Activity。換句話說,當你使用這個測試類時,Activity不能與其它Activity交互。每個測試用例都需要使用startActivity啟動測試activity。
單元測試在Studio中的應用
Android Studio對單元測試完全支持,已經幫開發者搭建好了測試環境。測試開發者只需要關心測試用例編寫,不用關心測試環境搭建,給測試開發帶來極大便利。在Android Studio上進行JUnit單元測試步驟:
1 編寫測試用例。
編寫測試用例需要繼承TestCase的子類。如果測試對象為普通java類,自定義測試用例需要繼承AndroidTestCase;如果測試對象為Activity,可以繼承ActivityUnitTestCase,也可以繼承ActivityInstrumentationTestCase2。
繼承ActivityUnitTestCase和ActivityInstrumentationTestCase2不同之處在于,前者需要自己使用startActivity啟動被測試的activity,并且該activity在一個獨立的環境中運行。繼承自ActivityInstrumentationTestCase2的測試用例不需要開發者自己調用被測試的activity,測試框架會自動啟動activity。
自定義測試用例類需要有構造函數,并且將測試目標類傳遞給父類。如:
public LoadActivityTest() {
super(LoadActivity.class);//LoadActivity為測試目標類
}
一般來說自定義測試用例需要重寫setUp和tearDown,如果測試用例中有多個測試方法,setUp和tearDown會在每個測試方法前后被調用。setUp用來初始化測試環境,tearDown在每個測試方法結束后還原測試環境。
使用斷言assertXXX來判斷測試結果。如果斷言失敗,則測試用例測試不通過,所有斷言都成功,則測試用例測試通過。 常見的斷言有:
assertNotNull:斷言測試對象不為NULL;
assertNull:斷言測試對象為NULL;
assertEquals:斷言測試的兩個對象值相等;
assertTrue:斷言Boolean值為true;
assertFalse:斷言Boolean值為false。
所有測試方法必須以test開頭,如testXXX。測試框架會自動調用所有以test為開頭的測試方法。各個測試方法沒有運行先后順序。因此,各個測試方法不應該有前后依賴順序。
UI測試。
1)UI測試應該在主線程中操作。
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
mPaymentButton.callOnClick();
}
});
2)模擬系統發送按鍵事件
this.sendKeys(KeyEvent.KEYCODE_HOME);
3)點擊按鍵事件
mPaymentButton.callOnClick();
3 線程同步問題
1)測試線程和異步任務線程同步
在單元測試中,有很多異步任務。我們需要異步任務返回結果再測試,這就需要用到線程同步相關知識。比如,我們測試的單元需要發送一個網絡請求,需要測試返回結果是否正確。這是一個典型的異步任務,這就需要用本節的方法去做單元測試。
CountDownLatch,一個同步輔助類,用信號量機制實現線程同步。主要方法有:
public CountDownLatch(int count);
構造函數,count指定了計數器的次數??梢岳斫鉃榧拥逆i數量。
public void await() throws InterruptedException
調用此方法會一直阻塞當前線程,直到計數器的值為0。
public void countDown();
每次調用計數減少1,當計數為0時,線程被喚醒。
2)測試線程和UI主線程同步
每個測試方法都運行在子線程中,在某些情況,測試子線程需要等待主線程(UI線程)空閑才繼續運行測試子線程。這就需要同步測試子線程和UI主線程。Instrumentation為我們提供了waitForIdleSync方法來滿足這類同步需求。使用方法如下:
public void testExample(){
......
getInstrumentation().waitForIdleSync();//等待主線程返回
.....
}
需要注意:waitForIdleSync只能在測試子線程中被調用。測試方法默認運行在子線程中,但也可以運行在UI線程中,只需要在測試方法前加上標注@UiThreadTest,這樣整個測試方法都在UI線程中運行。
測試Fragment
由于JUnit沒有提供對fragment測試的框架,所以只能在使用activity測試框架。通常的做法是使用ActivityInstrumentationTestCase2測試框架,先加載一個activity,然后在setup中啟動被測試的fragment。這樣在每個測試方法執行前都會啟動被測試的fragment。
使用ActivityInstrumentationTestCase2框架對fragment進行單元測試的不足之處在于,測試任何一個單元(函數),都需要先啟動該fragment,然后才能進行單元測試。
五 單元測試基礎要點
1 方法名稱必須以test開頭。
2不能依賴測試方法順序,每個測試方法都是在子線程中運行。
3 setUp方法和tearDown方法都是TestCase類的方法
1)setUp方法是在執行每個測試方法之前執行的
2)tearDown方法是在執行每個測試方法之后執行的
4 涉及UI操作的應該在主線程中執行。
5 waitForIdleSync和sendKeys不允許在UI線程里運行,只能運行在測試子線程中。
原理
需要說明的是,在Android系統中,測試程序也是應用程序,我們可以將其看成一個沒有UI的應用。
其實現過程大致如下:如圖,InstrumentationTestRunner通過調用Instrumentation殺除應用程序的進程,再用Instrumentation重啟該應用。這時,測試應用和被測應用就運行在同一進程下。測試應用怎么知道該測試哪個應用呢?嗯,這是通過在測試工程的mainfest文件中添加元素來實現的。當測試應用和被測應用運行在同一個進程里,它們之間就可以通過Instrumentation來進行消息交互,從而達到測試效果。當Instrumentation與某個程序交互時,其大致采用如下步驟:(資料來源:
http://blog.csdn.net/fireworkburn/article/details/20144153)。
首先,啟動時,初始化測試APK的配置文件AndroidManifest.xml文件中。該配置文件中標明了所使用的測試運行類、被測目標應用、包名等。然后,啟動被測應用的Activity。同時,將測試ActivityThread做為一個引用進行初始化。此時,如果找不到目標應用則會報錯。其次,執行測試腳本。測試時,測試工程中任何對目標應用進行的操作,都會用異步的方式,將消息體放在目標程序的MessageQueue中。這樣,目標程序在查看到自己的MessageQueue中有內容時就會執行。
Robolectric
根據我的研究以及我個人的需求,我覺得<font color=red>Robolectric</font>更適合開發使用,因為其速度快,而且不需要在真機上看效果,這樣可以把代碼邏輯都寫好,驗證完畢在真機上一次運行。大大提高代碼質量和效率。個人見解,不喜勿噴。
Robolectric簡介
robolectric官網介紹,Robolectric是為android上的TDD開發而產生的一款第三方測試框架,按照官網說的:
Robolectric is a unit test framework that de-fangs the Android SDK jar so you can test-drive the development of your Android app. Tests run inside the JVM on your workstation in seconds. With Robolectric you can write tests like this:
@RunWith(RobolectricTestRunner.class)
public class MyActivityTest {
@Test
public void clickingButton_shouldChangeResultsViewText() throws Exception {
MyActivity activity = Robolectric.setupActivity(MyActivity.class);
Button button = (Button) activity.findViewById(R.id.button);
TextView results = (TextView) activity.findViewById(R.id.results);
button.performClick();
assertThat(results.getText().toString()).isEqualTo("Robolectric Rocks!");
}
}
該框架測試起來是很方便的,但是同時由于不啟動app,可能遇到一些問題,總體上使用起來還是方便的。
eclipse配置
新建 Test Project
這里我新建了一個 Maven Project
File> New > Other > Maven Project ,跟 Appium 里面使用 Java 寫腳本,建立 Maven 項目是相同的。
我建立的測試項目 roboletrictest,pom.xml 的內容我這里貼下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>roboletrictest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>roboletrictest</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.robolectric</groupId>
<artifactId>robolectric</artifactId>
<version>3.0</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
配置 Test Project
配置 roboletrictest 的 Build Path
右鍵項目(roboletrictest) > Build Path > Configure Build Path
在 Project 選項中 add 被測項目(roboletric)
在 Libraries 中 Add External JARs,添加對應的 android.jar,android.jar 在 sdk 中(需要下載)
完成 Test Case
在 src/test/java 目錄下 new 一個 package,對應被測項目(com.example.roboletric.test)
在包里面 new 一個 JUnit Test Case,使用 Junit 4,Class Name 為 MainActivityTest
Run Test Case
右鍵 MainActivityTest > Run As > JUnit Test
自己打包依賴
因為是maven項目,其實你也可以自己把roboletric的項目打包,使用maven-assembly-plugin把項目打成jar包,直接導入使用。以前的官網提供了依賴包的,但是現在找不著了。
Android studio配置
repositories {
maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
mavenLocal()
mavenCentral()
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:22.0.0'
testCompile "junit:junit:4.10"
testCompile "org.assertj:assertj-core:1.7.0"
testCompile "org.robolectric:robolectric:${robolectricVersion}"
}
Shadow類的使用
Android Test Kit
Android Test Kit 是一組 Google 開源測試工具,用于 Android 平臺,包含 Espresso API 可用于編寫簡潔可靠的 Android UI 測試。它也可以跟其他的框架混合使用。
github Demo的地址:
https://github.com/googlesamples/android-testing
Monkey 和MonkeyRunner
Appium
框架基本原理
下次再看,我會開一篇代理設計模式的專欄
常見的測試方法
測試UI
一般的測試框架都提供了對UI的測試,具體可以參考谷歌源碼對一些原生app做的測試。
測試業務
建議把業務方法的入口都放在manager里,這樣測試的時候直接一調用就OK了。但是記住要測試一些邊界情況,例如空指針之類的。
測試http請求
因為http請求是異步的,這塊可能要對請求的框架做一些封裝,以便支持mock數據。