自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Android單元測試研究與實(shí)踐

移動(dòng)開發(fā) Android
單元測試是參與項(xiàng)目開發(fā)的工程師在項(xiàng)目代碼之外建立的白盒測試工程,用于執(zhí)行項(xiàng)目中的目標(biāo)函數(shù)并驗(yàn)證其狀態(tài)或者結(jié)果,其中,單元指的是測試的最小模塊,通常指函數(shù)。如圖1所示的綠色文件夾即是單元測試工程。這些代碼能夠檢測目標(biāo)代碼的正確性,打包時(shí)單元測試的代碼不會(huì)被編譯進(jìn)入APK中。

Android單元測試介紹

處于高速迭代開發(fā)中的Android項(xiàng)目往往需要除黑盒測試外更加可靠的質(zhì)量保障,這正是單元測試的用武之地。單元測試周期性對(duì)項(xiàng)目進(jìn)行函數(shù)級(jí)別的測試,在良好的覆蓋率下,能夠持續(xù)維護(hù)代碼邏輯,從而支持項(xiàng)目從容應(yīng)對(duì)快速的版本更新。

單元測試是參與項(xiàng)目開發(fā)的工程師在項(xiàng)目代碼之外建立的白盒測試工程,用于執(zhí)行項(xiàng)目中的目標(biāo)函數(shù)并驗(yàn)證其狀態(tài)或者結(jié)果,其中,單元指的是測試的最小模塊,通常指函數(shù)。如圖1所示的綠色文件夾即是單元測試工程。這些代碼能夠檢測目標(biāo)代碼的正確性,打包時(shí)單元測試的代碼不會(huì)被編譯進(jìn)入APK中。 

 

 

單元測試工程位置 

圖1 單元測試工程位置

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

Java單元測試

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

 

 

單元測試示例 

圖2 單元測試示例

上述示例中,針對(duì)函數(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ù)只改變其對(duì)象內(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來真對(duì)每一種特性逐一驗(yàn)證,或者采用一個(gè)case,逐一執(zhí)行目標(biāo)函數(shù)并驗(yàn)證其影響。

構(gòu)造用例的原則是測試用例與函數(shù)一對(duì)一,實(shí)現(xiàn)條件覆蓋與路徑覆蓋。Java單元測試中,良好的單元測試是需要保證所有函數(shù)執(zhí)行正確的,即所有邊界條件都驗(yàn)證過,一個(gè)用例只測一個(gè)函數(shù),便于維護(hù)。在Android單元測試中,并不要求對(duì)所有函數(shù)都覆蓋到,像Android SDK中的函數(shù)回調(diào)則不用測試。

Android單元測試

在Android中,單元測試的本質(zhì)依舊是驗(yàn)證函數(shù)的功能,測試框架也是JUnit。在Java中,編寫代碼面對(duì)的只有類、對(duì)象、函數(shù),編寫單元測試時(shí)可以在測試工程中創(chuàng)建一個(gè)對(duì)象出來然后執(zhí)行其函數(shù)進(jìn)行測試,而在Android中,編寫代碼需要面對(duì)的是組件、控件、生命周期、異步任務(wù)、消息傳遞等,雖然本質(zhì)是SDK主動(dòng)執(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。對(duì)于一些測試對(duì)象依賴度較高而需要解除依賴的場景,我們可以借助Mock框架。

Android單元測試環(huán)境配置

Robolectric環(huán)境配置

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

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

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

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

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

 

 

Test Artifact 

圖3 Test Artifact

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

Mock配置

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

  1. repositories { 
  2.  
  3. jcenter() 
  4.  
  5.  
  6. dependencies { 
  7.  
  8. testCompile "org.mockito:mockito-core:1.+" 
  9.  
  10.  

Robolectric使用介紹

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

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

  1. //省略一堆import 
  2.  
  3. @RunWith(RobolectricTestRunner.class) 
  4.  
  5. public class MainActivityTest { 
  6.  
  7. @Before 
  8.  
  9. public void setUp() { 
  10.  
  11. //執(zhí)行初始化的操作 
  12.  
  13.  
  14. <a href="http://www.jobbole.com/members/madao">@Test</a> 
  15.  
  16. public void testCase() { 
  17.  
  18. //執(zhí)行各種測試邏輯判斷 
  19.  
  20.  
  21.  

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

常見Robolectric用法

Robolectric支持單元測試范圍從Activity的跳轉(zhuǎn)、Activity展示View(包括菜單)和Fragment到View的點(diǎn)擊觸摸以及事件響應(yīng),同時(shí)Robolectric也能測試Toast和Dialog。對(duì)于需要網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)的測試,Robolectric可以模擬網(wǎng)絡(luò)請(qǐng)求的response。對(duì)于一些Robolectric不能測試的對(duì)象,比如ConcurrentTask,可以通過自定義Shadow的方式現(xiàn)實(shí)測試。下面將著重介紹Robolectric的常見用法。

Robolectric 2.4模擬網(wǎng)絡(luò)請(qǐng)求

由于商業(yè)App的多數(shù)Activity界面數(shù)據(jù)都是通過網(wǎng)絡(luò)請(qǐng)求獲取,因?yàn)榫W(wǎng)絡(luò)請(qǐng)求是大多數(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ò)請(qǐng)求的模擬。準(zhǔn)備網(wǎng)絡(luò)請(qǐng)求的代碼如下:

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

由于Robolectric 2.4并不會(huì)發(fā)送網(wǎng)絡(luò)請(qǐng)求,因此需要本地創(chuàng)建網(wǎng)絡(luò)請(qǐng)求所返回的數(shù)據(jù),上述函數(shù)的filePath便是本地?cái)?shù)據(jù)的文件的路徑,setDefaultHttpResponse()則創(chuàng)建了該請(qǐng)求的Response。上述函數(shù)執(zhí)行后,單元測試工程便擁有了與本地?cái)?shù)據(jù)數(shù)據(jù)對(duì)應(yīng)的網(wǎng)絡(luò)請(qǐng)求,在這個(gè)函數(shù)執(zhí)行后展示的Activity便是有數(shù)據(jù)的Activity。

在Robolectric 3.0環(huán)境下,單元測試可以發(fā)真的請(qǐng)求,并且能夠請(qǐng)求到數(shù)據(jù),本文依舊建議采用mock的辦法構(gòu)造網(wǎng)絡(luò)請(qǐng)求,而不要依賴網(wǎng)絡(luò)環(huán)境。

Activity展示測試與跳轉(zhuǎn)測試

創(chuàng)建網(wǎng)絡(luò)請(qǐng)求后,便可以測試Activity了。測試代碼如下:

  1. <a href="http://www.jobbole.com/members/madao">@Test</a> 
  2.  
  3. public void testSampleActivity(){ 
  4.  
  5. SampleActivity sampleActivity=Robolectric.buildActivity(SampleActivity.class). 
  6.  
  7. create().resume().get(); 
  8.  
  9. assertNotNull(sampleActivity); 
  10.  
  11. assertEquals("Activity的標(biāo)題", sampleActivity.getTitle()); 
  12.  
  13.  

Robolectric.buildActivity()用于構(gòu)造Activity,create()函數(shù)執(zhí)行后,該Activity會(huì)運(yùn)行到onCreate周期,resume()則對(duì)應(yīng)onResume周期。assertNotNull和assertEquals是JUnit中的斷言,Robolectric只提供運(yùn)行環(huán)境,邏輯判斷還是需要依賴JUnit中的斷言。

Activity跳轉(zhuǎn)是Android開發(fā)的重要邏輯,其測試方法如下:

  1. <a href="http://www.jobbole.com/members/madao">@Test</a> 
  2.  
  3. public void testActivityTurn(ActionBarActivity firstActivity, Class secondActivity) { 
  4.  
  5. Intent intent = new Intent(firstActivity.getApplicationContext(), secondActivity); 
  6.  
  7. assertEquals(intent, Robolectric.shadowOf(firstActivity).getNextStartedActivity());//3.0的API與2.4不同 
  8.  
  9.  

Fragment展示與切換

Fragment是Activity的一部分,在Robolectric模擬執(zhí)行Activity過程中,如果觸發(fā)了被測試的代碼中的Fragment添加邏輯,F(xiàn)ragment會(huì)被添加到Activity中。

需要注意Fragment出現(xiàn)的時(shí)機(jī),如果目標(biāo)Activity中的Fragment的添加是執(zhí)行在onResume階段,在Activity被Robolectric執(zhí)行resume()階段前,該Activity中并不會(huì)出現(xiàn)該Fragment。采用Robolectric主動(dòng)添加Fragment的方法如下:

  1. <a href="http://www.jobbole.com/members/madao">@Test</a> 
  2.  
  3. public void addfragment(Activity activity, int fragmentContent){ 
  4.  
  5. FragmentTestUtil.startFragment(activity.getSupportFragmentManager().findFragmentById(fragmentContent)); 
  6.  
  7. Fragment fragment = activity.getSupportFragmentManager().findFragmentById(fragmentContent); 
  8.  
  9. assertNotNull(fragment); 
  10.  
  11.  

startFragment()函數(shù)的主體便是常用的添加fragment的代碼。切換一個(gè)Fragment往往由Activity中的代碼邏輯完成,需要Activity的引用。

控件的點(diǎn)擊以及可視驗(yàn)證

  1. <a href="http://www.jobbole.com/members/madao">@Test</a> 
  2.  
  3. public void testButtonClick(int buttonID){ 
  4.  
  5. Button submitButton = (Button) activity.findViewById(buttonID); 
  6.  
  7. assertTrue(submitButton.isEnabled()); 
  8.  
  9. submitButton.performClick(); 
  10.  
  11. //驗(yàn)證控件的行為 
  12.  
  13.  

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

  1. //listView被展示之后 
  2.  
  3. listView.performItemClick(listView.getAdapter().getView(position, nullnull), 0, 0);  

與button等控件稍有不同。

Dialog和Toast測試

測試Dialog和Toast的方法如下:

  1. public void testDialog(){ 
  2.  
  3. Dialog dialog = ShadowDialog.getLatestDialog(); 
  4.  
  5. assertNotNull(dialog); 
  6.  
  7.  
  8. public void testToast(String toastContent){ 
  9.  
  10. ShadowHandler.idleMainLooper(); 
  11.  
  12. assertEquals(toastContent, ShadowToast.getTextOfLatestToast()); 
  13.  
  14.  

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

Shadow寫法介紹

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

  1. @Implements(Point.class) 
  2.  
  3. public class ShadowPoint { 
  4.  
  5. @RealObject private Point realPoint; 
  6.  
  7. ... 
  8.  
  9. public void __constructor__(int x, int y) { 
  10.  
  11. realPoint.x = x; 
  12.  
  13. realPoint.y = y; 
  14.  
  15.  
  16. }//樣例來源于Robolectric官網(wǎng)  

上述實(shí)例中,@Implements是聲明Shadow的對(duì)象,@RealObject是獲取一個(gè)Android 對(duì)象,constructor則是該Shadow的構(gòu)造函數(shù),Shadow還可以修改一些函數(shù)的功能,只需要在重載該函數(shù)的時(shí)候添加@Implementation,這種方式可以有效擴(kuò)展Robolectric的功能。

Shadow是通過對(duì)真實(shí)的Android對(duì)象進(jìn)行函數(shù)重載、初始化等方式對(duì)Android對(duì)象進(jìn)行擴(kuò)展,Shadow出來的對(duì)象的功能接近Android對(duì)象,可以看成是對(duì)Android對(duì)象一種修復(fù)。自定義的Shadow需要在config中聲明,聲明寫法是@Config(shadows=ShadowPoint.class)。

Mock寫法介紹

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

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

所得到的list對(duì)象實(shí)例便是List類型的實(shí)例,如果不采用mock,List其實(shí)只是個(gè)接口,我們需要構(gòu)造或者借助ArrayList才能進(jìn)行實(shí)例化。與Shadow不同,Mock構(gòu)造的是一個(gè)虛擬的對(duì)象,用于解耦真實(shí)對(duì)象所需要的依賴。Mock得到的對(duì)象僅僅是具備測試對(duì)象的類型,并不是真實(shí)的對(duì)象,也就是并沒有執(zhí)行過真實(shí)對(duì)象的邏輯。

Mock也具備一些補(bǔ)充JUnit的驗(yàn)證函數(shù),比如設(shè)置函數(shù)的執(zhí)行結(jié)果,示例如下:

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

上述代碼為被測函數(shù)定義一個(gè)可替代真實(shí)函數(shù)的結(jié)果的返回值。當(dāng)使用這個(gè)函數(shù)后,這個(gè)可驗(yàn)證的結(jié)果便會(huì)產(chǎn)生影響,從而代替函數(shù)的真實(shí)結(jié)果,這樣便解除了對(duì)真實(shí)函數(shù)的依賴。

同時(shí)Mock框架也可以驗(yàn)證函數(shù)的執(zhí)行次數(shù),代碼如下:

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

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

  1. //代碼參考了參考文獻(xiàn)[3] 
  2.  
  3. public class MockClient implements Client { 
  4.  
  5. @Override 
  6.  
  7. public Response execute(Request request) throws IOException { 
  8.  
  9. Uri uri = Uri.parse(request.getUrl()); 
  10.  
  11. String responseString = ""
  12.  
  13. if(uri.getPath().equals("/path/of/interest")) { 
  14.  
  15. responseString = "返回的json1";//這里是設(shè)置返回值 
  16.  
  17. else { 
  18.  
  19. responseString = "返回的json2"
  20.  
  21.  
  22. return new Response(request.getUrl(), 200, "nothing", Collections.EMPTY_LIST, new TypedByteArray("application/json",responseString.getBytes())); 
  23.  
  24.  
  25.  
  26. //MockClient使用方式如下: 
  27.  
  28. RestAdapter.Builder builder = new RestAdapter.Builder(); 
  29.  
  30. builder.setClient(new MockClient());  

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

在實(shí)際項(xiàng)目中使用Robolectric構(gòu)建單元測試

單元測試的范圍

在Android項(xiàng)目中,單元測試的對(duì)象是組件狀態(tài)、控件行為、界面元素和自定義函數(shù)。本文并不推薦對(duì)每個(gè)函數(shù)進(jìn)行一對(duì)一的測試,像onStart()、onDestroy()這些周期函數(shù)并不需要全部覆蓋到。商業(yè)項(xiàng)目多采用Scrum模式,要求快速迭代,有時(shí)候未必有較多的時(shí)間寫單元測試,不再要求逐個(gè)函數(shù)寫單元測試。

本文單元測試的case多來源于一個(gè)簡短的業(yè)務(wù)邏輯,單元測試case需要對(duì)這段業(yè)務(wù)邏輯進(jìn)行驗(yàn)證。在驗(yàn)證的過程中,開發(fā)人員可以深度了解業(yè)務(wù)流程,同時(shí)新人來了看一下項(xiàng)目單元測試就知道哪個(gè)邏輯跑了多少函數(shù),需要注意哪些邊界——是的,單元測試需要像文檔一樣具備業(yè)務(wù)指導(dǎo)能力。

在大型項(xiàng)目中,遇到需要改動(dòng)基類中代碼的需求時(shí),往往不能準(zhǔn)確快速地知道改動(dòng)后的影響范圍,緊急時(shí)多采用創(chuàng)建子類覆蓋父類函數(shù)的辦法,但這不是長久之計(jì),在足夠覆蓋率的單元測試支持下,跑一下單元測試就知道某個(gè)函數(shù)改動(dòng)后的影響,可以放心地修改基類。

美團(tuán)的Android單元測試編寫流程如圖4所示。 

 

 

美團(tuán)Android單元測試編寫流程 

圖4 美團(tuán)Android單元測試編寫流程

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

單元測試的流程

實(shí)際項(xiàng)目中,單元測試對(duì)象與頁面是一對(duì)一的,并不建議跨頁面,這樣的單元測試藕合度太大,維護(hù)困難。單元測試需要找到頁面的入口,分析項(xiàng)目頁面中的元素、業(yè)務(wù)邏輯,這里的邏輯不僅僅包括界面元素的展示以及控件組件的行為,還包括代碼的處理邏輯。然后可以創(chuàng)建單元測試case列表(列表用于紀(jì)錄項(xiàng)目中單元測試的范圍,便于單元測試的管理以及新人了解業(yè)務(wù)流程),列表中記錄單元測試對(duì)象的頁面,對(duì)象中的case邏輯以及名稱等。工程師可以根據(jù)這個(gè)列表開始寫單元測試代碼。

單元測試是工程師代碼級(jí)別的質(zhì)量保證工程,上述流程并不能完全覆蓋重要的業(yè)務(wù)邏輯以及邊界條件,因此,需要寫完后,看覆蓋率,找出單元測試中沒有覆蓋到的函數(shù)分支條件等,然后繼續(xù)補(bǔ)充單元測試case列表,并在單元測試工程代碼中補(bǔ)上case。

直到規(guī)劃的頁面中所有邏輯的重要分支、邊界條件都被覆蓋,該項(xiàng)目的單元測試結(jié)束。單元測試流程如圖5所示。 

 

 

單元測試執(zhí)行流程 

圖5 單元測試執(zhí)行流程

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

單元測試項(xiàng)目實(shí)踐

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

 

 

單元測試case設(shè)計(jì) 

圖6 單元測試case設(shè)計(jì)

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

 

 

單元測試case列表 

 

 

 

單元測試case列表 

表1 單元測試case列表

接下來需要在單元測試工程中實(shí)現(xiàn)上述case,最小斷言數(shù)是業(yè)務(wù)邏輯上的判斷,并不是代碼的邊界條件,真實(shí)的case需要考慮代碼的邊界條件,比如數(shù)組為空等條件,因此,最終的斷言數(shù)量會(huì)大于等于最小斷言數(shù)。在需求業(yè)務(wù)上,最小斷言數(shù)也是該需求的業(yè)務(wù)條件。

寫完case后需要跑一遍單元測試并檢查覆蓋率報(bào)告,當(dāng)覆蓋率報(bào)告中缺少有些單元測試case列表中沒有但是實(shí)際邏輯中會(huì)有的邏輯時(shí),需要更新單元測試case列表,添加遺漏的邏輯,并將對(duì)應(yīng)的代碼補(bǔ)上。直到所有需要維護(hù)的邏輯都被覆蓋,該項(xiàng)目中的單元測試才算完成。單元測試并不是QA的黑盒測試,需要保證對(duì)代碼邏輯的覆蓋。

對(duì)表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)的,兩者并不會(huì)相互影響,可以直接調(diào)用以減少重復(fù)代碼。第二個(gè)頁面不同于第一個(gè),一進(jìn)入就需要網(wǎng)絡(luò)請(qǐng)求,后續(xù)業(yè)務(wù)都需要依賴這個(gè)網(wǎng)絡(luò)請(qǐng)求,單元測試不應(yīng)該對(duì)某一個(gè)條件過度耦合,因此,需要用mock解除耦合,直接mock出網(wǎng)絡(luò)請(qǐng)求得到的數(shù)據(jù),單獨(dú)驗(yàn)證頁面對(duì)數(shù)據(jù)的響應(yīng)。

總結(jié)

單元測試并不是一個(gè)能直接產(chǎn)生回報(bào)的工程,它的運(yùn)行以及覆蓋率也不能直接提升代碼質(zhì)量,但其帶來的代碼控制力能夠大幅度降低大規(guī)模協(xié)同開發(fā)的風(fēng)險(xiǎn)?,F(xiàn)在的商業(yè)App開發(fā)都是大型團(tuán)隊(duì)協(xié)作開發(fā),不斷會(huì)有新人加入,無論新人是剛?cè)胄械膽?yīng)屆生還是工作多年,在代碼存在一定業(yè)務(wù)耦合度的時(shí)候,修改代碼就有一定風(fēng)險(xiǎn),可能會(huì)影響之前比較隱蔽的業(yè)務(wù)邏輯,或者是丟失曾經(jīng)的補(bǔ)丁,如果有高覆蓋率的單元測試工程,就能很快定位到新增代碼對(duì)現(xiàn)有項(xiàng)目的影響,與QA驗(yàn)收不同,這種影響是代碼級(jí)的。

在本文所設(shè)計(jì)的單元測試流程中,單元測試的case和具體頁面的具體業(yè)務(wù)流程以及該業(yè)務(wù)的代碼邏輯緊密聯(lián)系,單元測試如同技術(shù)文檔一般,能夠體現(xiàn)出一個(gè)業(yè)務(wù)邏輯運(yùn)行了多少函數(shù),需要注意什么樣的條件。這是一種新人了解業(yè)務(wù)流程、對(duì)業(yè)務(wù)進(jìn)行代碼級(jí)別融入的好辦法,看一下以前的單元測試case,就能知道與該case對(duì)應(yīng)的那個(gè)頁面上的那個(gè)業(yè)務(wù)邏輯會(huì)執(zhí)行多少函數(shù),以及這些函數(shù)可能出現(xiàn)的結(jié)果。

參考文獻(xiàn)

[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 

責(zé)任編輯:龐桂玉 來源: 安卓開發(fā)精選
相關(guān)推薦

2017-01-14 23:42:49

單元測試框架軟件測試

2022-04-08 09:01:56

腳本Go應(yīng)用單元

2024-08-15 08:11:10

2012-10-29 09:45:52

單元測試軟件測試測試實(shí)踐

2009-09-01 10:20:06

protected方法單元測試

2010-01-28 15:54:19

Android單元測試

2024-10-07 09:12:33

2011-11-18 15:18:41

Junit單元測試Java

2011-06-01 15:49:00

Android 測試

2017-04-07 13:45:02

PHP單元測試數(shù)據(jù)庫測試

2017-01-14 23:26:17

單元測試JUnit測試

2017-01-16 12:12:29

單元測試JUnit

2020-08-18 08:10:02

單元測試Java

2022-08-05 09:30:57

單元測試C++

2010-02-07 15:42:46

Android單元測試

2017-03-23 16:02:10

Mock技術(shù)單元測試

2021-05-05 11:38:40

TestNGPowerMock單元測試

2023-07-26 08:58:45

Golang單元測試

2011-07-04 18:16:42

單元測試

2020-05-07 17:30:49

開發(fā)iOS技術(shù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)