由於對測試並沒有太多的概念,一不小心搜索到各種前綴: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,我這里簡單寫了一個 [java] view plain copy 在CODE上查看代碼片派生到我的代碼片 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里加入需要聲明的東西 [html] view plain copy 在CODE上查看代碼片派生到我的代碼片 <?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然后分別自動點擊按鈕 [java] view plain copy 在CODE上查看代碼片派生到我的代碼片 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發現如下: [html] view plain copy 在CODE上查看代碼片派生到我的代碼片 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,也就是之前提到的命令 [java] view plain copy 在CODE上查看代碼片派生到我的代碼片 am instrument -w com.example.hello.test/android.test.InstrumentationRunner 然后這條am是不需要ROOT權限的,具體命令深入研究的話查看相關文檔,你懂得我懂得。暫時還沒測試但是據一些資料查閱,需要被測試與測試樣例有同一簽名才能進行測試,這或許也是需要重打包的另一個因素。 順便補充一條命令: [html] view plain copy 在CODE上查看代碼片派生到我的代碼片 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了,填入解析出來的數字就好了 [java] view plain copy 在CODE上查看代碼片派生到我的代碼片 mHelloTestButton = (Button)mHelloTestActivity.findViewById(xxxxx); mHelloEditText = (EditText)mHelloTestActivity.findViewById(xxxxx); 逆向看了一下,暫時沒有什么特別的地方,要有問題也就是能不能找到target對應的import 類了,畢竟有時候需要里面的內容,暫時只思考到了這里,還有很多API是很有用的,期待下次繼續吧!