Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android標准App的四大自動化測試

Android標准App的四大自動化測試

編輯:關於Android編程

WeTest導讀

說起Android的自動化測試,相信有很多小伙伴都接觸過或者有所耳聞,本文從框架最基本的功能介紹及API的使用入手,結合簡單的項目實戰來幫忙大家對該框架進一步理解和加深印象。下面讓我們來一睹標准App的四大自動化測試法寶的風采!

法寶1:穩定性測試利器——Monkey

要想發布一個新版本,得先通過穩定性測試。理想情況是找個上幼兒園的弟弟妹妹,打開應用把手機交給他,讓他胡亂的玩,看你的程序能不能接受這樣的折騰。但是我們身邊不可能都有正太和蘿莉,也不能保證他們拿到手機後不是測試軟件的健壯性,反而測試你的手機經不經摔,這與我們的期望差太遠了…
Google公司考慮到我們的需要,開發出了Monkey這個工具。但在很多人的印象中,Monkey測試就是讓設備隨機的亂點,事件都是隨機產生的,不帶任何人的主觀性。很少有人知道,其實Monkey也可以用來做簡單的自動化測試工作。
Mokey基本功能介紹
首先,介紹下Monkey的基本使用,如果要發送500個隨機事件,只需運行如下命令:
adb shell monkey 500

插上手機運行後,大家是不是發現手機開始瘋狂的運行起來了。So Easy!
在感受完Monkey的效果後,發現這“悟空”太調皮了,根本招架不住啊!是否有類似“緊箍咒”這種約束類命令,讓這只猴子在某個包或類中運行呢?要想Monkey牢牢的限制在某個包中,命令也很簡單:
adb shell monkey –p your-package-name 500

-p後面接你程序的包名。多想限制在多個包中,可以在命令行中添加多個包:
adb shell monkey –p your-package1-name –p your-package2-name 500

這樣“悟空”就飛不出你的五指山了。
Mokey編寫自動化測試腳本
若控制不住“悟空”,只讓它隨機亂點的話,Monkey是替代不了黑盒測試用例的。我們能不能想些辦法,控制住“悟空”讓他做些簡單的自動化測試的工作呢?下面來看一下,如何用Monkey來編寫腳本。
先簡單介紹下Monkey的API,若有需要詳細了解的小伙伴,可自行百度或谷歌一下查閱哈。
(1) 軌跡球事件:DispatchTrackball(參數1~參數12)
(2) 輸入字符串事件:DispatchString(String text)
(3) 點擊事件:DispatchPointer(參數1~參數12)
(4) 啟動應用:LaunchActivity(String pkg_name, String class_name)
(5) 等待事件:UserWait(long sleeptime)
(6) 按下鍵值:DispatchPress(int keyCode)
(7) 長按鍵值:LongPress(int keyCode)
(8) 發送鍵值:DispatchKey(參數1~參數8)
(9) 打開軟鍵盤:DispatchFlip(Boolean keyboardOpen)
了解完常用API後,我們來看一下Monkey腳本的編寫規范。Monkey Script是按照一定的語法規則編寫的有序的用戶事件流,使用於Monkey命令工具的腳本。Monkey腳本一般以如下4條語句開頭:

# Start Script
type = user    #指明腳本類型
count = 10     #腳本執行次數
speed = 1.0    #命令執行速率
start data >>  #用戶腳本入口,下面是用戶自己編寫的腳本

下面來看一個簡單應用的實戰,實現的效果很簡單,就是隨便輸入文本,選擇選項再進行提交,提交後要驗證提交後的效果。
這裡寫圖片描述
這裡寫圖片描述

# Start
Script
type = user
count = 10
speed = 1.0
start data >>LaunchActivity(com.ringo.bugben,com.ringo.bugben.MainActivity)
# 點擊文本框1
captureDispatchPointer(10,10,0,210,200,1,1,-1,1,1,0,0)
captureDispatchPointer(10,10,1,210,200,1,1,-1,1,1,0,0)
# 確定文本框1內容
captureDispatchString(Hello)
# 點擊文本框2
captureDispatchPointer(10,10,0,210,280,1,1,-1,1,1,0,0)
captureDispatchPointer(10,10,1,210,280,1,1,-1,1,1,0,0)
# 確定文本框2內容
captureDispatchString(Ringo)
# 點擊加粗
captureDispatchPointer(10,10,0,210,420,1,1,-1,1,1,0,0)
captureDispatchPointer(10,10,1,210,420,1,1,-1,1,1,0,0)
# 點擊大號
captureDispatchPointer(10,10,0,338,476,1,1,-1,1,1,0,0)
captureDispatchPointer(10,10,1,338,476,1,1,-1,1,1,0,0)
# 等待500毫秒
UserWait(500)
# 點擊提交
captureDispatchPointer(10,10,0,100,540,1,1,-1,1,1,0,0)
captureDispatchPointer(10,10,1,100,540,1,1,-1,1,1,0,0)

將上述代碼另存為HelloMonkey文件,然後將該腳本推送到手機的sd卡裡。

adb push HelloMonkey /mnt/sdcard/

然後運行:

adb shell monkey -v -f /mnt/sdcard/HelloMonkey 1

腳本後面的數字1表示運行該腳本的次數。小伙伴們可以安裝附件裡的Bugben.apk再執行下腳本感受下哦!

Monkey工具總結

Monkey可以編寫腳本做簡單的自動化測試,但局限性非常大,例如無法進行截屏操作,不能簡單的支持插件的編寫,沒有好的辦法控制事件流,不支持錄制回放等。我們在平時的使用中,關注較多的是利用好Monkey的優勢,如不需源碼,不需編譯就可以直接運行。

法寶2:Monkey之子——MonkeyRunner

Monkey雖然能實現部分的自動化測試任務,但本身有很多的局限性,例如不支持截屏,點擊事件是基於坐標的,不支持錄制回放等。我們在實際應用中,盡量關注利用好Monkey測試的優勢。若平時的工作中遇到Monkey工具無法滿足的,這裡給大家推薦另一款工具MonkeyRunner。
同樣先簡單的介紹下MonkeyRunner的API,這裡重點介紹能夠實現上文Monkey腳本的API,其余的API感興趣的小伙伴可以自行查閱。
(1) 等待設備連接:waitForConnection()
(2) 安裝apk應用:installPackage(String path)
(3) 啟動應用:startActivity(String packageName+activityName)
(4) 點擊事件:touch(int xPos, int yPos, dictionary type)
(5) 輸入事件:type(String text)
(6) 等待:sleep(int second)
(7) 截圖:takeSnapshot()
(8) 發送鍵值:press(String name, dictionary type)

MokeyRunner編寫自動化測試腳本

下面我們來看下,用MonkeyRunner實現的自動化腳本。


# import monkeyrunner modules
from com.android.monkeyrunner import MonkeyRunner, MonkeyDevice, MonkeyImage
# Parameters
txt1_x = 210
txt1_y = 200
txt2_x = 210
txt2_y = 280
txt3_x = 210
txt3_y = 420
txt4_x = 338
txt4_y = 476
submit_x = 100
submit_y = 540
type = 'DOWN_AND_UP'
seconds = 1
txt1_msg = 'Hello'
txt2_msg = 'MonkeyRunner' 
# package name and activity name
package = 'com.ringo.bugben'
activity = '.MainActivity'
component = package + '/'+activity 
# Connect device
device = MonkeyRunner.waitForConnection() 
# Install bugben
device.installPackage('./bugben.apk')
print 'Install bugben.apk...' 
# Launch bugbendevice.startActivity(component)
print 'Launching bugben...' 
# Wait 1s
MonkeyRunner.sleep(seconds)
# Input txt1
device.touch(txt1_x, txt1_y, type)device.type(txt1_msg)
print 'Inputing txt1...' 
# Input txt2
device.touch(txt2_x, txt2_y, type)
device.type(txt2_msg)
print 'Inputing txt2...' 
#select bold and size
device.touch(txt3_x, txt3_y, type)
device.touch(txt4_x, txt4_y, type) 
# Wait 1s
MonkeyRunner.sleep(seconds) 
# Submitdevice.touch(submit_x, submit_y, type)
print 'Submiting...' 
# Wait 1s
MonkeyRunner.sleep(seconds) 
# Get the snapshot
picture = device.takeSnapshot()
picture.writeToFile('./HelloMonkeyRunner.png','png')
print 'Complete! See bugben_pic.png in currrent folder!' 
# Back to home
device.press('KEYCODE_HOME', type)
print 'back to home.'

將腳本保存為HelloMonkeyRunner.py,並和Bugben.apk一起拷貝到Android SDK的tools目錄下,執行monkeyrunner HelloMonkeyRunner.py
這裡寫圖片描述

執行完成後,效果如上,並且會在當前目錄生成HelloMonkeyRunner.png截圖。
MokeyRunner的錄制回放
首先是環境配置,在源碼“~\sdk\monkeyrunner\scripts”目錄下有monkey_recZ喎?/kf/ware/vc/" target="_blank" class="keylink">vcmRlci5webrNbW9ua2V5X3BsYXliYWNrLnB5o6y9q9Xiwb249s7EvP4ouL28/tbQ09DV4sG9zsS8/im/vbG0tb1TREu1xHRvb2xzxL/CvM/Co6y+zb/J0tTNqLn9yOfPwrT6wuu9+NDQxvS2r6O6PC9jb2RlPjwvY29kZT48L2NvZGU+PC9wPg0KPHByZSBjbGFzcz0="brush:java;"> monkeyrunner monkey_recorder.py

運行結果如下圖所示:
這裡寫圖片描述
下面用MonkeyRecorder提供的控件,來進行腳本的錄制。
這裡寫圖片描述
錄制完成後,導出腳本保存為HelloMonkeyRunnerRecorder.mr,用文本編輯器打開代碼如下:

TOUCH|{'x':317,'y':242,'type':'downAndUp',}
TYPE|{'message':'Hello',}TOUCH|{'x':283,'y':304,'type':'downAndUp',}
TYPE|{'message':'MonkeyRecorder',}
TOUCH|{'x':249,'y':488,'type':'downAndUp',}
TOUCH|{'x':375,'y':544,'type':'downAndUp',}
TOUCH|{'x':364,'y':626,'type':'downAndUp',}

腳本錄制完畢,接來下看看回放腳本是否正常。回放腳本時執行以下命令:
monkeyrunner monkey_playback your_script.mr

由於腳本中未加入拉起應用的代碼,這裡運行前需手動拉起應用。
這裡寫圖片描述
]![這裡寫圖片描述
結果運行正常,符合我們的預期。

MonkeyRunner工具總結

MonkeyRunner有很多強大並好用的API,並且支持錄制回放和截圖操作。同樣它也不需源碼,不需編譯就可以直接運行。但MonkeyRunner和Monkey類似,也是基於控件坐標進行定位的,這樣的定位方式極易導致回放失敗。

法寶3:單元測試框架——Instrumentation

Monkey父子均可通過編寫相應的腳本,在不依賴源碼的前提下完成部分自動化測試的工作。但它們都是依靠控件坐標進行定位的,在實際項目中,控件坐標往往是最不穩定的,隨時都有可能因為程序員對控件位置的調整而導致腳本運行失敗。怎樣可以不依賴坐標來進行應用的自動化測試呢?下面就要亮出自動化測試的屠龍寶刀了——Instrumentation框架。
Instrumentation框架主要是依靠控件的ID來進行定位的,擁有成熟的用例管理系統,是Android主推的白盒測試框架。若想對項目進行深入的、系統的單元測試,基本上都離不開Instrumentation這把屠龍寶刀。
在了解Instrumentation框架之前,先對Android組件生命周期對應的回調函數做個說明:
這裡寫圖片描述
從上圖可以看出,Activity處於不同狀態時,將調用不同的回調函數。但Android API不提供直接調用這些回調函數的方法,在Instrumentation中則可以這樣做。Instrumentation類通過“hooks”控制著Android組件的正常生命周期,同時控制Android系統加載應用程序。通過Instrumentation類我們可以在測試代碼中調用這些回調函數,就像在調試該控件一樣一步一步地進入到該控件的整個生命周期中。
Instrumentation和Activity有點類似,只不過Activity是需要一個界面的,而Instrumentation並不是這樣的,我們可以將它理解為一種沒有圖形界面的,具有啟動能力的,用於監控其他類(用Target Package聲明)的工具類。
下面通過一個簡單的例子來講解Instrumentation的基本測試方法。
1. 首先建立項目名為HelloBugben的Project,類名為HelloBugbenActivity,代碼如下:

package com.example.hellobugben;
import android.app.Activity;
import android.os.Bundle;
import android.text.TextPaint;
import android.view.Menu;
import android.widget.TextView; 
public class HelloBugbenActivity extends Activity{         
private TextView textview1;         
private TextView textview2;        
@Override        
protectedvoidonCreate(Bundle savedInstanceState){         
super.onCreate(savedInstanceState);           
setContentView(R.layout.main);                    
String bugben_txt = "bugben";          
Boolean bugben_bold = true;         
Float bugben_size = (float)60.0;         
textview1 = (TextView)findViewById(R.id.textView1);                
textview2 = (TextView)findViewById(R.id.textView2);                
setTxt(bugben_txt);                
setTv1Bold(bugben_bold);                
setTv2Size(bugben_size);     
 }              
publicvoidsetTv2Size(Float bugben_size){             
// TODO Auto-generated method stub            
TextPaint tp = textview2.getPaint();                
tp.setTextSize(bugben_size);      
 }              
publicvoidsetTv1Bold(Boolean bugben_bold){               
// TODO Auto-generated method stub             
TextPaint tpPaint = textview1.getPaint();                          
tpPaint.setFakeBoldText(bugben_bold);       
}            
publicvoidsetTxt(String bugben_txt){              
// TODO Auto-generated method stub              
textview1.setText(bugben_txt);            
 textview2.setText(bugben_txt);     
      }
}

這個程序的功能很簡單,就是給2個TextView的內容設置不同的文本格式。
2. 對於測試工程師而言,HelloBugben是一個已完成的項目。接下來需創建一個測試項目,選擇“New->Other->Android Test Project”,命名為HelloBugbenTest,選擇要測試的目標項目為HelloBugben項目,然後點擊Finish即可完成測試項目的創建。
這裡寫圖片描述
這裡寫圖片描述
可以注意到,該項目的包名自帶了com.example.hellobugben.test這個test標簽,這就說明該測試項目是針對HelloBugben所設置的。
打開AndroidManifest可看到標簽,該標簽元素用來指定要測試的應用程序,自動將com.example.hellobugben設為targetPackage對象,代碼清單如下:

在標簽中,android:name聲明了測試框架,android:targetPackage指定了待測項目包名。
下面來看一下,如何用Instrumentation框架編寫測試程序,代碼如下:

package com.example.hellobugben.test;
import com.example.hellobugben.HelloBugbenActivity;
import com.example.hellobugben.R; 
import android.os.Handler;
import android.text.TextPaint;
import android.widget.TextView;
import android.test.ActivityInstrumentationTestCase2;
public classHelloBugbenTestBaseextendsActivityInstrumentationTestCase2{              
public HelloBugbenTestBase() {         
super(HelloBugbenActivity.class);  
 }               
HelloBugbenActivity helloBugben;    
private Handler handler = null;       
private TextView textView1;     
private TextView textView2;          
String bugben_txt = "bugben";      
Boolean bugben_bold = true;      
Float bugben_sizeFloat = (float)20.0;      
Float value;          
@Override      
public void setUp() throws Exception{               
super.setUp();           
helloBugben = getActivity();            
textView1 = (TextView)helloBugben.findViewById(R.id.textView1);             
textView2 = (TextView)helloBugben.findViewById(R.id.textView2);             
handler = new Handler();    }            
@Override      
public voidtearDown()throws Exception{              
super.tearDown();      }              
 public void testSetTxt(){         
new Thread(){           
public voidrun(){               
 if (handler != null) {                                                
handler.post(runnableTxt);                           
            }     
        }              
   }.start();          
String cmpTxtString = textView1.getText().toString();              
assertTrue(cmpTxtString.compareToIgnoreCase(bugben_txt) == 0);     
  }              
public void testSetBold(){           
helloBugben.setTv1Bold(bugben_bold);         
TextPaint tp = textView1.getPaint();          
Boolean cmpBold = tp.isFakeBoldText();                             
assertTrue(cmpBold);      
 }                  
publicvoidtestSetSize(){             
 helloBugben.setTv2Size(bugben_sizeFloat);             
Float cmpSizeFloat = textView2.getTextSize();                      
assertTrue(cmpSizeFloat.compareTo(bugben_sizeFloat) == 0);    
   }                 
Runnable runnableTxt = new Runnable() {                               
@Override           
publicvoidrun(){                
// TODO Auto-generated method stub            
helloBugben.setTxt(bugben_txt);        
   }    
     };
}

上述代碼中,我們首先引入import android.test.ActivityInstrumentationTestCase2。其次讓HelloBugbenTestBase繼承自ActivityInstrumentationTestCase2這個類。接著在setUp()方法中通過getActivity()方法獲取待測項目的實例,並通過textview1和textview2獲取兩個TextView控件。最後編寫3個測試用例:控制文本設置測試testSetText()、字體加粗屬性測試testSetBold、字體大小屬性測試testSetSize()。這裡用到的關鍵方法是Instrumentation API裡面的getActivity()方法,待測的Activity在沒有調用此方法的時候是不會啟動的。
眼尖的小伙伴可能已經發現控制文本設置測試這裡啟用了一個新線程,這是因為在Android中相關的view和控件不是線程安全的,必須單獨在新的線程中做處理,不然會報

android.view.ViewRootImpl$CalledFromWrongThreadException:
Only the original thread that created a view hierarchy can touch its views

這個錯誤。所以需要啟動新線程進行處理,具體步驟如下:
1) 在setUp()方法中創建Handler對象,代碼如下:

public void setUp() throws Exception{           
 super.setUp();            
 handler = new Handler(); 
   }

2) 創建Runnable對象,在Runnable中進行控件文本設置,代碼如下:

 Runnable runnableTxt = new Runnable() {                        
@Override         
public void run(){                  
// TODO Auto-generated method stub                                 
helloBugben.setTxt(bugben_txt);     
  }  
    };

3) 在具體測試方法中通過調用runnable對象,實現文本設置,代碼如下:

 new Thread(){    
public void run() {                                  
if (handler != null) {                                                    
handler.post(runnableTxt);                   
         }                                  
    }            
}.start(); 

我們運行一下結果,結果截圖如下:
這裡寫圖片描述

可以看到3個測試用例結果運行正常。
可能有小伙伴要問,程序中為啥要繼承ActivityInstrumentationTestCase2呢?我們先看一下ActivityInstrumentationTestCase2的繼承結構:
java.lang.Object
junit.framework.Assert
junit.framework.TestCase
android.test.InstrumentationTestCase
android.test.ActivityTestCase
android.test.ActivityInstrumentationTestCase2

ActivityInstrumentationTestCase2允許InstrumentationTestCase. launchActivity來啟動被測試的Activity。而且ActivityInstrumentationTestCase2還支持在新的UI線程中運行測試方法,能注入Intent對象到被測試的Activity中,這樣一來,我們就能直接操作被測試的Activity了。正因為ActivityInstrumentationTestCase2有如此出眾的有點,它才成功取代了比它早出世的哥哥:ActivityInstrumentationTestCase,成為了Instrumentation測試的基礎。

Instrumentation測試框架實戰

了解完Instrumentation的基本測試方法後,我們來看一下如何運用Instrumentation框架完成前文Monkey父子完成的自動化測試任務。
這裡寫圖片描述
這裡寫圖片描述

首先建立項目名為Bugben的Project,類名為MainActivity,代碼如下:
package com.ringo.bugben;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RadioButton; 
public classMainActivityextendsActivity{       
private EditText editText1 = null;         
private EditText editText2 = null;       
private RadioButton bold = null;       
private RadioButton  small = null;      
private Button button = null;        
@Override    
protected void onCreate(Bundle savedInstanceState){           
super.onCreate(savedInstanceState);            
setContentView(R.layout.main);            
editText1 = (EditText)findViewById(R.id.editText1);         
editText2 = (EditText)findViewById(R.id.editText2);        
button = (Button)findViewById(R.id.mybutton1);         
bold = (RadioButton)findViewById(R.id.radioButton1);               
small = (RadioButton)findViewById(R.id.radioButton3);                     
button.setOnClickListener(new OnClickListener(){                       
@Override                      
publicvoidonClick(View v){                
Log.v("Ringo", "Press Button");                                    
String isBold = bold.isChecked() ? "bold" : "notbold";            
 String wordSize = small.isChecked() ? "small" : "big";             
// TODO Auto-generated method stub                                 
Intent intent = new Intent(MainActivity.this, OtherActivity.class);                                 
intent.putExtra("text1", editText1.getText().toString());          
intent.putExtra("text2", editText2.getText().toString());          
intent.putExtra("isBold", isBold);                                 
intent.putExtra("wordSize", wordSize);                             
startActivity(intent);        
     }           
  });   
 }
}
在建立一個名為OtherActivity的類,點擊提交按鈕後,跳轉到這個界面,代碼如下:
package com.ringo.bugben;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.text.TextPaint;
import android.widget.TextView; 
public classOtherActivityextendsActivity{       
private TextView textView2 = null;        
private TextView textView3 = null;         
Boolean bugben_bold = true;      
Boolean bugben_notbold = false;         
Float bugben_small_size = (float)20.0;      
Float bugben_big_size = (float)60.0;          
@Override   protectedvoidonCreate(Bundle savedInstanceState){     
super.onCreate(savedInstanceState);        
setContentView(R.layout.other);         
textView2 = (TextView)findViewById(R.id.textView2);    
textView3 = (TextView)findViewById(R.id.textView3);                
Intent data = getIntent();       
textView2.setText(data.getStringExtra("text1"));     
textView3.setText(data.getStringExtra("text2"));      
if (data.getStringExtra("isBold").equalsIgnoreCase("bold")) {                
TextPaint tPaint = textView2.getPaint();                           
tPaint.setFakeBoldText(bugben_bold);            
}else{             
TextPaint tPaint = textView2.getPaint();                           tPaint.setFakeBoldText(bugben_notbold);          
   }           
 if (data.getStringExtra("wordSize").equalsIgnoreCase("small")) {       
TextPaint tPaint = textView3.getPaint();                     
tPaint.setTextSize(bugben_small_size);         
}else{                   
TextPaint tPaint = textView3.getPaint();                          
tPaint.setTextSize(bugben_big_size);        
   }    
 }
}

3.接下來需創建一個測試項目,命名為BugbenTestBase,選擇要測試的目標項目為Bugben項目,然後點擊Finish即可完成測試項目的創建。
這裡寫圖片描述
這裡寫圖片描述
在com.ringo.bugben.test包中添加BugbenTestBase這個類,類的代碼如下:

package com.ringo.bugben.test;
import com.ringo.bugben.MainActivity;
import com.ringo.bugben.OtherActivity;
import com.ringo.bugben.R;
import android.app.Instrumentation.ActivityMonitor;
import android.content.Intent;
import android.os.SystemClock;
import android.test.ActivityInstrumentationTestCase2;
import android.text.TextPaint;
import android.util.Log;
import android.widget.Button;import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.TextView;
public class BugbenTestBase extends ActivityInstrumentationTestCase2{        
publicBugbenTestBase(){            
super(MainActivity.class);     
  }             
MainActivity mainActivity;   
OtherActivity otherActivity;        
private EditText txt1;     
private EditText txt2;      
private RadioButton bold;      
private RadioButton notbold;     
private RadioButton small;      
private RadioButton big;     
private Button subButton;     
private TextView textView1;     
private TextView textView2;      
// 輸入值      
String bugben_txt1 = "RingoYan";    
String bugben_txt2 = "自動化測試";    
Boolean bugben_bold = true;       
Boolean bugben_notbold = false;      
Float bugben_small_size = (float)20.0;     
Float bugben_big_size = (float)60.0;        
@Override      
public void setUp() throws Exception{                
super.setUp();                           
// 啟動MainActivity              
Intent intent = new Intent();           
intent.setClassName("com.ringo.bugben", MainActivity.class.getName());                 
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);             
mainActivity = (MainActivity)getInstrumentation().startActivitySync(intent);             
// 通過mainActivity的findViewById獲取MainActivity界面的控件                      
txt1 = (EditText)mainActivity.findViewById(R.id.editText1);                      
txt2 = (EditText)mainActivity.findViewById(R.id.editText2);                    
bold = (RadioButton)mainActivity.findViewById(R.id.radioButton1);                    
notbold = (RadioButton)mainActivity.findViewById(R.id.radioButton2);               
small = (RadioButton)mainActivity.findViewById(R.id.radioButton3);                     
big = (RadioButton)mainActivity.findViewById(R.id.radioButton4);                     
subButton = (Button)mainActivity.findViewById(R.id.mybutton1);    
   }                      
@Override         
publicvoidtearDown()throws Exception{              
super.tearDown();       
   }                  
// 提交測試     
public void testSubmit()throws Throwable{            
Log.v("Ringo", "test normal submit");              
// 添加一個監聽器,監視OtherActivity的啟動                    
ActivityMonitor bugbenMonitor = getInstrumentation().addMonitor(                    
OtherActivity.class.getName(), null, false);             
// 要操作待測程序的UI必須在runTestOnUiThread中執行             
runTestOnUiThread(new Runnable() {                              
@Override                         
publicvoidrun(){                 
// TODO Auto-generated method stub                              
txt1.setText(bugben_txt1);                                  
txt2.setText(bugben_txt2);                                  
bold.setChecked(true);                                      
big.setChecked(true);                                                                
// 等待500毫秒,避免程序響應慢出錯                             
SystemClock.sleep(500);                                                                  
// 點擊提交按鈕                  
subButton.performClick();          
      }              
  });                           
// 從ActivityMonitor監視器中獲取OtherActivity的實例           
otherActivity = (OtherActivity)getInstrumentation().waitForMonitor(bugbenMonitor);                
// 獲取的OtherActivity實例應不為空         
assertTrue(otherActivity != null);                          
textView1 = (TextView)otherActivity.findViewById(R.id.textView2);                  
textView2 = (TextView)otherActivity.findViewById(R.id.textView3);         
assertEquals(bugben_txt1, textView1.getText().toString());                 
assertEquals(bugben_txt2, textView2.getText().toString());                            
TextPaint tp = textView1.getPaint();        
Boolean cmpBold = tp.isFakeBoldText();          
assertTrue(cmpBold);            
Float cmpSize = textView2.getTextSize();         
assertTrue(cmpSize.compareTo(bugben_big_size) == 0);             
// 等待500毫秒,避免程序響應慢出錯                
SystemClock.sleep(5000);   
      }
}

上述代碼中,共包括自動化測試需要進行的5個步驟,具體如下:
(1) 啟動應用:通過Intent對象setClassName()方法設置包名和類名,通過setFlags()方法設置標示,然後通過getInstrumentation()的startActivitySync(intent)來啟動應用,進入到主界面。
(2) 編輯控件:在Android中相關的view和控件不是線程安全的,所以必須單獨在新的線程中做處理。代碼中我們在runTestOnUiThread(new Runnable())中的run()方法中執行的。
(3) 提交結果:點擊提交按鈕進行結果的提交,由於點擊按鈕也屬於界面操作,所以也需要在runTestOnUiThread這個線程中完成。
(4) 界面跳轉:這是Instrumentation自動化測試中最需要注意的一個點,特別是如何確認界面已經發生了跳轉。在Instrumentation中可以通過設置Monitor監視器來確認。代碼如下:

ActivityMonitor bugbenMonitor = getInstrumentation().addMonitor(        
OtherActivity.class.getName(), null, false);

然後通過waitForMonitor方法等待界面跳轉。

otherActivity = (OtherActivity)getInstrumentation().waitForMonitor(bugbenMonitor);

若返回結果otherActivity對象不為空,說明跳轉正常。
(5) 驗證顯示:跳轉後,通過assertEquals()或assertTrue()方法來判斷顯示的正確性。
我們運行一下結果,結果截圖如下:
這裡寫圖片描述
]![這裡寫圖片描述
這裡寫圖片描述

Instrumentation工具總結

Instrumentation框架的整體運行流程圖如下:
這裡寫圖片描述
Instrumentation是基於源碼進行腳本開發的,測試的穩定性好,可移植性高。正因為它是基於源碼的,所以需要腳本開發人員對Java語言、Android框架運行機制、Eclipse開發工具都非常熟悉。Instrumentation框架本身不支持多應用的交互,例如測試“通過短信中的號碼去撥打電話”這個用例,被測應用將從短信應用界面跳轉到撥號應用界面,但Instrumentation沒有辦法同事控制短信和撥號兩個應用,這是因為Android系統自身的安全性限制,禁止多應用的進程間相互訪問。

法寶4:終極自動化測試框架——UIAutomator

鑒於Instrumentation框架需要讀懂項目源碼、腳本開發難度較高並且不支持多應用交互,Android官網亮出了自動化測試的王牌——UIAutomator,並主推這個自動化測試框架。該框架無需項目源碼,腳本開發效率高且難度低,並且支持多應用的交互。當UIAutomator面世後,Instrumentation框架回歸到了其單元測試框架的本來位置。
下面我們來看一下這個框架是如何運行起來的。首先運行位於Android SDK的tools目錄下的uiautomatorviewer.bat,可以看到啟動界面。
這裡寫圖片描述

啟動bugben應用後,點擊這裡寫圖片描述
這個圖標來采集手機的界面信息,如下所示:
這裡寫圖片描述
我們可以看到,用uiautomatorviewer捕捉到的控件非常清晰,很方便元素位置的定位。在UIAutomator框架中,測試程序與待測程序之間是松耦合關系,即完全不需要獲取待測程序的控件ID,只需對控件的文本(text)、描述(content-desc)等信息進行識別即可。
在進行實戰之前,我們先看一下UIAutomator的API部分,由以下架構圖組成。
這裡寫圖片描述
下面來看下如何利用該框架創建測試工程。
1. 創建BugBenTestUIAuto項目,右鍵點擊項目並選擇Properties > Java Build Path
點擊Add Library > Junit > Junit3,添加Junit框架。

點擊Add External Jar,並導航到Android SDK目錄下,選擇platforms目錄下面的android.jar和UIAutomator.jar兩個文件。

這裡寫圖片描述
2. 設置完成後,可以開始編寫項目測試的代碼,具體如下:

package com.ringo.bugben.test;
import java.io.File;import android.util.Log;
import com.android.uiautomator.core.UiDevice;
import com.android.uiautomator.core.UiObject;
import com.android.uiautomator.core.UiObjectNotFoundException;
import com.android.uiautomator.core.UiSelector;
import com.android.uiautomator.testrunner.UiAutomatorTestCase;;
public class BugBenTest extends UiAutomatorTestCase{   
public BugBenTest (){      super();  }   
String bugben_txt1 = "xiaopangzhu";  
String bugben_txt2 = "bugben";  
String storePath = "/data/local/tmp/displayCheck.png";  
String testCmp = "com.ringo.bugben/.MainActivity";   
@Override  
public void setUp ()throws Exception{      
super.setUp();      
// 啟動MainActivity       
startApp(testCmp);  }   
private int startApp(String componentName){      
StringBuffer sBuffer = new StringBuffer();      
sBuffer.append("am start -n ");      
sBuffer.append(componentName);      
int ret = -1;     
try {        
Process process = Runtime.getRuntime().exec(sBuffer.toString());        
ret = process.waitFor();      
} 
catch (Exception e) {        
// TODO: handle exception         
e.printStackTrace();    
  }      
return ret; 
 }   
@Override   
public void tearDown()throws Exception{      
super.tearDown();  
}   
// 提交文字測試   
public void testSubmitTest() throws UiObjectNotFoundException{Log.v  ("Ringo", "test change the textview's txt and size by UIAutomator");   
// 獲取文本框1並賦值   
UiObject bugben_et1 = new UiObject(new UiSelector().text("Ringoyan"));  
if(bugben_et1.exists() && bugben_et1.isEnabled()){     
bugben_et1.click();     
bugben_et1.setText(bugben_txt1);  
}else{     
Log.e("Ringo", "can not find bugben_et1");  }  
// 獲取文本框2並賦值   
UiObject bugben_et2 = new UiObject(new UiSelector().text("18888"));  
if(bugben_et2.exists() && bugben_et2.isEnabled()){     
bugben_et2.click();     
bugben_et2.setText(bugben_txt2);  
}else{     
Log.e("Ringo", "can not find bugben_et2");  
}   
// 獲取加粗選項並賦值   UiObject bugben_bold = new UiObject(new UiSelector().text("加粗"));  
if(bugben_bold.exists() && bugben_bold.isEnabled()){     
bugben_bold.click();  
}else{     
Log.e("Ringo", "can not find 加粗");  
}   
// 獲取大號字體選項並賦值   
UiObject bugben_big = new UiObject(new UiSelector().text("大號"));  
if(bugben_big.exists() && bugben_big.isEnabled()){     
bugben_big.click();  
}else{     
Log.e("Ringo", "can not find 大號");  }       
// 獲取提交按鈕並跳轉   
UiObject subButton = new UiObject(new UiSelector().text("提交"));  
if(subButton.exists() && subButton.isEnabled()){    
subButton.clickAndWaitForNewWindow();  
}else{    
Log.e("Ringo", "can not find 提交");}     
// 獲取文本框1文本   
UiObject bugben_tv1 = new UiObject(new UiSelector()   
.className("android.widget.LinearLayout")   
.index(0)
.childSelector(new UiSelector()   
.className("android.widget.FrameLayout")   
.index(1))   
.childSelector(new UiSelector()  
.className("android.widget.TextView")   
.instance(0)));      
// 獲取文本框2文本    
UiObject bugben_tv2 = new UiObject(new UiSelector()   
.className("android.widget.LinearLayout")   
.index(0).childSelector(new UiSelector()   
.className("android.widget.FrameLayout")   
.index(1))   
.childSelector(new UiSelector()   
.className("android.widget.TextView")   
.instance(1)));      
// 驗證    
if (bugben_tv1.exists() && bugben_tv1.isEnabled()) {       
assertEquals(bugben_txt1, bugben_tv1.getText().toString());   
}else{       
Log.e("Ringo", "can not find bugben_tv1"); 
  }   
if (bugben_tv2.exists() && bugben_tv2.isEnabled()) {       
assertEquals(bugben_txt2, bugben_tv2.getText().toString());   
}else{       
Log.e("Ringo", "can not find bugben_tv2"); 
  }   
// 截圖    
File displayPicFile = new File(storePath);   
Boolean displayCap = UiDevice.getInstance().takeScreenshot(displayPicFile);   
assertTrue(displayCap);  
 }
}

上述代碼中,我們首先引入import com.android.uiautomator.testrunner.UiAutomatorTestCase類,並讓BugbenTest繼承自UiAutomatorTestCase這個類。同樣,我們來看下UiAutomator框架下自動化測試進行的5個步驟,具體如下:
(1) 啟動應用:於Instrumentation框架不同,UiAutomator是通過命令行進行應用啟動的。
am start –n 包名/.應用名

(2) 編輯控件:UiAutomator框架中,控件的編輯相對簡單,直接通過UiSelector的text()方法找到對應的控件,然後調用控件的setText()即可對其賦值。

UiObject bugben_et1 = new UiObject(new UiSelector().text("Ringoyan"));           
if(bugben_et1.exists() && bugben_et1.isEnabled()){              
bugben_et1.click();               
bugben_et1.setText(bugben_txt1);    
   }

(3) 提交結果:點擊提交按鈕進行結果的提交,也是通過UiSelector的text()方法找到對應的控件,然後調用clickAndWaitForNewWindow()方法來等待跳轉完成。

UiObject subButton = new UiObject(new UiSelector().text("提交"));                 
if(subButton.exists() && subButton.isEnabled()){                
subButton.clickAndWaitForNewWindow();            
  }

(4) 界面跳轉元素獲取:用uiautomatorviewer捕捉跳轉後的控件,例如捕捉跳轉後的文本1:
這裡寫圖片描述

UiObject bugben_tv1 = new 
UiObject(new UiSelector()  
.className("android.widget.LinearLayout")  
.index(0)  
.childSelector(new UiSelector()  
.className("android.widget.FrameLayout")  
.index(1))  
.childSelector(new UiSelector()  
.className("android.widget.TextView")  
.instance(0)));

(5) 驗證顯示:跳轉後,通過assertEquals()或assertTrue()方法來判斷顯示的正確性。

if (bugben_tv1.exists() && bugben_tv1.isEnabled())    
{assertEquals(bugben_txt1, bugben_tv1.getText().toString());}

至此核心代碼部分已編寫完畢。UIAutomator有一個麻煩之處:沒法通過Eclipse直接編譯。可以借助於一系列命令行進行編譯,詳細步驟如下:
1) 通過如下命令創建編譯的build.xml文件

android create uitest-project –n BugBenTestUIAuto –t 1 –p "E:\workspace\BugBenTestUIAuto"

創建完成後,刷新BugBenTestUIAuto項目,得到如下圖:
這裡寫圖片描述
這裡寫圖片描述
打開build.xml會看到,編譯項目名為BugBenTestUIAuto。
2) 設置SDK的路徑:

set ANDROID_HOME="E:\sdk\android-sdk-windows"

3) 進入測試目錄,然後進行編譯:

cd /d E:\workspace\android\BugBenTestUIAutoant build

編譯完成後,再次刷新項目,你將看到BugBenTestUIAuto.jar包生成在bin目錄下了,如圖:
這裡寫圖片描述
4) 將生成的jar包推送到手機端

adb push E:\workspace\android\BugBenTestUIAuto\bin\BugBenTestUIAuto.jar /data/local/tmp/

5) 在手機端運行自動化腳本,即jar包中的測試用例,命令行如下:

adb shell uiautomator runtest BugBenTestUIAuto.jar -c com.ringo.bugben.test.BugBenTest

運行結果如下,返回OK表示運行成功。
這裡寫圖片描述
6) 最後,將運行後的截圖從手機端拷貝到PC上

adb pull /data/local/tmp/displayCheck.png E:\workspace\android\BugBenTestUIAuto

這裡寫圖片描述
至此整個代碼就編譯和運行完畢,如果覺得調試時反復修改和編譯比較麻煩,可以將以上腳本寫成一個批處理文件。
UIAutomator工具總結
相比於Instrumentation工具,UIAutomator工具更靈活一些,它不需要項目源碼,擁有可視化的界面和可視化的樹狀層級列表,極大降低了自動化測試腳本開發的門檻。並且UIAutomator支持多應用的交互,彌補了Instrumentation工具的不足。但UIAutomator難以捕捉到控件的顏色、字體粗細、字號等信息,要驗證該類信息的話,需要通過截圖的方式進行半自動驗證。同時,UIAutomator的調試相比Instrumentation要困難。所以在平時的測試過程中,建議將兩者結合起來使用,可達到更佳的效果!

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved