1. 程式人生 > >行為觸發之Android自動化測試instrumentation(一)

行為觸發之Android自動化測試instrumentation(一)

由於對測試並沒有太多的概念,一不小心搜尋到各種字首:Instrumentation、InstrumentationTestCase、InstrumentationTestSuite、InstrumentationTestRunner 傻了眼,但仔細一看把字首去掉後大致就分類為了這三類:TestCase、TestSuite、TestRunner。用中文來翻譯應該可以認為是測試樣例,測試集合,測試執行工具吧。之後再官方文件中搜索InstrumentationTestRunner,用法介紹http://developer.android.com/reference/android/test/InstrumentationTestRunner.html

常規Android自動化方法分3步走:

1、先繼承各種****TestCase完成測試樣例的編寫(這裡有很多***TestCase,適用於不用場景,都可以使用,對於broadcastreceiver用intent可以觸發)

2、在Andriodmanifest中需要加入<instrumentation> 並配置些啥

3、完成之後可以adb shell am instrument ****執行我們的自動化測試

下面用一個小demo來完成入門,主要還是自己單獨建立一個測試專案,但重做安全的角度來說,我們更多的是在做黑盒測試,那麼就會產生了不少疑問,在下面闡述。

1.先是需要測試的app,我這裡簡單寫了一個

package com.example.hello;

import com.example.hello.R;

import android.app.Activity;
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.Toast;

public class HelloActivity extends Activity {

	final String TAG = "helloactivity";
	Button mButton;
	EditText mEditText;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		Log.i(TAG, "hello onCreate");
		setContentView(R.layout.activity_hello_test);
		mButton = (Button) findViewById(R.id.Button1);
		mEditText = (EditText) findViewById(R.id.EditText1);
		mEditText.setHint("INPUT");
		mButton.setOnClickListener(new OnClickListener(){
			public void onClick(View v){
				String msg = mEditText.getText().toString();
				if (msg.equals("1")) {
					Toast.makeText(getApplicationContext(), "hello_1", Toast.LENGTH_LONG).show();
				} else if(msg.equals("2")){
					Toast.makeText(getApplicationContext(), "hello_2", Toast.LENGTH_SHORT).show();
				} else {
					Toast.makeText(getApplicationContext(), "hello_nothing", Toast.LENGTH_SHORT).show();
				}
			}
		});
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.hello_test, menu);
		return true;
	}

	@Override
	protected void onDestroy() {
		super.onDestroy();
		Log.i(TAG,"hello onDestroy");
	}

功能就是輸入1、2、其他字串之後onClick之後會走不同路徑


2、編寫自動化測試的TestCase

官方推薦測試樣例專案建立路徑最好滿足一下規範(剛開始以為這樣子是為了更好的找到測試APP中相應的類,後來想想也不對,不是同一個專案,為啥能直接匯入測試APP的類import com.example.hello.HelloActivity呢,自己還索性在其他路徑建了一個測試專案,發現com.example.hello.HelloActivity確實能匯入,而其他專案的類則不行,後來想想應該是target的原因,ADT檢測該專案沒有該類,便自動以target為目標匹配吧):

MyProject/
      AndroidManifest.xml
      res/
          ... (resources for main application)
      src/
          ... (source code for main application) ...
      tests/
          AndroidManifest.xml
          res/
              ... (resources for tests)
          src/
              ... (source code for tests)
上圖新建-》其他專案-》Android Test Project:


之後可以選擇需要測試的目標APP:


我們這裡需要測試的是Hello,這樣自動生成後,ADT自動幫我們完成了第二步中在AndroiManifest里加入需要宣告的東西

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.hello.test"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="8" />

    <instrumentation
        android:name="android.test.InstrumentationTestRunner"
        android:targetPackage="com.example.hello"
        android:label="the hello test" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <uses-library android:name="android.test.runner" />
    </application>

</manifest>

target指明瞭需要測試的目標,label為測試的標籤(在模擬器中使用dev tool可以看到對應的標籤),這裡寫了個一個簡單的測試樣例,自動向編輯框中輸入1,2,3然後分別自動點選按鈕
package com.example.hello.test;

import android.app.Instrumentation;
import android.content.Intent;
import android.os.SystemClock;
import android.test.InstrumentationTestCase;
import android.util.Log;
import android.widget.Button;
import android.widget.EditText;

import com.example.hello.HelloActivity;
import com.example.hello.R;

public class TestHelloActiviry extends InstrumentationTestCase {
	
	final String TAG = "TestHelloAppTestHelloApp"; 
	
	Button mHelloTestButton;
	EditText mHelloEditText;
	HelloActivity mHelloTestActivity;
	Instrumentation mInstrumentation;
	
	public void testHelloActivity() {
		Log.i(TAG, "call testHelloActivity()");
		mHelloTestButton = (Button)mHelloTestActivity.findViewById(R.id.Button1);
		mHelloEditText = (EditText)mHelloTestActivity.findViewById(R.id.EditText1);
		for (int i = 0; i < 3; i++) {
			//設定事件在主執行緒中執行
			mInstrumentation.runOnMainSync(new Click(mHelloTestButton,mHelloEditText,Integer.toString(i)));
			SystemClock.sleep(3000);
		}
		
	}
	
	public void testHelloActivity2() {
		
	}
	
	private class Click implements Runnable{
		Button button;
		EditText editText;
		String str;
		Click(Button b,EditText e,String s){
			button = b;
			editText = e;
			str = s;
		}
		@Override
		public void run() {
			editText.setText(str);	
			button.performClick();
			
		}
	}
	
	//負責testcase開始前的初始化工作
	@Override
	protected void setUp() throws Exception {
		super.setUp();
		Log.i(TAG, "call setUp()");
		mInstrumentation = getInstrumentation();
		Intent intent = new Intent();
		intent.setClassName("com.example.hello", "com.example.hello.HelloActivity");
		intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		//通過intent觸發activity
		mHelloTestActivity = (HelloActivity)mInstrumentation.startActivitySync(intent);
	}


	@Override
	protected void tearDown() throws Exception {
		super.tearDown();
		
		Log.i(TAG, "tearDown()");
	}
	
	
}

3、執行起來吧

接下來要麼直接在ADT中run as-》Android jUnit test,但這樣太智慧了,我們並不知道實際上做了什麼,對於我們安全來說,我們肯定不能開個ADT直接點點點吧。索性跟蹤了一下console發現如下:

trouble writing output: already prepared
[2014-03-15 18:40:42 - tests] ------------------------------
[2014-03-15 18:40:42 - tests] Android Launch!
[2014-03-15 18:40:42 - tests] adb is running normally.
[2014-03-15 18:40:42 - tests] Performing android.test.InstrumentationTestRunner JUnit launch
[2014-03-15 18:40:42 - tests] Automatic Target Mode: using device '?'
[2014-03-15 18:40:42 - tests] Uploading tests.apk onto device '?'
[2014-03-15 18:40:42 - tests] Installing tests.apk...
[2014-03-15 18:41:09 - tests] Success!
[2014-03-15 18:41:09 - tests] Project dependency found, installing: Hello
[2014-03-15 18:41:14 - Hello] Uploading Hello.apk onto device '?'
[2014-03-15 18:41:14 - Hello] Installing Hello.apk...
[2014-03-15 18:41:41 - Hello] Success!
[2014-03-15 18:41:41 - tests] Launching instrumentation android.test.InstrumentationTestRunner on ?
[2014-03-15 18:41:43 - tests] Sending test information to Eclipse

其實是在安裝tests.apk時候,ADT根據target檢測到依賴關係,接著自動安裝了Hello.apk。接著執行instrumentation,也就是之前提到的命令
am instrument -w com.example.hello.test/android.test.InstrumentationRunner

然後這條am是不需要ROOT許可權的,具體命令深入研究的話檢視相關文件,你懂得我懂得。暫時還沒測試但是據一些資料查閱,需要被測試與測試樣例有同一簽名才能進行測試,這或許也是需要重打包的另一個因素。

順便補充一條命令:

pm list instrumentation 

可以檢視手機目前裝了那些instrumentation

4.回到正題

前面介紹了那麼多,其實只是為了搞安全行為觸發做鋪墊,為了解決之前開篇的論文疑惑做鋪墊。假設我們接觸不到原始碼,假設我們也需要指令碼自動化完成觸發,那這時候不肯能在ADT ADT的叫叫叫了!

走到這裡,有沒有發現其實重打包可能是為了被測試APP,測試樣例APP有同一個簽名。同時為了提示資料被傳輸,需要Toast,或許重打包另一些原因就在於這吧,資料觸發的時候把相應的事件打印出來(因為測試樣例的APP是不能彈出來的,最多log),所以論文裡的重打包或許只是在做這些。或者就是他們採用的方法不一樣,是把測試樣例smali放入被測試smali資料夾,修改列印內容下並修改AndroidManifest(有的測試寫在單獨程式java包中,而不是單獨一個測試工程)

通過一些自動化逆向工具,分析被測試APP自然能看到一些有用的資訊,通過AndroidManifest可以看到元件、intent。或許我們還需要view控制元件,那麼我們可以通過解析出來的檔案檢視,最後保證簽名一樣就可以更為完整測試了:


layout->activity_main可以看到控制元件資訊主要是id,通過values的public我們可以找到對應id的數值,那麼這時候就不存在R.id.xxxxx了,填入解析出來的數字就好了

		mHelloTestButton = (Button)mHelloTestActivity.findViewById(xxxxx);
		mHelloEditText = (EditText)mHelloTestActivity.findViewById(xxxxx);
逆向看了一下,暫時沒有什麼特別的地方,要有問題也就是能不能找到target對應的import 類了,畢竟有時候需要裡面的內容,暫時只思考到了這裡,還有很多API是很有用的,期待下次繼續吧!