詳解Android單元測試最佳實踐

目的

充分的單元測試就是提高代碼質量最有效的手段之一,而單元測試嚴重依賴代碼的可測試性,本文主要通過一個簡單的DEMO演示如何對Android原生應用進行單元測試,同時示例代碼采用MVP模式以提高代碼的可讀性和可測試性

簡介

在Android原生應用開發中,存在兩種單元測試:本地JVM測試和Instrumentation測試。本文僅介紹本地JVM測試

本地jvm的單元測試

這種方式運行速度快,對運行環境沒有特殊要求,可以很方便的做自動化測試,是單元測試首選的方法

Instrumentation測試

Instrumentation測試需要運行在Android環境下,可以是模擬器或者手機等真實設備。這種方式運行速度慢,且嚴重依賴Android運行環境,更適合用來做集成測試

準備

我準備了一個簡單的APP,模擬一個耗時的網絡請求獲得一段數據并顯示在界面上,針對這個APP編寫單元測試用例并進行本地單元測試。

App運行效果

依賴庫

依賴庫作用
JUnit-4.12基礎得單元測試框架
Robolectric-3.8Android SDK測試框架
PowerMock-1.6.6模擬被測對象依賴的靜態方法
Mockito-1.10.19模擬被測對象依賴的對象

配置build.gradle

增加編譯選項,在測試中包含資源文件

1

2

3

4

5

testOptions {

?unitTests {

??includeAndroidResources true

?}

}

添加測試依賴庫

1

2

3

4

5

6

7

8

testImplementation 'junit:junit:4.12'

testImplementation 'org.robolectric:robolectric:3.8'

testImplementation 'org.robolectric:shadows-supportv4:3.8'

testImplementation 'org.powermock:powermock-module-junit4:1.6.6'

testImplementation 'org.powermock:powermock-module-junit4-rule:1.6.6'

testImplementation 'org.powermock:powermock-api-mockito:1.6.6'

testImplementation 'org.powermock:powermock-classloading-xstream:1.6.6'

testImplementation 'org.mockito:mockito-all:1.10.19'

測試Activity

測試Activity主要是測試它各個生命周期的狀態變化、對外界輸入的響應是否符合預期,Activity測試完全依賴Android SDK,需要用Robolectric。

Robolectric是一個開源的單元測試框架,能夠完全模擬Android SDK并在JVM中運行。

UI依賴于Persenter,在Activity中通過靜態工廠方法創建依賴的Presenter實例,需要使用PowerMock來模擬創建Presenter過程,完成Presenter模擬對象的注入

配置

  • 通過@RunWith指定使用RobolectricTestRunner
  • 通過@Config配置Robolectric的運行環境
  • 通過@PrepareForTest配置PowerMock需要模擬的靜態類型

1

2

3

4

@RunWith(RobolectricTestRunner.class)

@Config(sdk = 21, constants = BuildConfig.class)

@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})

@PrepareForTest({PresenterFactory.class})

1

2

3

4

5

@Before

public void setUp() {

?appContext = RuntimeEnvironment.application.getApplicationContext();

?PowerMockito.mockStatic(PresenterFactory.class);

}

onCreate用例

通過Robolectric的ActivityController來構建并管理activity的生命周期,運行至onCreate階段,然后驗證這個階段text1是否正確初始化

1

2

3

4

5

6

@Test

public void onCreate_text1() {

?MainActivity activity = Robolectric.buildActivity(MainActivity.class).create().get();

?String expect = appContext.getString(R.string.hell_world);

?assertEquals(expect, ((TextView)activity.findViewById(R.id.lbl_text1)).getText());

}

Click Button1用例

Activity完全顯示以后,驗證button1的click操作是否顯示toast消息

1

2

3

4

5

6

7

@Test

public void btn1_click() {

?MainActivity activity = Robolectric.setupActivity(MainActivity.class);

?activity.findViewById(R.id.btn_1).performClick();

?String expect = appContext.getString(R.string.hell_world);

?assertEquals(expect, ShadowToast.getTextOfLatestToast());

}

Click Button2用例

Activity完全顯示以后,驗證button2的click操作是否調用了presenter的fetch方法

1

2

3

4

5

6

7

8

9

10

11

12

13

@Test

public void btn2_click() {

?MainContract.Presenter presenter = Mockito.mock(MainContract.Presenter.class);

?PowerMockito.when(PresenterFactory.create(Mockito.any(MainContract.View.class), Mockito.any(AppExecutors.class)))

???.thenReturn(presenter);

?MainActivity activity = Robolectric.setupActivity(MainActivity.class);

?activity.findViewById(R.id.btn_2).performClick();

?Mockito.verify(presenter, Mockito.times(1))

???.fetch();

}

測試Presenter

Presenter的測試一般可以不用依賴Android SDK了,Presenter依賴于底層的領域服務,也依賴上層View,demo中對領域服務的依賴沒有通過構造函數的方式注入,而是通過靜態工廠方法構建,還是需要用到PowerMock

配置

  1. 通過@RunWith指定使用PowerMockRunner
  2. 通過@PrepareForTest配置PowerMock需要模擬的靜態類型

1

2

@RunWith(PowerMockRunner.class)

@PrepareForTest({ServiceFactory.class})

1

2

3

4

@Before

public void setUp() {

?PowerMockito.mockStatic(ServiceFactory.class);

}

成功路徑用例

驗證View的方法是否成功調用且調用參數是否一致

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

@Test

public void fetch_success() {

?String expected = "hello world";

?SlowService service = Mockito.mock(SlowService.class);

?Mockito.when(service.fetch()).thenReturn(expected);

?PowerMockito.when(ServiceFactory.create())

???.thenReturn(service);

?MainContract.View view = Mockito.mock(MainContract.View.class);

?MainPresenter presenter = new MainPresenter(view, executors);

?presenter.fetch();

?Mockito.verify(service, Mockito.times(1)).fetch();

?Mockito.verify(view, Mockito.times(1)).onFetchStarted();

?Mockito.verify(view, Mockito.times(1)).onFetchCompleted();

?Mockito.verify(view, Mockito.times(0)).onFetchFailed(Mockito.anyObject());

?ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);

?Mockito.verify(view, Mockito.times(1)).onFetchSuccess(captor.capture());

?assertEquals(expected, captor.getValue());

}

失敗路徑用例

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

@Test

public void fetch_failed() {

?RuntimeException exception = new RuntimeException("fetch failed");

?SlowService service = Mockito.mock(SlowService.class);

?Mockito.when(service.fetch()).thenThrow(exception);

?PowerMockito.when(ServiceFactory.create())

???.thenReturn(service);

?MainContract.View view = Mockito.mock(MainContract.View.class);

?MainPresenter presenter = new MainPresenter(view, executors);

?presenter.fetch();

?Mockito.verify(service, Mockito.times(1)).fetch();

?Mockito.verify(view, Mockito.times(1)).onFetchStarted();

?Mockito.verify(view, Mockito.times(1)).onFetchCompleted();

?ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class);

?Mockito.verify(view, Mockito.times(1)).onFetchFailed(captor.capture());

?assertEquals(exception, captor.getValue());

?Mockito.verify(view, Mockito.times(0)).onFetchSuccess(Mockito.anyString());

}

測試Service

Service不會對上層有依賴,可以直接使用JUnit測試

1

2

3

4

5

6

7

8

9

10

public class SlowServiceImplTest {

?@Test

?public void fetch_data() {

??SlowServiceImpl impl = new SlowServiceImpl();

??String data = impl.fetch();

??assertEquals("from slow service", data);

?}

}

自動化測試

自動化測試一般是在持續集成環境中使用命令來執行單元測試

1

gradlew :app:testDebugUnitTest

總結

寫完這個demo,總覺得給Android APP做單元測試還是非常簡單的,作為一個優秀的程序員,怎么能夠不關注自己的代碼質量呢,還是自己動手試試吧

?現在我也找了很多測試的朋友,做了一個分享技術的交流群,共享了很多我們收集的技術文檔和視頻教程。
如果你不想再體驗自學時找不到資源,沒人解答問題,堅持幾天便放棄的感受
可以加入我們一起交流。而且還有很多在自動化,性能,安全,測試開發等等方面有一定建樹的技術大牛
分享他們的經驗,還會分享很多直播講座和技術沙龍
可以免費學習!劃重點!開源的!!!
qq群號:485187702【暗號:csdn11】

最后感謝每一個認真閱讀我文章的人,看著粉絲一路的上漲和關注,禮尚往來總是要有的,雖然不是什么很值錢的東西,如果你用得到的話可以直接拿走!?希望能幫助到你!【100%無套路免費領取】

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/209532.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/209532.shtml
英文地址,請注明出處:http://en.pswp.cn/news/209532.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

mmdetection測試保存到新的文件夾,無需標簽

這個是用demo這個代碼測試的&#xff0c;需要先訓練一個pth文件夾&#xff0c;訓練之后再調用pth文件夾進行測試。測試的代碼文件名是&#xff1a;image_demo_new.py&#xff0c;代碼如系所示&#xff1a; # Copyright (c) OpenMMLab. All rights reserved. import asyncio fr…

使用selenium的edge瀏覽器登錄某為

互聯網上基本都是某哥的用法&#xff0c;其實edge和某哥的用法是一樣的就有一下參數不一樣。 一、運行環境 Python&#xff1a;3.7 Selenium&#xff1a;4.11.2 Edge&#xff1a;版本 120.0.2210.61 (正式版本) (64 位) 二、執行代碼 from time import sleepfrom selenium…

調新浪分享

前端寫一個按鈕,通過按鈕來調出新浪界面, window.location.href http://service.weibo.com/share/share.php?url 這行代碼調出新浪分享界面,要是想要添加一些圖片和文字 使用: window.location.href http://service.weibo.com/share/share.php?url encodeURIComponent…

P2 Qt Creator創建第一個Qt程序

前言 &#x1f3ac; 個人主頁&#xff1a;ChenPi &#x1f43b;推薦專欄1: 《C_ChenPi的博客-CSDN博客》??? &#x1f525; 推薦專欄2: 《LLinux C應用編程&#xff08;概念類&#xff09;_ChenPi的博客-CSDN博客》??? &#x1f33a;本篇簡介 &#xff1a;這一章我們學…

Python基礎——兩個常用且容易忘記的知識點

1. replace函數的第三個參數 replace 方法提供了一個可選的參數 count&#xff0c;可以用于指定替換的次數。你可以將 count 設置為 1 來限制替換的次數&#xff0c;只替換第一個匹配項。 下面是使用 replace 方法限制替換次數的示例&#xff1a; date_str "2023/05/1…

二百一十一、Flume——Flume實時采集Linux中的Hive日志寫入到HDFS中(親測、附截圖)

一、目的 為了實現用Flume實時采集Hive的操作日志到HDFS中&#xff0c;于是進行了一場實驗 二、前期準備 &#xff08;一&#xff09;安裝好Hadoop、Hive、Flume等工具 &#xff08;二&#xff09;查看Hive的日志在Linux系統中的文件路徑 [roothurys23 conf]# find / -name…

smarty模版 [BJDCTF2020]The mystery of ip 1

打開題目 點擊flag給了我們一個ip 點擊hint&#xff0c;查看源代碼處告訴了我們要利用這個ip bp抓包&#xff0c;并添加X-Forward-For頭 所以這道題是XFF可控 本來聯想到XFF漏洞引起的sql注入&#xff0c;但是我們無論輸入什么都會正常回顯&#xff0c;就聯想到ssti注入 我們…

C/C++指針操作整理

C/C指針操作整理 面向曾經學習過指針的人&#xff0c;并非針對究極初學者。 一維指針 數據類型存儲的地址&#xff0c;指向數據存儲的地址&#xff0c;可以使用 &運算符取變量的地址&#xff0c;將其賦給指針變量。 int a 2; int *p &a;同時因為C/C中數組是連續存儲…

Java實現插入排序算法

插入排序算法 &#xff08;1&#xff09;概念&#xff1a;通過構建有序序列&#xff0c;對于未排序數據&#xff0c;在已排序序列中從后向前掃描&#xff0c;找到相應的位置并插入。 &#xff08;2&#xff09;一個通俗的比喻&#xff1a; 插入排序就類似于斗地主時&#xf…

CloudCompare 二次開發(23)——計算兩點云之間的放縮倍數

目錄 一、概述二、代碼集成三、結果展示一、概述 使用CloudCompare編程實現計算兩點云之間的放縮倍數。具體計算原理見:。 二、代碼集成 1、mainwindow.h文件public中添加: void doActionComputeScale(); // 計算兩點云的放縮倍數2、mainwindow.cpp文件void MainWin…

vue-element使用html2canvas實現網頁指定區域(指定dom元素)截圖

直接上代碼&#xff1a; <template><el-dialog :visible.sync"printDialogVisible" width"1000px" :close-on-click-modal"false" append-to-body><template><div :id"print_content" ref"print_content&q…

Python網絡爬蟲的基礎理解-對應的自我理解誤區

##通過一個中國大學大學排名爬蟲的示例進行基礎性理解 以軟科中國最好大學排名為分析對象&#xff0c;基于requests庫和bs4庫編寫爬蟲程序&#xff0c;對2015年至2019年間的中國大學排名數據進行爬取&#xff1a;&#xff08;1&#xff09;按照排名先后順序輸出不同年份的前10…

Linux下通過find找文件---通過修改時間查找(-mtime)

通過man手冊查找和-mtime選項相關的內容 man find | grep -A 3 mtime # 這里簡單介紹了 -mtime &#xff0c;還有一個簡單的示例-mtime n Files data was last modified n*24 hours ago. See the comments for -atime to understand how rounding affects the interpretati…

【已解決】解決Win7虛擬機打開網頁報錯的情況

因為剛才下載了個虛擬機&#xff0c;同樣出現了無法安裝VMtools的情況&#xff0c;所以想直接通過虛擬機的瀏覽器來下載一個補丁&#xff08;因為自己的U盤在虛擬機上面無法識別&#xff0c;應該是太老了Win7&#xff09; 結果發現Win7內置的IE瀏覽器太拉了。于是向下載一個火…

深度學習記錄--神經網絡表示及其向量化

神經網絡表示 如下圖 就這個神經網絡圖來說&#xff0c;它有三層&#xff0c;分別是輸入層(Input layer)&#xff0c;隱藏層(Hidden layer)&#xff0c;輸出層(Output layer) 對于其他的神經網絡&#xff0c;隱藏層可以有很多層 一般來說&#xff0c;不把輸入層算作一個標準…

【ITK庫學習】使用itk庫進行圖像濾波ImageFilter:幾何變換:翻轉、重采樣(未完)

目錄 1、itkFlipImageFilter 圖像翻轉濾波器2、itkResampleImageFilter 重采樣圖像濾波器 1、itkFlipImageFilter 圖像翻轉濾波器 該類的主要功能是使輸入數據在用戶指定的軸上進行翻轉。 翻轉軸通過函數SetFlipAxes(array) 設置&#xff0c;其中輸入是FixArray<bool,Imag…

UML圖的各種類型以及軟件設計師考試考察的方式

UML建模 前言 常見的UML的類型 UML 比前兩題是更難的&#xff08;略高&#xff0c;但是學會就可以了。前兩題是&#xff1a;數據流圖&#xff0c;數據庫的設計&#xff09;&#xff0c;因為UML圖有很多類型&#xff1a;用例圖&#xff0c;類圖與對象圖&#xff0c;順序圖&…

3_CSS層疊樣式表基礎

第3章-CSS層疊樣式表基礎 學習目標(Objective) 掌握標簽選擇器的使用掌握類選擇器的使用了解id選擇器和通配符選擇器掌握font屬性和color屬性的應用 1.HTML的局限性 如果要改變下高度或者變一個顏色&#xff0c;就需要大量重復操作 總結&#xff1a; HTML滿足不了設計者的需…

Emacs之dired模式重新綁定鍵值v(一百三十一)

簡介&#xff1a; CSDN博客專家&#xff0c;專注Android/Linux系統&#xff0c;分享多mic語音方案、音視頻、編解碼等技術&#xff0c;與大家一起成長&#xff01; 優質專欄&#xff1a;Audio工程師進階系列【原創干貨持續更新中……】&#x1f680; 優質專欄&#xff1a;多媒…

uniapp實戰 —— 輪播圖【數字下標】(含組件封裝,點擊圖片放大全屏預覽)

組件封裝 src\components\SUI_Swiper2.vue <script setup lang"ts"> import { ref } from vue const props defineProps({config: Object, })const activeIndex ref(0) const change: UniHelper.SwiperOnChange (e) > {activeIndex.value e.detail.cur…