Android單元測試
其實很多開發者都知道單元測量,也能寫一些簡單的單元測試,但是就我工作以來,很少,基本沒有看到專案中有編寫單元測試的。因為編寫額外的程式碼,麻煩,加上不熟悉,就更加不想寫了。我以前也是這種想法,但是最近的接觸,然後覺得,做單元測試還是很有必要的。
舉例
-
網路請求
比如測試一個功能,而這個功能會進行網路請求,當出現問題時,我們得拿到網路請求返回的資料,這樣才知道是後端問題,還是前端邏輯問題。
而進入這個功能需要進行好幾步操作,如果需要更改什麼配置,還需要重新安裝apk,想想過程都複雜,而且重新安裝apk可以一個耗時的過程。
這時候,我們就可以用單元測試,可以在不重新安裝apk,不用去幾番操作就可以拿到網路請求的結果。下面會實戰舉例。
-
api測試
這種情況,用單元測試最快捷的,只需要在“test”目錄下編寫程式碼,在本機執行即可,比起安裝apk,然後去點點點方便很多。
缺點
缺點當然就是要編寫額外的測試程式碼,如果業務邏輯有改動,測試程式碼也得相應改動,存在後期維護,還有一點點的學習成本。不過總得來說,還是利大於弊的。
單元測試
單元測試可以直接在業務程式碼的module下編寫程式碼,也可以專門建一個單元測試module。
加入我們,群。642830685,領取最新軟體測試大廠面試資料和Python自動化、介面、框架搭建學習資料!同行一起交流,技術大牛解惑答疑
業務module下做單元測試
我們在新建module的時候,Android Studio會在資源目錄src下生成“androidTest”和“test”兩個目錄,並且有生成一個簡單的單元測試檔案。單元測試需要的相應依賴也會配置好。你只需要在檔案中編寫測試程式碼即可。
-
androidTest目錄:
這些測試在硬體裝置或模擬器上執行。這些測試有權使用 Instrumentation API,可讓您獲取某些資訊(例如您要測試的應用的 Context,並且可讓您通過測試程式碼來控制受測應用。在編寫整合和功能介面測試來自動執行使用者互動時,或者當您的測試具有模擬物件無法滿足的 Android 依賴項時,可以使用這些測試。
-
test目錄:
這些測試在計算機的本地 Java 虛擬機器 (JVM) 上執行。如果您的測試沒有 Android 框架依賴項,或者您可以模擬 Android 框架依賴項,使用這些測試可以最大限度地縮短執行時間。
單獨的測試module
我們可以像建立lib庫那樣,給需要測試的工程建立一個用於單元測試的module。
- 第一步
建立一個Android Library module
- 第二步
將“apply plugin: 'com.android.library'”改成“apply plugin: 'com.android.test'”
- 第三步
新增測試所用的依賴庫時,和其它module一樣用“implementation”等,而不是“androidTestImplementation”等
- 第四步
指定需要被測試的module,在“AndroidManifest.xml”下:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.pds.testapp" >
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.pds.blog"/>
</manifest>
在build.gradle中指定需要被測試的module: targetProjectPath ':app'。
android:targetPackage 指定需要被測試module的包名,targetProjectPath這是module在工程中的路徑。
測試相關配置
具體欄位的意思,可以參考官網。
android {
compileSdkVersion app.compileSdkVersion
defaultConfig {
minSdkVersion 26
targetSdkVersion app.targetSdkVersion
testApplicationId "com.pds.test.${project.name}"
testHandleProfiling true
testFunctionalTest true
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
// 配置需要被測試的工程,和settings.gradle名字一致
targetProjectPath ':app'
javaCompileOptions {annotationProcessorOptions {includeCompileClasspath = true}}}
testOptions {
reportDir "$rootDir/test_app/test-reports"
resultsDir "$rootDir/test_app/test-results"
// 要僅為本地單元測試指定選項,請配置 testOptions {} 中的 unitTests {} 程式碼塊。
unitTests {
// 如果您的測試依賴於資源 預設情況下,Android Studio 3.4 及更高版本提供編譯版本的資源。
includeAndroidResources = true
all {
jvmArgs '-XX:MaxPermSize=256m'
if (it.name == 'test_app') {systemProperty 'debug', 'true'}
}
}
}
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
}
基礎依賴
dependencies {
implementation 'androidx.test:rules:1.2.0'
implementation 'androidx.test:runner:1.2.0'
implementation 'org.hamcrest:hamcrest-core:1.3'
implementation 'androidx.test.ext:junit:1.1.1'
implementation 'androidx.test.ext:truth:1.2.0'
implementation 'com.google.truth:truth:0.42'
}
實戰
- 啟動Activity
@Test
public void launchMarqueeTextPage() {
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
Intent intent = new Intent(context, GlideTestActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
ActivityTestRule<GlideTestActivity> activityTestRule = new ActivityTestRule<>(GlideTestActivity.class, true, false);
activityTestRule.launchActivity(intent);
// 讓介面不自動退出
try {
CountDownLatch countdown = new CountDownLatch(1);
countdown.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在測試module裡,如果啟動繼承於AppCompatActivity的Activity會報錯,目前沒有找到解決辦法,可以正常啟動繼承於Activity的Activity。
- 網路請求測試
比如在業務module,定義了一套網路請求的api,那麼我們可以直接引用業務module裡面寫好的網路api,來發起網路請求。
@Test
public void getPhoneNumber() {
Disposable observable
= ApiManager
.getUserApi()
.getPhoneNumber("")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.compose(SchedulersCompat.<BaseEntity<DataEntity>>applyIoSchedulers())
.map(new HttpResultFunc<DataEntity>())
.subscribe(new Consumer<DataEntity>() {
@Override
public void accept(DataEntity data) throws Exception {
}
}, new ErrorConsumer() {
@Override
public void accept(Throwable throwable) {
super.accept(throwable);
}
});
}
- 自定義Rule
@Rule
public TipsRule tipsRule = new TipsRule();
public class TipsRule implements TestRule {
@Override
public Statement apply(final Statement base, final Description description) {
return new Statement() {
// evaluate前執行方法相當於@Before
@Override
public void evaluate() throws Throwable {
// 獲取測試方法的名字
String methodName = description.getMethodName();
System.out.println("-------"+ methodName + "------>測試開始!");
// 執行的測試方法
base.evaluate();
// evaluate後執行方法相當於@After
System.out.println("-------"+ methodName + "------>測試結束!");
}
};
}
}
- 自定義Matcher
@Test
public void testAssertThatMatcher(){assertThat("19508460000",new MobilePhoneMatcher());}
public class MobilePhoneMatcher extends BaseMatcher<String> {
/**
* 進行斷言判定,返回true則斷言成功,否則斷言失敗
*/
@Override
public boolean matches(Object item) {
if (null == item) return false;
Pattern pattern = Pattern.compile("(1|861)(3|5|7|8)\\d{9}$*");
Matcher matcher = pattern.matcher((String) item);
return matcher.find();
}
/**
* 給期待斷言成功的物件增加描述
*/
@Override
public void describeTo(Description description) {
description.appendText("預計此字串是手機號碼!");
}
/**
* 給斷言失敗的物件增加描述
*/
@Override
public void describeMismatch(Object item, Description description) {
description.appendText(item.toString() + "不是手機號碼!");
}
}
monkeyrunner
monkeyrunner 工具提供了一個 API,用於編寫可從 Android 程式碼外部控制 Android 裝置或模擬器的程式。使用 monkeyrunner,您可以編寫一個 Python 程式,以便安裝 Android 應用或測試軟體包,執行它,向其傳送按鍵,擷取其介面的螢幕截圖,並將螢幕截圖儲存到工作站中。monkeyrunner 工具主要用於在功能/框架級測試應用和裝置以及執行單元測試套件,但您也可以自由地將其用於其他目的。參考:monkeyrunner
用python編寫測試指令碼,然後用monkeyrunner工具執行。
monkeyrunner可執行檔案存在於sdk/tools/bin目錄下,編寫好的python指令碼用monkeyrunner命令執行,例如: monkeyrunner monkey.py。單獨執行python檔案是不行的,沒法匯入python中用到的Java庫。
希望本文對你有所幫助,點個讚唄!