日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android单元测试研究与实践

發(fā)布時(shí)間:2024/7/5 Android 52 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android单元测试研究与实践 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

處于高速迭代開發(fā)中的Android項(xiàng)目往往需要除黑盒測試外更加可靠的質(zhì)量保障,這正是單元測試的用武之地。單元測試周期性對項(xiàng)目進(jìn)行函數(shù)級別的測試,在良好的覆蓋率下,能夠持續(xù)維護(hù)代碼邏輯,從而支持項(xiàng)目從容應(yīng)對快速的版本更新。
單元測試是參與項(xiàng)目開發(fā)的工程師在項(xiàng)目代碼之外建立的白盒測試工程,用于執(zhí)行項(xiàng)目中的目標(biāo)函數(shù)并驗(yàn)證其狀態(tài)或者結(jié)果,其中,單元指的是測試的最小模塊,通常指函數(shù)。如圖1所示的綠色文件夾即是單元測試工程。這些代碼能夠檢測目標(biāo)代碼的正確性,打包時(shí)單元測試的代碼不會被編譯進(jìn)入APK中。

![](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2015/f6b3c032.png)
圖1 單元測試工程位置

與Java單元測試相同,Android單元測試也是維護(hù)代碼邏輯的白盒工程,但由于Android運(yùn)行環(huán)境的不同,Android單元測試的環(huán)境配置以及實(shí)施流程均有所不同。

Java單元測試

在傳統(tǒng)Java單元測試中,我們需要針對每個(gè)函數(shù)進(jìn)行設(shè)計(jì)單元測試用例。如圖2便是一個(gè)典型的單元測試的用例。

![](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2015/7f6b69db.png)
圖2 單元測試示例

上述示例中,針對函數(shù)dosomething(Boolean param)的每個(gè)分支,我們都需要構(gòu)造相應(yīng)的參數(shù)并驗(yàn)證結(jié)果。單元測試的目標(biāo)函數(shù)主要有三種: 1. 有明確的返回值,如上圖的dosomething(Boolean param),做單元測試時(shí),只需調(diào)用這個(gè)函數(shù),然后驗(yàn)證函數(shù)的返回值是否符合預(yù)期結(jié)果。 2. 這個(gè)函數(shù)只改變其對象內(nèi)部的一些屬性或者狀態(tài),函數(shù)本身沒有返回值,就驗(yàn)證它所改變的屬性和狀態(tài)。 3. 一些函數(shù)沒有返回值,也沒有直接改變哪個(gè)值的狀態(tài),這就需要驗(yàn)證其行為,比如點(diǎn)擊事件。

既沒有返回值,也沒有改變狀態(tài),又沒有觸發(fā)行為的函數(shù)是不可測試的,在項(xiàng)目中不應(yīng)該存在。當(dāng)存在同時(shí)具備上述多種特性時(shí),本文建議采用多個(gè)case來真對每一種特性逐一驗(yàn)證,或者采用一個(gè)case,逐一執(zhí)行目標(biāo)函數(shù)并驗(yàn)證其影響。
構(gòu)造用例的原則是測試用例與函數(shù)一對一,實(shí)現(xiàn)條件覆蓋與路徑覆蓋。Java單元測試中,良好的單元測試是需要保證所有函數(shù)執(zhí)行正確的,即所有邊界條件都驗(yàn)證過,一個(gè)用例只測一個(gè)函數(shù),便于維護(hù)。在Android單元測試中,并不要求對所有函數(shù)都覆蓋到,像Android SDK中的函數(shù)回調(diào)則不用測試。

Android單元測試

在Android中,單元測試的本質(zhì)依舊是驗(yàn)證函數(shù)的功能,測試框架也是JUnit。在Java中,編寫代碼面對的只有類、對象、函數(shù),編寫單元測試時(shí)可以在測試工程中創(chuàng)建一個(gè)對象出來然后執(zhí)行其函數(shù)進(jìn)行測試,而在Android中,編寫代碼需要面對的是組件、控件、生命周期、異步任務(wù)、消息傳遞等,雖然本質(zhì)是SDK主動執(zhí)行了一些實(shí)例的函數(shù),但創(chuàng)建一個(gè)Activity并不能讓它執(zhí)行到resume的狀態(tài),因此需要JUnit之外的框架支持。
當(dāng)前主流的單元測試框架AndroidTest和Robolectric,前者需要運(yùn)行在Android環(huán)境上,后者可以直接運(yùn)行在JVM上,速度也更快,可以直接由Jenkins周期性執(zhí)行,無需準(zhǔn)備Android環(huán)境。因此我們的單元測試基于Robolectric。對于一些測試對象依賴度較高而需要解除依賴的場景,我們可以借助Mock框架。

Robolectric環(huán)境配置

Android單元測試依舊需要JUnit框架的支持,Robolectric只是提供了Android代碼的運(yùn)行環(huán)境。如果使用Robolectric 3.0,依賴配置如下:

testCompile 'junit:junit:4.10' testCompile 'org.robolectric:robolectric:3.0'

Gradle對Robolectric 2.4的支持并不像3.0這樣好,但Robolectric 2.4所有的測試框架均在一個(gè)包里,另外參考資料也比較豐富,作者更習(xí)慣使用2.4。如果使用Robolectric 2.4,則需要如下配置:

classpath 'org.robolectric:robolectric-gradle-plugin:0.14.+'//這行配置在buildscript的dependencies中 apply plugin: 'robolectric' androidTestCompile 'org.robolectric:robolectric:2.4'

上述配置中,本文將testCompile寫成androidTest,并且常見的Android工程的單元測試目錄名稱有test也有androidTest,這兩種寫法并沒有功能上的差別,只是Android單元測試Test Artifact不同而已。Test Artifact如圖3所示:

![](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2015/6e8678e9.png)
圖3 Test Artifact

在Gradle插件中,這兩種Artifact執(zhí)行的Task還是有些區(qū)別的,但是并不影響單元測試的寫法與效果。雖然可以主動配置單元測試的項(xiàng)目路徑,本文依舊建議采用與Test Artifact對應(yīng)的項(xiàng)目路徑和配置寫法。

Mock配置

如果要測試的目標(biāo)對象依賴關(guān)系較多,需要解除依賴關(guān)系,以免測試用例過于復(fù)雜,用Robolectric的Shadow是個(gè)辦法,但是推薦更加簡單的Mock框架,比如Mockito,該框架可以模擬出對象來,而且本身提供了一些驗(yàn)證函數(shù)執(zhí)行的功能。Mockito配置如下:

repositories {jcenter() } dependencies {testCompile "org.mockito:mockito-core:1.+" }

Robolectric單元測試編寫結(jié)構(gòu)

單元測試代碼寫在項(xiàng)目的test(也可能是androidTest,該目錄在項(xiàng)目中會呈淺綠色)目錄下。單元測試也是一個(gè)標(biāo)準(zhǔn)的Java工程,以類為文件單位編寫,執(zhí)行的最小單位是函數(shù),測試用例(以下簡稱case)是帶有@Test注解的函數(shù),單元測試?yán)锩鎺в衏ase的類由Robolectric框架執(zhí)行,需要為該類添加注解@RunWith(RobolectricTestRunner.class)?;赗obolectric的代碼結(jié)構(gòu)如下:

//省略一堆import @RunWith(RobolectricTestRunner.class) public class MainActivityTest {@Beforepublic void setUp() {//執(zhí)行初始化的操作}@Testpublic void testCase() {//執(zhí)行各種測試邏輯判斷} }

上述結(jié)構(gòu)中,帶有@Before注解的函數(shù)在該類實(shí)例化后,會立即執(zhí)行,通常用于執(zhí)行一些初始化的操作,比如構(gòu)造網(wǎng)絡(luò)請求和構(gòu)造Activity。帶有@test注解的是單元測試的case,由Robolectric執(zhí)行,這些case本身也是函數(shù),可以在其他函數(shù)中調(diào)用,因此,case也是可以復(fù)用的。每個(gè)case都是獨(dú)立的,case不會互相影響,即便是相互調(diào)用也不會存在多線程干擾的問題。

常見Robolectric用法

Robolectric支持單元測試范圍從Activity的跳轉(zhuǎn)、Activity展示View(包括菜單)和Fragment到View的點(diǎn)擊觸摸以及事件響應(yīng),同時(shí)Robolectric也能測試Toast和Dialog。對于需要網(wǎng)絡(luò)請求數(shù)據(jù)的測試,Robolectric可以模擬網(wǎng)絡(luò)請求的response。對于一些Robolectric不能測試的對象,比如ConcurrentTask,可以通過自定義Shadow的方式現(xiàn)實(shí)測試。下面將著重介紹Robolectric的常見用法。 Robolectric 2.4模擬網(wǎng)絡(luò)請求 由于商業(yè)App的多數(shù)Activity界面數(shù)據(jù)都是通過網(wǎng)絡(luò)請求獲取,因?yàn)榫W(wǎng)絡(luò)請求是大多數(shù)App首要處理的模塊,測試依賴網(wǎng)絡(luò)數(shù)據(jù)的Activity時(shí),可以在@Before標(biāo)記的函數(shù)中準(zhǔn)備網(wǎng)絡(luò)數(shù)據(jù),進(jìn)行網(wǎng)絡(luò)請求的模擬。準(zhǔn)備網(wǎng)絡(luò)請求的代碼如下:

public void prepareHttpResponse(String filePath) throws IOException {String netData = FileUtils.readFileToString(FileUtils.toFile(getClass().getResource(filePath)), HTTP.UTF_8);Robolectric.setDefaultHttpResponse(200, netData); }//代碼適用于Robolectric 2.4,3.0需要注意網(wǎng)絡(luò)請求的包的位置

由于Robolectric 2.4并不會發(fā)送網(wǎng)絡(luò)請求,因此需要本地創(chuàng)建網(wǎng)絡(luò)請求所返回的數(shù)據(jù),上述函數(shù)的filePath便是本地?cái)?shù)據(jù)的文件的路徑,setDefaultHttpResponse()則創(chuàng)建了該請求的Response。上述函數(shù)執(zhí)行后,單元測試工程便擁有了與本地?cái)?shù)據(jù)數(shù)據(jù)對應(yīng)的網(wǎng)絡(luò)請求,在這個(gè)函數(shù)執(zhí)行后展示的Activity便是有數(shù)據(jù)的Activity。
在Robolectric 3.0環(huán)境下,單元測試可以發(fā)真的請求,并且能夠請求到數(shù)據(jù),本文依舊建議采用mock的辦法構(gòu)造網(wǎng)絡(luò)請求,而不要依賴網(wǎng)絡(luò)環(huán)境。 Activity展示測試與跳轉(zhuǎn)測試 創(chuàng)建網(wǎng)絡(luò)請求后,便可以測試Activity了。測試代碼如下:

@Test public void testSampleActivity(){SampleActivity sampleActivity=Robolectric.buildActivity(SampleActivity.class).create().resume().get();assertNotNull(sampleActivity);assertEquals("Activity的標(biāo)題", sampleActivity.getTitle()); }

Robolectric.buildActivity()用于構(gòu)造Activity,create()函數(shù)執(zhí)行后,該Activity會運(yùn)行到onCreate周期,resume()則對應(yīng)onResume周期。assertNotNull和assertEquals是JUnit中的斷言,Robolectric只提供運(yùn)行環(huán)境,邏輯判斷還是需要依賴JUnit中的斷言。
Activity跳轉(zhuǎn)是Android開發(fā)的重要邏輯,其測試方法如下:

@Test public void testActivityTurn(ActionBarActivity firstActivity, Class secondActivity) {Intent intent = new Intent(firstActivity.getApplicationContext(), secondActivity);assertEquals(intent, Robolectric.shadowOf(firstActivity).getNextStartedActivity());//3.0的API與2.4不同 }

Fragment展示與切換 Fragment是Activity的一部分,在Robolectric模擬執(zhí)行Activity過程中,如果觸發(fā)了被測試的代碼中的Fragment添加邏輯,Fragment會被添加到Activity中。
需要注意Fragment出現(xiàn)的時(shí)機(jī),如果目標(biāo)Activity中的Fragment的添加是執(zhí)行在onResume階段,在Activity被Robolectric執(zhí)行resume()階段前,該Activity中并不會出現(xiàn)該Fragment。采用Robolectric主動添加Fragment的方法如下:

@Test public void addfragment(Activity activity, int fragmentContent){FragmentTestUtil.startFragment(activity.getSupportFragmentManager().findFragmentById(fragmentContent));Fragment fragment = activity.getSupportFragmentManager().findFragmentById(fragmentContent);assertNotNull(fragment); }

startFragment()函數(shù)的主體便是常用的添加fragment的代碼。切換一個(gè)Fragment往往由Activity中的代碼邏輯完成,需要Activity的引用。 控件的點(diǎn)擊以及可視驗(yàn)證

@Test public void testButtonClick(int buttonID){Button submitButton = (Button) activity.findViewById(buttonID);assertTrue(submitButton.isEnabled());submitButton.performClick();//驗(yàn)證控件的行為 }

對控件的點(diǎn)擊驗(yàn)證是調(diào)用performClick(),然后斷言驗(yàn)證其行為。對于ListView這類涉及到Adapter的控件的點(diǎn)擊驗(yàn)證,寫法如下:

//listView被展示之后 listView.performItemClick(listView.getAdapter().getView(position, null, null), 0, 0);

與button等控件稍有不同。 Dialog和Toast測試 測試Dialog和Toast的方法如下:

public void testDialog(){Dialog dialog = ShadowDialog.getLatestDialog();assertNotNull(dialog); } public void testToast(String toastContent){ShadowHandler.idleMainLooper();assertEquals(toastContent, ShadowToast.getTextOfLatestToast()); }

上述函數(shù)均需要在Dialog或Toast產(chǎn)生之后執(zhí)行,能夠測試Dialog和Toast是否彈出。

Shadow寫法介紹

Robolectric的本質(zhì)是在Java運(yùn)行環(huán)境下,采用Shadow的方式對Android中的組件進(jìn)行模擬測試,從而實(shí)現(xiàn)Android單元測試。對于一些Robolectirc暫不支持的組件,可以采用自定義Shadow的方式擴(kuò)展Robolectric的功能。

@Implements(Point.class) public class ShadowPoint {@RealObject private Point realPoint;...public void __constructor__(int x, int y) {realPoint.x = x;realPoint.y = y;} }//樣例來源于Robolectric官網(wǎng)

上述實(shí)例中,@Implements是聲明Shadow的對象,@RealObject是獲取一個(gè)Android 對象,constructor則是該Shadow的構(gòu)造函數(shù),Shadow還可以修改一些函數(shù)的功能,只需要在重載該函數(shù)的時(shí)候添加@Implementation,這種方式可以有效擴(kuò)展Robolectric的功能。
Shadow是通過對真實(shí)的Android對象進(jìn)行函數(shù)重載、初始化等方式對Android對象進(jìn)行擴(kuò)展,Shadow出來的對象的功能接近Android對象,可以看成是對Android對象一種修復(fù)。自定義的Shadow需要在config中聲明,聲明寫法是@Config(shadows=ShadowPoint.class)。

Mock寫法介紹

對于一些依賴關(guān)系復(fù)雜的測試對象,可以采用Mock框架解除依賴,常用的有Mockito。例如Mock一個(gè)List類型的對象實(shí)例,可以采用如下方式:

List list = mock(List.class); //mock得到一個(gè)對象,也可以用@mock注入一個(gè)對象

所得到的list對象實(shí)例便是List類型的實(shí)例,如果不采用mock,List其實(shí)只是個(gè)接口,我們需要構(gòu)造或者借助ArrayList才能進(jìn)行實(shí)例化。與Shadow不同,Mock構(gòu)造的是一個(gè)虛擬的對象,用于解耦真實(shí)對象所需要的依賴。Mock得到的對象僅僅是具備測試對象的類型,并不是真實(shí)的對象,也就是并沒有執(zhí)行過真實(shí)對象的邏輯。 Mock也具備一些補(bǔ)充JUnit的驗(yàn)證函數(shù),比如設(shè)置函數(shù)的執(zhí)行結(jié)果,示例如下:

When(sample.dosomething()).thenReturn(someAction);//when(一個(gè)函數(shù)執(zhí)行).thenReturn(一個(gè)可替代真實(shí)函數(shù)的結(jié)果的返回值); //上述代碼是設(shè)置sample.dosomething()的返回值,當(dāng)執(zhí)行了sample.dosomething()這個(gè)函數(shù)時(shí),就會得到someAction,從而解除了對真實(shí)的sample.dosomething()函數(shù)的依賴

上述代碼為被測函數(shù)定義一個(gè)可替代真實(shí)函數(shù)的結(jié)果的返回值。當(dāng)使用這個(gè)函數(shù)后,這個(gè)可驗(yàn)證的結(jié)果便會產(chǎn)生影響,從而代替函數(shù)的真實(shí)結(jié)果,這樣便解除了對真實(shí)函數(shù)的依賴。 同時(shí)Mock框架也可以驗(yàn)證函數(shù)的執(zhí)行次數(shù),代碼如下:

List list = mock(List.class); //Mock得到一個(gè)對象 list.add(1); //執(zhí)行一個(gè)函數(shù) verify(list).add(1); //驗(yàn)證這個(gè)函數(shù)的執(zhí)行 verify(list,time(3)).add(1); //驗(yàn)證這個(gè)函數(shù)的執(zhí)行次數(shù)

在一些需要解除網(wǎng)絡(luò)依賴的場景中,多使用Mock。比如對retrofit框架的網(wǎng)絡(luò)依賴解除如下:

//代碼參考了參考文獻(xiàn)[3] public class MockClient implements Client {@Overridepublic Response execute(Request request) throws IOException {Uri uri = Uri.parse(request.getUrl());String responseString = "";if(uri.getPath().equals("/path/of/interest")) {responseString = "返回的json1";//這里是設(shè)置返回值} else {responseString = "返回的json2";}return new Response(request.getUrl(), 200, "nothing", Collections.EMPTY_LIST, new TypedByteArray("application/json", responseString.getBytes()));} } //MockClient使用方式如下: RestAdapter.Builder builder = new RestAdapter.Builder(); builder.setClient(new MockClient());

這種方式下retrofit的response可以由單元測試編寫者設(shè)置,而不來源于網(wǎng)絡(luò),從而解除了對網(wǎng)絡(luò)環(huán)境的依賴。

單元測試的范圍

在Android項(xiàng)目中,單元測試的對象是組件狀態(tài)、控件行為、界面元素和自定義函數(shù)。本文并不推薦對每個(gè)函數(shù)進(jìn)行一對一的測試,像onStart()、onDestroy()這些周期函數(shù)并不需要全部覆蓋到。商業(yè)項(xiàng)目多采用Scrum模式,要求快速迭代,有時(shí)候未必有較多的時(shí)間寫單元測試,不再要求逐個(gè)函數(shù)寫單元測試。
本文單元測試的case多來源于一個(gè)簡短的業(yè)務(wù)邏輯,單元測試case需要對這段業(yè)務(wù)邏輯進(jìn)行驗(yàn)證。在驗(yàn)證的過程中,開發(fā)人員可以深度了解業(yè)務(wù)流程,同時(shí)新人來了看一下項(xiàng)目單元測試就知道哪個(gè)邏輯跑了多少函數(shù),需要注意哪些邊界——是的,單元測試需要像文檔一樣具備業(yè)務(wù)指導(dǎo)能力。
在大型項(xiàng)目中,遇到需要改動基類中代碼的需求時(shí),往往不能準(zhǔn)確快速地知道改動后的影響范圍,緊急時(shí)多采用創(chuàng)建子類覆蓋父類函數(shù)的辦法,但這不是長久之計(jì),在足夠覆蓋率的單元測試支持下,跑一下單元測試就知道某個(gè)函數(shù)改動后的影響,可以放心地修改基類。
美團(tuán)的Android單元測試編寫流程如圖4所示。

![](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2015/607bd9fe.png)
圖4 美團(tuán)Android單元測試編寫流程

單元測試最終需要輸出文檔式的單元測試代碼,為線上代碼提供良好的代碼穩(wěn)定性保證。

單元測試的流程

實(shí)際項(xiàng)目中,單元測試對象與頁面是一對一的,并不建議跨頁面,這樣的單元測試藕合度太大,維護(hù)困難。單元測試需要找到頁面的入口,分析項(xiàng)目頁面中的元素、業(yè)務(wù)邏輯,這里的邏輯不僅僅包括界面元素的展示以及控件組件的行為,還包括代碼的處理邏輯。然后可以創(chuàng)建單元測試case列表(列表用于紀(jì)錄項(xiàng)目中單元測試的范圍,便于單元測試的管理以及新人了解業(yè)務(wù)流程),列表中記錄單元測試對象的頁面,對象中的case邏輯以及名稱等。工程師可以根據(jù)這個(gè)列表開始寫單元測試代碼。 單元測試是工程師代碼級別的質(zhì)量保證工程,上述流程并不能完全覆蓋重要的業(yè)務(wù)邏輯以及邊界條件,因此,需要寫完后,看覆蓋率,找出單元測試中沒有覆蓋到的函數(shù)分支條件等,然后繼續(xù)補(bǔ)充單元測試case列表,并在單元測試工程代碼中補(bǔ)上case。 直到規(guī)劃的頁面中所有邏輯的重要分支、邊界條件都被覆蓋,該項(xiàng)目的單元測試結(jié)束。單元測試流程如圖5所示。

![](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2015/b0f09068.jpg)
圖5 單元測試執(zhí)行流程

上述分析頁面入口所得到結(jié)果便是@Before標(biāo)記的函數(shù)中的代碼,之后的循環(huán)便是所有的case(@Test標(biāo)記的函數(shù))。

為了系統(tǒng)的介紹單元測試的實(shí)施過程,本文創(chuàng)建了一個(gè)小型的demo項(xiàng)目作為測試對象。demo的功能是供用戶發(fā)布所見的新聞到服務(wù)端,并瀏覽所有已經(jīng)發(fā)表的新聞,是個(gè)典型的自媒體應(yīng)用。該demo的開發(fā)和測試涉及到TextView、EditView、ListView、Button以及自定義View,包含了網(wǎng)絡(luò)請求、多線程、異步任務(wù)以及界面跳轉(zhuǎn)等。能夠?yàn)槎鄶?shù)商業(yè)項(xiàng)目提供參照樣例。項(xiàng)目頁面如圖6所示。

![](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2015/6a8ce640.jpg)
圖6 單元測試case設(shè)計(jì)

首先需要分析App的每個(gè)頁面,針對頁面提取出簡短的業(yè)務(wù)邏輯,提取出的業(yè)務(wù)邏輯如圖6綠色圈圖所示。根據(jù)這些邏輯來設(shè)計(jì)單元測試的case(帶有@Test注解的那個(gè)函數(shù)),這里的業(yè)務(wù)邏輯不僅指需求中的業(yè)務(wù),還包括其他需要維護(hù)的代碼邏輯。業(yè)務(wù)流程不允許跨頁面,以免增加單元測試case的維護(hù)成本。針對demo中界面的單元測試case設(shè)計(jì)如下:

表1 單元測試case列表 目標(biāo)頁面業(yè)務(wù)覆蓋界面元素邏輯描述最小斷言數(shù)case名稱
創(chuàng)建新聞頁面
NewsCreatedActivity
編寫新聞1.標(biāo)題框
2.內(nèi)容框
3.發(fā)布按鈕
1.向標(biāo)題框輸入內(nèi)容
2.向內(nèi)容框輸入內(nèi)容
3.當(dāng)標(biāo)題和內(nèi)容都存在的時(shí)候,上傳按鈕可點(diǎn)擊
3testWriteNews()
輸入新聞的金額1.Checkbox
2.金額控件
1.選中免費(fèi)發(fā)布時(shí),金額輸入框消失
2.不選免費(fèi)時(shí)可以輸入金額
3.金額輸入框只接受小數(shù)點(diǎn)后最多兩位
3testValue()
菜單跳轉(zhuǎn)至新聞列表1.菜單按鈕1.點(diǎn)擊菜單跳轉(zhuǎn)到新聞列表頁面1testMenuForTrunNewsList()
發(fā)布新聞1.發(fā)布按鈕
2.Toast
1.當(dāng)標(biāo)題或者內(nèi)容為空時(shí),發(fā)布按鈕不可點(diǎn)擊
2.編寫了新聞的前提下,點(diǎn)擊發(fā)布按鈕
3.新聞發(fā)布成功,彈出Toast提示 “新聞已提交”
4.沒有標(biāo)題或者內(nèi)容時(shí),新聞發(fā)布失敗,彈出Toast提示“新聞提交失敗”
5testNewsPush()、
testPushNewsFailed()
新聞列表頁面
NewsListActivity
瀏覽新聞列表1.列表1.進(jìn)入此頁面后會出現(xiàn)新聞列表
2.有網(wǎng)絡(luò)情況下,能發(fā)起網(wǎng)絡(luò)請求
3.網(wǎng)絡(luò)請求需要用Mock解除偶和,單獨(dú)驗(yàn)證頁面對數(shù)據(jù)的響應(yīng),后端返回一項(xiàng)時(shí),列表只有一條數(shù)據(jù)
6testNewsListNoNetwork()、
testGetnewsWhenNetwork()、
testSetNews()
菜單跳轉(zhuǎn)至創(chuàng)建新聞頁面1.菜單按鈕1.點(diǎn)擊菜單跳轉(zhuǎn)到創(chuàng)建新聞頁面1testMenuForTrunCreatNews()
查看詳細(xì)新聞1.有內(nèi)容的列表
2.Dialog
1.有新聞的前提下,列表可點(diǎn)擊,點(diǎn)擊彈出Dialog1testNewsDialog()

接下來需要在單元測試工程中實(shí)現(xiàn)上述case,最小斷言數(shù)是業(yè)務(wù)邏輯上的判斷,并不是代碼的邊界條件,真實(shí)的case需要考慮代碼的邊界條件,比如數(shù)組為空等條件,因此,最終的斷言數(shù)量會大于等于最小斷言數(shù)。在需求業(yè)務(wù)上,最小斷言數(shù)也是該需求的業(yè)務(wù)條件。
寫完case后需要跑一遍單元測試并檢查覆蓋率報(bào)告,當(dāng)覆蓋率報(bào)告中缺少有些單元測試case列表中沒有但是實(shí)際邏輯中會有的邏輯時(shí),需要更新單元測試case列表,添加遺漏的邏輯,并將對應(yīng)的代碼補(bǔ)上。直到所有需要維護(hù)的邏輯都被覆蓋,該項(xiàng)目中的單元測試才算完成。單元測試并不是QA的黑盒測試,需要保證對代碼邏輯的覆蓋。
對表1分析,第一個(gè)頁面的“發(fā)布新聞”的case可以直接調(diào)用“編寫新聞”的case,以滿足條件“2.編寫了新聞的前提下,點(diǎn)擊發(fā)布按鈕”,在JUnit框架下,case(帶@Test注解的那個(gè)函數(shù))也是個(gè)函數(shù),直接調(diào)用這個(gè)函數(shù)就不是case,和case是無關(guān)的,兩者并不會相互影響,可以直接調(diào)用以減少重復(fù)代碼。第二個(gè)頁面不同于第一個(gè),一進(jìn)入就需要網(wǎng)絡(luò)請求,后續(xù)業(yè)務(wù)都需要依賴這個(gè)網(wǎng)絡(luò)請求,單元測試不應(yīng)該對某一個(gè)條件過度耦合,因此,需要用mock解除耦合,直接mock出網(wǎng)絡(luò)請求得到的數(shù)據(jù),單獨(dú)驗(yàn)證頁面對數(shù)據(jù)的響應(yīng)。

單元測試并不是一個(gè)能直接產(chǎn)生回報(bào)的工程,它的運(yùn)行以及覆蓋率也不能直接提升代碼質(zhì)量,但其帶來的代碼控制力能夠大幅度降低大規(guī)模協(xié)同開發(fā)的風(fēng)險(xiǎn)。現(xiàn)在的商業(yè)App開發(fā)都是大型團(tuán)隊(duì)協(xié)作開發(fā),不斷會有新人加入,無論新人是剛?cè)胄械膽?yīng)屆生還是工作多年,在代碼存在一定業(yè)務(wù)耦合度的時(shí)候,修改代碼就有一定風(fēng)險(xiǎn),可能會影響之前比較隱蔽的業(yè)務(wù)邏輯,或者是丟失曾經(jīng)的補(bǔ)丁,如果有高覆蓋率的單元測試工程,就能很快定位到新增代碼對現(xiàn)有項(xiàng)目的影響,與QA驗(yàn)收不同,這種影響是代碼級的。
在本文所設(shè)計(jì)的單元測試流程中,單元測試的case和具體頁面的具體業(yè)務(wù)流程以及該業(yè)務(wù)的代碼邏輯緊密聯(lián)系,單元測試如同技術(shù)文檔一般,能夠體現(xiàn)出一個(gè)業(yè)務(wù)邏輯運(yùn)行了多少函數(shù),需要注意什么樣的條件。這是一種新人了解業(yè)務(wù)流程、對業(yè)務(wù)進(jìn)行代碼級別融入的好辦法,看一下以前的單元測試case,就能知道與該case對應(yīng)的那個(gè)頁面上的那個(gè)業(yè)務(wù)邏輯會執(zhí)行多少函數(shù),以及這些函數(shù)可能出現(xiàn)的結(jié)果。

[1] http://robolectric.org [2] https://github.com/square/okhttp/tree/master/mockwebserver [3] http://stackoverflow.com/questions/17544751/square-retrofit-server-mock-for-testing [4] https://en.wikipedia.org/wiki/Unit_testing

總結(jié)

以上是生活随笔為你收集整理的Android单元测试研究与实践的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。