手把手教你Android標(biāo)準(zhǔn)App的四大自動(dòng)化測(cè)試法寶

作者:Ringoyan温技,騰訊測(cè)試開發(fā)工程師上真。先后為植物大戰(zhàn)僵尸Online祭埂,糖果傳奇等游戲擔(dān)任測(cè)試經(jīng)理绞蹦,其負(fù)責(zé)的“我叫MT2”測(cè)試項(xiàng)目曾獲騰訊互動(dòng)娛樂精品文化獎(jiǎng)銀獎(jiǎng)。目前擔(dān)任騰訊WeTest測(cè)試經(jīng)理嘀倒。擅長領(lǐng)域:App的自動(dòng)化測(cè)試和Web的安全測(cè)試工作。

注:核心內(nèi)容轉(zhuǎn)自許奔的《深入理解Android自動(dòng)化測(cè)試》,本書將在許奔公眾號(hào)“巴哥奔”中全文連載废恋。

商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系騰訊WeTest獲得授權(quán)谈秫,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。

WeTest導(dǎo)讀

說起Android的自動(dòng)化測(cè)試鱼鼓,相信有很多小伙伴都接觸過或者有所耳聞拟烫,本文從框架最基本的功能介紹及API的使用入手,結(jié)合簡單的項(xiàng)目實(shí)戰(zhàn)來幫忙大家對(duì)該框架進(jìn)一步理解和加深印象迄本。下面讓我們來一睹標(biāo)準(zhǔn)App的四大自動(dòng)化測(cè)試法寶的風(fēng)采硕淑!

法寶1:穩(wěn)定性測(cè)試?yán)鳌狹onkey

要想發(fā)布一個(gè)新版本,得先通過穩(wěn)定性測(cè)試嘉赎。理想情況是找個(gè)上幼兒園的弟弟妹妹置媳,打開應(yīng)用把手機(jī)交給他,讓他胡亂的玩公条,看你的程序能不能接受這樣的折騰拇囊。但是我們身邊不可能都有正太和蘿莉,也不能保證他們拿到手機(jī)后不是測(cè)試軟件的健壯性靶橱,反而測(cè)試你的手機(jī)經(jīng)不經(jīng)摔寥袭,這與我們的期望差太遠(yuǎn)了…
Google公司考慮到我們的需要,開發(fā)出了Monkey這個(gè)工具关霸。但在很多人的印象中传黄,Monkey測(cè)試就是讓設(shè)備隨機(jī)的亂點(diǎn),事件都是隨機(jī)產(chǎn)生的队寇,不帶任何人的主觀性膘掰。很少有人知道,其實(shí)Monkey也可以用來做簡單的自動(dòng)化測(cè)試工作佳遣。
Mokey基本功能介紹
首先识埋,介紹下Monkey的基本使用,如果要發(fā)送500個(gè)隨機(jī)事件零渐,只需運(yùn)行如下命令:
adb shell monkey 500

插上手機(jī)運(yùn)行后窒舟,大家是不是發(fā)現(xiàn)手機(jī)開始瘋狂的運(yùn)行起來了。So Easy!
在感受完Monkey的效果后相恃,發(fā)現(xiàn)這“悟空”太調(diào)皮了辜纲,根本招架不住啊拦耐!是否有類似“緊箍咒”這種約束類命令耕腾,讓這只猴子在某個(gè)包或類中運(yùn)行呢?要想Monkey牢牢的限制在某個(gè)包中杀糯,命令也很簡單:
adb shell monkey –p your-package-name 500

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

這樣“悟空”就飛不出你的五指山了固翰。
Mokey編寫自動(dòng)化測(cè)試腳本
若控制不住“悟空”狼纬,只讓它隨機(jī)亂點(diǎn)的話羹呵,Monkey是替代不了黑盒測(cè)試用例的。我們能不能想些辦法疗琉,控制住“悟空”讓他做些簡單的自動(dòng)化測(cè)試的工作呢冈欢?下面來看一下,如何用Monkey來編寫腳本盈简。
先簡單介紹下Monkey的API凑耻,若有需要詳細(xì)了解的小伙伴,可自行百度或谷歌一下查閱哈柠贤。
(1) 軌跡球事件:DispatchTrackball(參數(shù)1~參數(shù)12)
(2) 輸入字符串事件:DispatchString(String text)
(3) 點(diǎn)擊事件:DispatchPointer(參數(shù)1~參數(shù)12)
(4) 啟動(dòng)應(yīng)用:LaunchActivity(String pkg_name, String class_name)
(5) 等待事件:UserWait(long sleeptime)
(6) 按下鍵值:DispatchPress(int keyCode)
(7) 長按鍵值:LongPress(int keyCode)
(8) 發(fā)送鍵值:DispatchKey(參數(shù)1~參數(shù)8)
(9) 打開軟鍵盤:DispatchFlip(Boolean keyboardOpen)
了解完常用API后香浩,我們來看一下Monkey腳本的編寫規(guī)范。Monkey Script是按照一定的語法規(guī)則編寫的有序的用戶事件流臼勉,使用于Monkey命令工具的腳本邻吭。Monkey腳本一般以如下4條語句開頭:

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

下面來看一個(gè)簡單應(yīng)用的實(shí)戰(zhàn)宴霸,實(shí)現(xiàn)的效果很簡單囱晴,就是隨便輸入文本,選擇選項(xiàng)再進(jìn)行提交猖败,提交后要驗(yàn)證提交后的效果速缆。

![](http://upload-images.jianshu.io/upload_images/1944350-04000f883b90cd4b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![](http://upload-images.jianshu.io/upload_images/1944350-258fbd5d09060de5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)




Start

Script
type = user
count = 10
speed = 1.0
start data >>LaunchActivity(com.ringo.bugben,com.ringo.bugben.MainActivity)

點(diǎn)擊文本框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內(nèi)容

captureDispatchString(Hello)

點(diǎn)擊文本框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內(nèi)容

captureDispatchString(Ringo)

點(diǎn)擊加粗

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)

點(diǎn)擊大號(hào)

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)

點(diǎn)擊提交

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文件降允,然后將該腳本推送到手機(jī)的sd卡里恩闻。

adb push HelloMonkey /mnt/sdcard/

然后運(yùn)行:

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

腳本后面的數(shù)字1表示運(yùn)行該腳本的次數(shù)。小伙伴們可以安裝附件里的Bugben.apk再執(zhí)行下腳本感受下哦剧董!

Monkey工具總結(jié)

Monkey可以編寫腳本做簡單的自動(dòng)化測(cè)試幢尚,但局限性非常大,例如無法進(jìn)行截屏操作翅楼,不能簡單的支持插件的編寫尉剩,沒有好的辦法控制事件流,不支持錄制回放等毅臊。我們?cè)谄綍r(shí)的使用中理茎,關(guān)注較多的是利用好Monkey的優(yōu)勢(shì),如不需源碼管嬉,不需編譯就可以直接運(yùn)行皂林。

法寶2:Monkey之子——MonkeyRunner

Monkey雖然能實(shí)現(xiàn)部分的自動(dòng)化測(cè)試任務(wù),但本身有很多的局限性蚯撩,例如不支持截屏础倍,點(diǎn)擊事件是基于坐標(biāo)的,不支持錄制回放等胎挎。我們?cè)趯?shí)際應(yīng)用中沟启,盡量關(guān)注利用好Monkey測(cè)試的優(yōu)勢(shì)忆家。若平時(shí)的工作中遇到Monkey工具無法滿足的,這里給大家推薦另一款工具M(jìn)onkeyRunner德迹。
同樣先簡單的介紹下MonkeyRunner的API芽卿,這里重點(diǎn)介紹能夠?qū)崿F(xiàn)上文Monkey腳本的API,其余的API感興趣的小伙伴可以自行查閱胳搞。
(1) 等待設(shè)備連接:waitForConnection()
(2) 安裝apk應(yīng)用:installPackage(String path)
(3) 啟動(dòng)應(yīng)用:startActivity(String packageName+activityName)
(4) 點(diǎn)擊事件:touch(int xPos, int yPos, dictionary type)
(5) 輸入事件:type(String text)
(6) 等待:sleep(int second)
(7) 截圖:takeSnapshot()
(8) 發(fā)送鍵值:press(String name, dictionary type)

MokeyRunner編寫自動(dòng)化測(cè)試腳本

下面我們來看下蹬竖,用MonkeyRunner實(shí)現(xiàn)的自動(dòng)化腳本。

# 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目錄下币厕,執(zhí)行monkeyrunner HelloMonkeyRunner.py


執(zhí)行完成后,效果如上芽腾,并且會(huì)在當(dāng)前目錄生成HelloMonkeyRunner.png截圖旦装。
MokeyRunner的錄制回放
首先是環(huán)境配置,在源碼“~\sdk\monkeyrunner\scripts”目錄下有monkey_recorder.py和monkey_playback.py摊滔,將這兩個(gè)文件(附件中有這兩文件)拷貝到SDK的tools目錄下阴绢,就可以通過如下代碼進(jìn)行啟動(dòng):

monkeyrunner monkey_recorder.py

運(yùn)行結(jié)果如下圖所示:


下面用MonkeyRecorder提供的控件,來進(jìn)行腳本的錄制艰躺。


錄制完成后呻袭,導(dǎo)出腳本保存為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',}

腳本錄制完畢腺兴,接來下看看回放腳本是否正常左电。回放腳本時(shí)執(zhí)行以下命令:
monkeyrunner monkey_playback your_script.mr

由于腳本中未加入拉起應(yīng)用的代碼页响,這里運(yùn)行前需手動(dòng)拉起應(yīng)用篓足。


結(jié)果運(yùn)行正常,符合我們的預(yù)期闰蚕。

MonkeyRunner工具總結(jié)

MonkeyRunner有很多強(qiáng)大并好用的API栈拖,并且支持錄制回放和截圖操作。同樣它也不需源碼没陡,不需編譯就可以直接運(yùn)行涩哟。但MonkeyRunner和Monkey類似,也是基于控件坐標(biāo)進(jìn)行定位的盼玄,這樣的定位方式極易導(dǎo)致回放失敗贴彼。

法寶3:單元測(cè)試框架——Instrumentation

Monkey父子均可通過編寫相應(yīng)的腳本,在不依賴源碼的前提下完成部分自動(dòng)化測(cè)試的工作强岸。但它們都是依靠控件坐標(biāo)進(jìn)行定位的锻弓,在實(shí)際項(xiàng)目中,控件坐標(biāo)往往是最不穩(wěn)定的蝌箍,隨時(shí)都有可能因?yàn)槌绦騿T對(duì)控件位置的調(diào)整而導(dǎo)致腳本運(yùn)行失敗青灼。怎樣可以不依賴坐標(biāo)來進(jìn)行應(yīng)用的自動(dòng)化測(cè)試呢暴心?下面就要亮出自動(dòng)化測(cè)試的屠龍寶刀了——Instrumentation框架。
Instrumentation框架主要是依靠控件的ID來進(jìn)行定位的杂拨,擁有成熟的用例管理系統(tǒng)专普,是Android主推的白盒測(cè)試框架。若想對(duì)項(xiàng)目進(jìn)行深入的弹沽、系統(tǒng)的單元測(cè)試檀夹,基本上都離不開Instrumentation這把屠龍寶刀。
在了解Instrumentation框架之前策橘,先對(duì)Android組件生命周期對(duì)應(yīng)的回調(diào)函數(shù)做個(gè)說明:

從上圖可以看出炸渡,Activity處于不同狀態(tài)時(shí),將調(diào)用不同的回調(diào)函數(shù)丽已。但Android API不提供直接調(diào)用這些回調(diào)函數(shù)的方法蚌堵,在Instrumentation中則可以這樣做。Instrumentation類通過“hooks”控制著Android組件的正常生命周期沛婴,同時(shí)控制Android系統(tǒng)加載應(yīng)用程序吼畏。通過Instrumentation類我們可以在測(cè)試代碼中調(diào)用這些回調(diào)函數(shù),就像在調(diào)試該控件一樣一步一步地進(jìn)入到該控件的整個(gè)生命周期中嘁灯。
Instrumentation和Activity有點(diǎn)類似泻蚊,只不過Activity是需要一個(gè)界面的,而Instrumentation并不是這樣的丑婿,我們可以將它理解為一種沒有圖形界面的性雄,具有啟動(dòng)能力的,用于監(jiān)控其他類(用Target Package聲明)的工具類枯冈。
下面通過一個(gè)簡單的例子來講解Instrumentation的基本測(cè)試方法毅贮。

  1. 首先建立項(xiàng)目名為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);     
      }
}

這個(gè)程序的功能很簡單,就是給2個(gè)TextView的內(nèi)容設(shè)置不同的文本格式病蛉。

  1. 對(duì)于測(cè)試工程師而言炫加,HelloBugben是一個(gè)已完成的項(xiàng)目。接下來需創(chuàng)建一個(gè)測(cè)試項(xiàng)目铺然,選擇“New->Other->Android Test Project”俗孝,命名為HelloBugbenTest,選擇要測(cè)試的目標(biāo)項(xiàng)目為HelloBugben項(xiàng)目魄健,然后點(diǎn)擊Finish即可完成測(cè)試項(xiàng)目的創(chuàng)建赋铝。
![](http://upload-images.jianshu.io/upload_images/1944350-a36c366acd98b643.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

可以注意到,該項(xiàng)目的包名自帶了com.example.hellobugben.test這個(gè)test標(biāo)簽沽瘦,這就說明該測(cè)試項(xiàng)目是針對(duì)HelloBugben所設(shè)置的革骨。
打開AndroidManifest可看到<Instrumentation>標(biāo)簽农尖,該標(biāo)簽元素用來指定要測(cè)試的應(yīng)用程序,自動(dòng)將com.example.hellobugben設(shè)為targetPackage對(duì)象良哲,代碼清單如下:

<?xml version="1.0" encoding="utf-8"?><manifestxmlns:android="http://schemas.android.com/apk/res/android"    
package="com.example.hellobugben.test"     
android:versionCode="1"      
android:versionName="1.0" >       
<uses-sdkandroid:minSdkVersion="8" />       
<instrumentation            
android:name="android.test.InstrumentationTestRunner"              
android:targetPackage="com.example.hellobugben" />      
<application             
android:icon="@drawable/ic_launcher"          
android:label="@string/app_name" >           
<uses-libraryandroid:name="android.test.runner" />    
</application></manifest>

在<Instrumentation>標(biāo)簽中盛卡,android:name聲明了測(cè)試框架,android:targetPackage指定了待測(cè)項(xiàng)目包名筑凫。
下面來看一下滑沧,如何用Instrumentation框架編寫測(cè)試程序,代碼如下:

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<HelloBugbenActivity>{              
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<HelloBugbenActivity>這個(gè)類。接著在setUp()方法中通過getActivity()方法獲取待測(cè)項(xiàng)目的實(shí)例棚潦,并通過textview1和textview2獲取兩個(gè)TextView控件殖属。最后編寫3個(gè)測(cè)試用例:控制文本設(shè)置測(cè)試testSetText()、字體加粗屬性測(cè)試testSetBold瓦盛、字體大小屬性測(cè)試testSetSize()洗显。這里用到的關(guān)鍵方法是Instrumentation API里面的getActivity()方法,待測(cè)的Activity在沒有調(diào)用此方法的時(shí)候是不會(huì)啟動(dòng)的原环。
眼尖的小伙伴可能已經(jīng)發(fā)現(xiàn)控制文本設(shè)置測(cè)試這里啟用了一個(gè)新線程挠唆,這是因?yàn)樵贏ndroid中相關(guān)的view和控件不是線程安全的,必須單獨(dú)在新的線程中做處理嘱吗,不然會(huì)報(bào)

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

這個(gè)錯(cuò)誤玄组。所以需要啟動(dòng)新線程進(jìn)行處理,具體步驟如下:

  1. 在setUp()方法中創(chuàng)建Handler對(duì)象谒麦,代碼如下:
public void setUp() throws Exception{           
 super.setUp();            
 handler = new Handler(); 
   }
  1. 創(chuàng)建Runnable對(duì)象俄讹,在Runnable中進(jìn)行控件文本設(shè)置,代碼如下:
 Runnable runnableTxt = new Runnable() {                        
@Override         
public void run(){                  
// TODO Auto-generated method stub                                 
helloBugben.setTxt(bugben_txt);     
  }  
    };
  1. 在具體測(cè)試方法中通過調(diào)用runnable對(duì)象绕德,實(shí)現(xiàn)文本設(shè)置患膛,代碼如下:
 new Thread(){    
public void run() {                                  
if (handler != null) {                                                    
handler.post(runnableTxt);                   
         }                                  
    }            
}.start(); 

我們運(yùn)行一下結(jié)果,結(jié)果截圖如下:

可以看到3個(gè)測(cè)試用例結(jié)果運(yùn)行正常耻蛇。
可能有小伙伴要問踪蹬,程序中為啥要繼承ActivityInstrumentationTestCase2呢?我們先看一下ActivityInstrumentationTestCase2的繼承結(jié)構(gòu):
java.lang.Object
junit.framework.Assert
junit.framework.TestCase
android.test.InstrumentationTestCase
android.test.ActivityTestCase
android.test.ActivityInstrumentationTestCase2<T>

ActivityInstrumentationTestCase2允許InstrumentationTestCase. launchActivity來啟動(dòng)被測(cè)試的Activity臣咖。而且ActivityInstrumentationTestCase2還支持在新的UI線程中運(yùn)行測(cè)試方法跃捣,能注入Intent對(duì)象到被測(cè)試的Activity中,這樣一來夺蛇,我們就能直接操作被測(cè)試的Activity了疚漆。正因?yàn)锳ctivityInstrumentationTestCase2有如此出眾的有點(diǎn),它才成功取代了比它早出世的哥哥:ActivityInstrumentationTestCase,成為了Instrumentation測(cè)試的基礎(chǔ)娶聘。

Instrumentation測(cè)試框架實(shí)戰(zhàn)

了解完Instrumentation的基本測(cè)試方法后灵临,我們來看一下如何運(yùn)用Instrumentation框架完成前文Monkey父子完成的自動(dòng)化測(cè)試任務(wù)。

  1. 首先建立項(xiàng)目名為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);        
     }           
  });   
 }
}
  1. 在建立一個(gè)名為OtherActivity的類,點(diǎn)擊提交按鈕后发钝,跳轉(zhuǎn)到這個(gè)界面顿涣,代碼如下:
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.接下來需創(chuàng)建一個(gè)測(cè)試項(xiàng)目,命名為BugbenTestBase酝豪,選擇要測(cè)試的目標(biāo)項(xiàng)目為Bugben項(xiàng)目涛碑,然后點(diǎn)擊Finish即可完成測(cè)試項(xiàng)目的創(chuàng)建。

在com.ringo.bugben.test包中添加BugbenTestBase這個(gè)類孵淘,類的代碼如下:

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<MainActivity>{        
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 = "自動(dòng)化測(cè)試";    
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();                           
// 啟動(dòng)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();       
   }                  
// 提交測(cè)試     
public void testSubmit()throws Throwable{            
Log.v("Ringo", "test normal submit");              
// 添加一個(gè)監(jiān)聽器蒲障,監(jiān)視OtherActivity的啟動(dòng)                    
ActivityMonitor bugbenMonitor = getInstrumentation().addMonitor(                    
OtherActivity.class.getName(), null, false);             
// 要操作待測(cè)程序的UI必須在runTestOnUiThread中執(zhí)行             
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毫秒,避免程序響應(yīng)慢出錯(cuò)                             
SystemClock.sleep(500);                                                                  
// 點(diǎn)擊提交按鈕                  
subButton.performClick();          
      }              
  });                           
// 從ActivityMonitor監(jiān)視器中獲取OtherActivity的實(shí)例           
otherActivity = (OtherActivity)getInstrumentation().waitForMonitor(bugbenMonitor);                
// 獲取的OtherActivity實(shí)例應(yīng)不為空         
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毫秒瘫证,避免程序響應(yīng)慢出錯(cuò)                
SystemClock.sleep(5000);   
      }
}

上述代碼中揉阎,共包括自動(dòng)化測(cè)試需要進(jìn)行的5個(gè)步驟,具體如下:
(1) 啟動(dòng)應(yīng)用:通過Intent對(duì)象setClassName()方法設(shè)置包名和類名背捌,通過setFlags()方法設(shè)置標(biāo)示毙籽,然后通過getInstrumentation()的startActivitySync(intent)來啟動(dòng)應(yīng)用,進(jìn)入到主界面毡庆。
(2) 編輯控件:在Android中相關(guān)的view和控件不是線程安全的坑赡,所以必須單獨(dú)在新的線程中做處理。代碼中我們?cè)趓unTestOnUiThread(new Runnable())中的run()方法中執(zhí)行的么抗。
(3) 提交結(jié)果:點(diǎn)擊提交按鈕進(jìn)行結(jié)果的提交毅否,由于點(diǎn)擊按鈕也屬于界面操作,所以也需要在runTestOnUiThread這個(gè)線程中完成蝇刀。
(4) 界面跳轉(zhuǎn):這是Instrumentation自動(dòng)化測(cè)試中最需要注意的一個(gè)點(diǎn)螟加,特別是如何確認(rèn)界面已經(jīng)發(fā)生了跳轉(zhuǎn)。在Instrumentation中可以通過設(shè)置Monitor監(jiān)視器來確認(rèn)熊泵。代碼如下:

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

然后通過waitForMonitor方法等待界面跳轉(zhuǎn)仰迁。

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

若返回結(jié)果otherActivity對(duì)象不為空,說明跳轉(zhuǎn)正常顽分。
(5) 驗(yàn)證顯示:跳轉(zhuǎn)后,通過assertEquals()或assertTrue()方法來判斷顯示的正確性施蜜。
我們運(yùn)行一下結(jié)果卒蘸,結(jié)果截圖如下:

Instrumentation工具總結(jié)

Instrumentation框架的整體運(yùn)行流程圖如下:

Instrumentation是基于源碼進(jìn)行腳本開發(fā)的,測(cè)試的穩(wěn)定性好,可移植性高缸沃。正因?yàn)樗腔谠创a的恰起,所以需要腳本開發(fā)人員對(duì)Java語言、Android框架運(yùn)行機(jī)制趾牧、Eclipse開發(fā)工具都非常熟悉检盼。Instrumentation框架本身不支持多應(yīng)用的交互,例如測(cè)試“通過短信中的號(hào)碼去撥打電話”這個(gè)用例翘单,被測(cè)應(yīng)用將從短信應(yīng)用界面跳轉(zhuǎn)到撥號(hào)應(yīng)用界面吨枉,但I(xiàn)nstrumentation沒有辦法同事控制短信和撥號(hào)兩個(gè)應(yīng)用,這是因?yàn)锳ndroid系統(tǒng)自身的安全性限制哄芜,禁止多應(yīng)用的進(jìn)程間相互訪問貌亭。

法寶4:終極自動(dòng)化測(cè)試框架——UIAutomator

鑒于Instrumentation框架需要讀懂項(xiàng)目源碼、腳本開發(fā)難度較高并且不支持多應(yīng)用交互认臊,Android官網(wǎng)亮出了自動(dòng)化測(cè)試的王牌——UIAutomator圃庭,并主推這個(gè)自動(dòng)化測(cè)試框架。該框架無需項(xiàng)目源碼失晴,腳本開發(fā)效率高且難度低剧腻,并且支持多應(yīng)用的交互。當(dāng)UIAutomator面世后涂屁,Instrumentation框架回歸到了其單元測(cè)試框架的本來位置恕酸。
下面我們來看一下這個(gè)框架是如何運(yùn)行起來的。首先運(yùn)行位于Android SDK的tools目錄下的uiautomatorviewer.bat胯陋,可以看到啟動(dòng)界面蕊温。

啟動(dòng)bugben應(yīng)用后,點(diǎn)擊



這個(gè)圖標(biāo)來采集手機(jī)的界面信息遏乔,如下所示:


我們可以看到义矛,用uiautomatorviewer捕捉到的控件非常清晰,很方便元素位置的定位盟萨。在UIAutomator框架中凉翻,測(cè)試程序與待測(cè)程序之間是松耦合關(guān)系,即完全不需要獲取待測(cè)程序的控件ID捻激,只需對(duì)控件的文本(text)制轰、描述(content-desc)等信息進(jìn)行識(shí)別即可。
在進(jìn)行實(shí)戰(zhàn)之前胞谭,我們先看一下UIAutomator的API部分垃杖,由以下架構(gòu)圖組成。

下面來看下如何利用該框架創(chuàng)建測(cè)試工程丈屹。

  1. 創(chuàng)建BugBenTestUIAuto項(xiàng)目调俘,右鍵點(diǎn)擊項(xiàng)目并選擇Properties > Java Build Path
    點(diǎn)擊Add Library > Junit > Junit3伶棒,添加Junit框架。

點(diǎn)擊Add External Jar彩库,并導(dǎo)航到Android SDK目錄下肤无,選擇platforms目錄下面的android.jar和UIAutomator.jar兩個(gè)文件。

  1. 設(shè)置完成后骇钦,可以開始編寫項(xiàng)目測(cè)試的代碼宛渐,具體如下:
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();      
// 啟動(dòng)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();  
}   
// 提交文字測(cè)試   
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");  
}   
// 獲取加粗選項(xiàng)并賦值   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 加粗");  
}   
// 獲取大號(hào)字體選項(xiàng)并賦值   
UiObject bugben_big = new UiObject(new UiSelector().text("大號(hào)"));  
if(bugben_big.exists() && bugben_big.isEnabled()){     
bugben_big.click();  
}else{     
Log.e("Ringo", "can not find 大號(hào)");  }       
// 獲取提交按鈕并跳轉(zhuǎn)   
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)));      
// 驗(yàn)證    
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這個(gè)類窥翩。同樣,我們來看下UiAutomator框架下自動(dòng)化測(cè)試進(jìn)行的5個(gè)步驟坦仍,具體如下:
(1) 啟動(dòng)應(yīng)用:于Instrumentation框架不同鳍烁,UiAutomator是通過命令行進(jìn)行應(yīng)用啟動(dòng)的。
am start –n 包名/.應(yīng)用名

(2) 編輯控件:UiAutomator框架中繁扎,控件的編輯相對(duì)簡單幔荒,直接通過UiSelector的text()方法找到對(duì)應(yīng)的控件,然后調(diào)用控件的setText()即可對(duì)其賦值梳玫。

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) 提交結(jié)果:點(diǎn)擊提交按鈕進(jìn)行結(jié)果的提交爹梁,也是通過UiSelector的text()方法找到對(duì)應(yīng)的控件,然后調(diào)用clickAndWaitForNewWindow()方法來等待跳轉(zhuǎn)完成提澎。

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

(4) 界面跳轉(zhuǎn)元素獲纫:用uiautomatorviewer捕捉跳轉(zhuǎn)后的控件,例如捕捉跳轉(zhuǎn)后的文本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) 驗(yàn)證顯示:跳轉(zhuǎn)后盼忌,通過assertEquals()或assertTrue()方法來判斷顯示的正確性积糯。

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

至此核心代碼部分已編寫完畢。UIAutomator有一個(gè)麻煩之處:沒法通過Eclipse直接編譯谦纱】闯桑可以借助于一系列命令行進(jìn)行編譯,詳細(xì)步驟如下:

  1. 通過如下命令創(chuàng)建編譯的build.xml文件
    <android-sdk目錄>/tools/android create uitest-project –n 工程名 –t 1 –p 項(xiàng)目路徑
    針對(duì)我們的項(xiàng)目跨嘉,命令如下:
    android create uitest-project –n BugBenTestUIAuto –t 1 –p "E:\workspace\BugBenTestUIAuto"

創(chuàng)建完成后川慌,刷新BugBenTestUIAuto項(xiàng)目,得到如下圖:

打開build.xml會(huì)看到祠乃,編譯項(xiàng)目名為BugBenTestUIAuto梦重。

  1. 設(shè)置SDK的路徑:
set ANDROID_HOME="E:\sdk\android-sdk-windows"
  1. 進(jìn)入測(cè)試目錄,然后進(jìn)行編譯:
cd /d E:\workspace\android\BugBenTestUIAutoant build

編譯完成后亮瓷,再次刷新項(xiàng)目琴拧,你將看到BugBenTestUIAuto.jar包生成在bin目錄下了,如圖:

  1. 將生成的jar包推送到手機(jī)端
adb push E:\workspace\android\BugBenTestUIAuto\bin\BugBenTestUIAuto.jar /data/local/tmp/
  1. 在手機(jī)端運(yùn)行自動(dòng)化腳本寺庄,即jar包中的測(cè)試用例艾蓝,命令行如下:
adb shell uiautomator runtest BugBenTestUIAuto.jar -c com.ringo.bugben.test.BugBenTest

運(yùn)行結(jié)果如下力崇,返回OK表示運(yùn)行成功斗塘。

  1. 最后赢织,將運(yùn)行后的截圖從手機(jī)端拷貝到PC上
adb pull /data/local/tmp/displayCheck.png E:\workspace\android\BugBenTestUIAuto

至此整個(gè)代碼就編譯和運(yùn)行完畢,如果覺得調(diào)試時(shí)反復(fù)修改和編譯比較麻煩馍盟,可以將以上腳本寫成一個(gè)批處理文件于置。
UIAutomator工具總結(jié)
相比于Instrumentation工具,UIAutomator工具更靈活一些贞岭,它不需要項(xiàng)目源碼八毯,擁有可視化的界面和可視化的樹狀層級(jí)列表,極大降低了自動(dòng)化測(cè)試腳本開發(fā)的門檻瞄桨。并且UIAutomator支持多應(yīng)用的交互话速,彌補(bǔ)了Instrumentation工具的不足。但UIAutomator難以捕捉到控件的顏色芯侥、字體粗細(xì)泊交、字號(hào)等信息,要驗(yàn)證該類信息的話柱查,需要通過截圖的方式進(jìn)行半自動(dòng)驗(yàn)證廓俭。同時(shí),UIAutomator的調(diào)試相比Instrumentation要困難唉工。所以在平時(shí)的測(cè)試過程中研乒,建議將兩者結(jié)合起來使用,可達(dá)到更佳的效果淋硝!

注:核心內(nèi)容轉(zhuǎn)自許奔的《深入理解Android自動(dòng)化測(cè)試》

關(guān)于騰訊WeTest (wetest.qq.com)

騰訊WeTest是騰訊游戲官方推出的一站式游戲測(cè)試平臺(tái)雹熬,用十年騰訊游戲測(cè)試經(jīng)驗(yàn)幫助廣大開發(fā)者對(duì)游戲開發(fā)全生命周期進(jìn)行質(zhì)量保障。騰訊WeTest提供:適配兼容測(cè)試谣膳;云端真機(jī)調(diào)試竿报;安全測(cè)試;耗電量測(cè)試参歹;服務(wù)器性能測(cè)試仰楚;輿情監(jiān)控等服務(wù)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末犬庇,一起剝皮案震驚了整個(gè)濱河市僧界,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌臭挽,老刑警劉巖捂襟,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異欢峰,居然都是意外死亡葬荷,警方通過查閱死者的電腦和手機(jī)涨共,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宠漩,“玉大人举反,你說我怎么就攤上這事“怯酰” “怎么了火鼻?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長雕崩。 經(jīng)常有香客問我魁索,道長,這世上最難降的妖魔是什么盼铁? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任粗蔚,我火速辦了婚禮,結(jié)果婚禮上饶火,老公的妹妹穿的比我還像新娘鹏控。我一直安慰自己,他們只是感情好趁窃,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布牧挣。 她就那樣靜靜地躺著,像睡著了一般醒陆。 火紅的嫁衣襯著肌膚如雪瀑构。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天刨摩,我揣著相機(jī)與錄音寺晌,去河邊找鬼。 笑死澡刹,一個(gè)胖子當(dāng)著我的面吹牛呻征,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播罢浇,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼陆赋,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了嚷闭?” 一聲冷哼從身側(cè)響起攒岛,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎胞锰,沒想到半個(gè)月后灾锯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嗅榕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年顺饮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了吵聪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡兼雄,死狀恐怖吟逝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情君旦,我是刑警寧澤澎办,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布嘲碱,位于F島的核電站金砍,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏麦锯。R本人自食惡果不足惜恕稠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望扶欣。 院中可真熱鬧鹅巍,春花似錦、人聲如沸料祠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽髓绽。三九已至敛苇,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間顺呕,已是汗流浹背枫攀。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留株茶,地道東北人来涨。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓巡蘸,卻偏偏與公主長得像扇调,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子户辞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,072評(píng)論 25 707
  • 解放程序猿寶貴的右手(或者是左手) ——Android自動(dòng)化測(cè)試技巧 Google大神鎮(zhèn)樓 : http://de...
    eclipse_xu閱讀 4,946評(píng)論 6 40
  • 標(biāo)簽(空格分隔): Android 單元測(cè)試的好處:Martin Fowler在《重構(gòu)》里面還解釋了為什么單元測(cè)試...
    背影殺手不太冷閱讀 5,821評(píng)論 3 25
  • Instrumentation介紹 Instrumentation是個(gè)什么東西僵闯? Instrumentation測(cè)...
    打不死的小強(qiáng)qz閱讀 7,785評(píng)論 2 39
  • 我以前常說我要去漂泊 我喜歡漂泊 而在漂泊過的這兩年里我逐漸愛上了鄉(xiāng)下 我想有一片地 當(dāng)然最好面向一面海 或者一汪...
    soumnstan閱讀 383評(píng)論 0 1