Android單元測試,使用ThreadingTest進行全新體驗前端
一、 背景java
長期以來,軟件測試工程師都在如何提升軟件系統質量和如何提升測試效率的道路上艱難地探索,但始終沒有一款性能全面的測試工具能夠知足需求。android
ThreadingTest智能型測試工具系列一期,是基於程序源代碼的白盒測試工具。採起前端分析器和後端結果分析分離的技術路線,實現對多種語言的編譯器級分析和多維度測試。後端
ThreadingTest經過一系列自動、高效、可視化技術,使軟件維護與開發效率加倍、成本減半、系統軟件質量提升幾個數量級。安全
ThreadingTest採用離線分析操做,即便電腦脫離互聯網也能夠得到一樣的體驗,保障代碼的安全。app
二、 ThreadingTestAndroid JUnit Test單元測試eclipse
ThreadingTest AndroidJUnit Test是基於Android JUnit Test,利用ThreadingTest自己的優點,編寫的一套單元測試組件。在原有的Android JUnit Test測試驅動代碼的基礎上,只須要進行很小的改動,就能夠修改成符合ThreadingTest Android JUnit Test測試代碼的單元測試。除此以外,二者的測試幾乎沒有差異,只須要您在測試時,打開TT實時監控來接收數據,而在測試過程當中,則無需其餘操做,甚至感受不到數據的收集過程。tcp
對於傳統的單元測試,在單元測試結束後,測試人員只能得到測試結果是否符合預期結果。若是與預期結果存在差別,也沒法當即獲得錯誤緣由。ide
當使用ThreadingTest AndroidJUnit Test進行單元測試時,仍是相同的測試體驗,卻可以獲得白盒測試的數據,當單元執行結束後,能夠根據產生的數據來分析程序的出錯點,使Bug的查找更快速,更準確,而爲此付出最小的代價。函數
三、 Android單元測試的編寫
Android單元測試有兩種方法,首先做爲java程序,能夠試用JUnit Test進行測試,另外也可以使用Android JUnit Test進行單元測試。
1)、JUnit Test進行單元測試
JUnit對Android應用程序進行單元測試須要使用Java命令來啓動或者在eclipse裏面將啓動的Bootstrap Entries改成JRE,可是這種只能測試邏輯代碼,由於是是運行在JVM上,而不是Android系統中,因此不能測試Android有關的代碼。
使用JUnit測試的代碼,須要在函數的以前加@Test,函數必須爲public類型,在eclipse中,在類上右擊,選擇JUnit Test便可進行測試。
2)Android JUnit Test進行單元測試
Android JUnit Test單元測試是一組直接或間接繼承自junit.framework.Testcase的類集合,入口是InstrumentationTestRunner。
使用Android JUnit Test測試,也存在兩種測試方法,測試驅動與測試代碼在同一工程下和測試驅動和測試代碼位於不一樣工程,二者之間並沒有巨大差別,只是在測試時,前者只安裝一個App,後者安裝兩個App。
以下爲一個單元測試的實例:
步驟 1:準備好須要測試的源代碼
步驟2:添加測試類,編寫測試代碼
package calculator.xwg.test;
import java.util.ArrayList; import java.util.LinkedList; import android.test.ActivityInstrumentationTestCase2; import android.app.AlertDialog; import android.app.Instrumentation; import android.widget.Button; import android.widget.ListView; import android.widget.TextView; import calculator.xwg.CalculatorMainActivity;
publicclass CalculatorMainActivityTestextends ActivityInstrumentationTestCase2<CalculatorMainActivity> { private InstrumentationmInstrumentation; private CalculatorMainActivitymActivity; private TextViewmQuestionText; private TextViewmAnswerText; private ButtonmCalculateButton; private ButtonmButton1; private ButtonmButtonPlus; private ButtonmButtonAngle; private ButtonmButtonDegree; private ButtonmButtonF6;
class AssertPair{ public AssertPair(CharSequence quest, CharSequence res){ question = quest; result = res; } boolean assertResult(){ return (question.toString().compareTo(result.toString()) == 0); } public CharSequence question; public CharSequence result; }
ArrayList<AssertPair> assertList = new ArrayList<AssertPair>(); AssertPair mAssertPair; FailureInfo mFailureInfo;
@SuppressWarnings("deprecation") public CalculatorMainActivityTest() { super("calculator.xwg", CalculatorMainActivity.class); //TODO Auto-generated constructor stub }
@Override protectedvoid setUp()throws Exception { super.setUp(); mInstrumentation = getInstrumentation(); setActivityInitialTouchMode(false);
mActivity = (CalculatorMainActivity) getActivity(); mQuestionText = (TextView)mActivity.findViewById(calculator.xwg.R.id.textQuestion); mAnswerText = (TextView)mActivity.findViewById(calculator.xwg.R.id.textAnswer); mCalculateButton = (Button)mActivity.findViewById(calculator.xwg.R.id.button74); mButton1 = (Button)mActivity.findViewById(calculator.xwg.R.id.button60); mButtonPlus = (Button)mActivity.findViewById(calculator.xwg.R.id.button63); mButtonAngle = (Button)mActivity.findViewById(calculator.xwg.R.id.button03); mButtonDegree = (Button)mActivity.findViewById(calculator.xwg.R.id.button02); mButtonF6 = (Button)mActivity.findViewById(calculator.xwg.R.id.buttonF6);
mActivity.engine.clearCustomFunctions(); } // end of setUp() method definition
@Override protectedvoid tearDown()throws Exception { mActivity.engine.clearCustomFunctions(); super.tearDown(); }
publicvoid testPreConditions() { //assertTrue(mSpinner.getOnItemSelectedListener() != null); //assertTrue(mPlanetData != null); //assertEquals(mPlanetData.getCount(),ADAPTER_COUNT); } // end of testPreConditions() method definition
publicvoid testCalculate() { LinkedList<AssertPair> checkItemList =new LinkedList<AssertPair>(); checkItemList.add(new AssertPair("1-2×((3+4)/58+8)×25%","-3.06034482759")); checkItemList.add(new AssertPair("root(16,4)","2")); checkItemList.add(new AssertPair("sin(0°)","0")); checkItemList.add(new AssertPair("sin(30°)","0.5")); checkItemList.add(new AssertPair("sin(390°)","0.5")); checkItemList.add(new AssertPair("sin(-330°)","0.5")); checkItemList.add(new AssertPair("sin(90°)","1")); checkItemList.add(new AssertPair("sin(145°)","0.573576436351")); checkItemList.add(new AssertPair("sin(180°)","0")); checkItemList.add(new AssertPair("sin(200°)","-0.342020143326")); checkItemList.add(new AssertPair("sin(270°)","-1")); checkItemList.add(new AssertPair("sin(300°)","-0.866025403784")); checkItemList.add(new AssertPair("sin(360°)","0")); checkItemList.add(new AssertPair("sin(π/6)","0.5")); checkItemList.add(new AssertPair("tan(0)","0")); checkItemList.add(new AssertPair("tan(0.2)","0.202710035509")); checkItemList.add(new AssertPair("tan(0.4)","0.422793218738")); checkItemList.add(new AssertPair("tan(0.6)","0.684136808342")); checkItemList.add(new AssertPair("tan(0.8)","1.02963855705")); checkItemList.add(new AssertPair("tan(1.0)","1.55740772465")); checkItemList.add(new AssertPair("tan(1.2)","2.57215162213")); checkItemList.add(new AssertPair("tan(1.4)","5.79788371548")); checkItemList.add(new AssertPair("tan(1.8)","-4.28626167463")); checkItemList.add(new AssertPair("tan(2.0)","-2.18503986326")); checkItemList.add(new AssertPair("tan(2.2)","-1.37382305677")); checkItemList.add(new AssertPair("tan(2.4)","-0.916014289673")); checkItemList.add(new AssertPair("tan(2.6)","-0.60159661309")); checkItemList.add(new AssertPair("tan(2.8)","-0.355529831651")); checkItemList.add(new AssertPair("tan(3.0)","-0.142546543074")); checkItemList.add(new AssertPair("tan(3.2)","0.0584738544596")); checkItemList.add(new AssertPair("tan(π/2)","tan:Invalid input.")); checkItemList.add(new AssertPair("tan(π)","0")); checkItemList.add(new AssertPair("tan(π/2×3)","tan:Invalid input.")); checkItemList.add(new AssertPair("tan(2×π)","0")); checkItemList.add(new AssertPair("tan(45°)","1")); checkItemList.add(new AssertPair("tan(90°)","tan:Invalid input.")); checkItemList.add(new AssertPair("tan(405°)","1")); checkItemList.add(new AssertPair("tan(-315°)","1")); checkItemList.add(new AssertPair("tan(-675°)","1")); checkItemList.add(new AssertPair("1+1","2")); checkItemList.add(new AssertPair("(1+2i)×(3-4i)/(5+6i)","1.0983607-0.91803279i"));
for(final AssertPair pair : checkItemList){ mActivity.runOnUiThread( new Runnable() { publicvoid run() { mQuestionText.setText(pair.question); mActivity.onButtonClick(mCalculateButton); } } ); mInstrumentation.waitForIdleSync(); assertEquals("The result of " + pair.question +":", pair.result,mAnswerText.getText()); } }
publicvoid testConvert() { mActivity.runOnUiThread( new Runnable() { publicvoid run() { mQuestionText.setText("1-i"); mActivity.onButtonClick(mButtonAngle); } } ); mInstrumentation.waitForIdleSync(); assertEquals("1.4142136∠5.4977871",mAnswerText.getText()); mActivity.runOnUiThread( new Runnable() { publicvoid run() { mQuestionText.setText("1-i"); mActivity.onButtonClick(mButtonDegree); } } ); mInstrumentation.waitForIdleSync(); assertEquals("1.4142136∠315°",mAnswerText.getText()); }
publicvoid testContinue() { mActivity.runOnUiThread( new Runnable() { publicvoid run() { mQuestionText.setText("1-i"); mActivity.onButtonClick(mButtonAngle); assertList.add(new AssertPair("1.4142136∠5.4977871",mAnswerText.getText())); mActivity.onButtonClick(mButtonDegree); assertList.add(new AssertPair("1.4142136∠315°",mAnswerText.getText())); } } ); mInstrumentation.waitForIdleSync(); for(AssertPair pair :assertList){ assertEquals(pair.question, pair.result); } assertList.clear(); }
publicvoid testCustomFunction() { mActivity.runOnUiThread( new Runnable() { publicvoid run() { mActivity.onButtonClick(mButtonF6); mActivity.onButtonClick("5"); mActivity.onButtonClick(")"); mActivity.onButtonClick("=");
mFailureInfo = FailureInfo.checkFailure("Unknown keyword:F",mAnswerText.getText()); if(mFailureInfo !=null)return;
mQuestionText.setText("π×#1×#1"); mActivity.onButtonClick("FS"); TextView tv = (TextView)mActivity.mDialog.findViewById(calculator.xwg.R.id.inputText); tv.setText("Test"); Button ok = (Button)mActivity.mDialog.findViewById(calculator.xwg.R.id.buttonOK); ok.performClick(); ListView lv = (ListView)((AlertDialog)mActivity.mDialog).getListView(); lv.requestFocus(); lv.setSelection(5); lv.performItemClick(null, 5, 5);
mActivity.onButtonClick(mButtonF6); mActivity.onButtonClick("5"); mActivity.onButtonClick(")"); mActivity.onButtonClick("=");
mFailureInfo = FailureInfo.checkFailure("78.5398163397",mAnswerText.getText()); if(mFailureInfo !=null)return;
mActivity.engine.clearCustomFunctions(); } } ); mInstrumentation.waitForIdleSync(); if(mFailureInfo !=null){ assertEquals(mFailureInfo.mExpect,mFailureInfo.mResult); mFailureInfo =null; } } } |
步驟3:修改AndroidManifest.xml文件
<?xmlversion="1.0" encoding="utf-8"?> <manifestxmlns:android="http://schemas.android.com/apk/res/android" package="calculator.xwg" android:versionCode="1" android:versionName="1.0"> <uses-sdkandroid:minSdkVersion="8" /> <uses-permissionandroid:name="android.permission.INTERNET"></uses-permission>
<applicationandroid:label="@string/app_name" android:icon="@drawable/calculator"> <uses-libraryandroid:name="android.test.runner"/> <activityandroid:name=".CalculatorMainActivity" android:label="@string/app_name"> <intent-filter> <actionandroid:name="android.intent.action.MAIN" /> <categoryandroid:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activityandroid:name="HelpActivity"></activity>
</application>
<instrumentation android:name="android.test.InstrumentationTestRunner" android:targetPackage="calculator.xwg" android:label="Tests for TT calculator."/> </manifest> |
注意:綠色背景爲添加部分。其中android:label="Testsfor TT calculator."在模擬器中的Dev Tools工具中,設置Instrumentation下顯示的名稱,在步驟4運行中,點擊該標籤,也可運行單元測試。在ThreadingTest AndroidJUnit Test中也是重要一步,能夠設置多個單元,多個名稱,每一個名稱儘可能不要重複,以區分不一樣的單元測試。
步驟4:運行測試類
運行測試類有三種方式,包括:
一、 命令行方式
使用adb命令啓動單元測試
二、 eclipse中選擇Android JUnit Test運行方式
在eclipse中,右擊測試工程,選擇run as -> AndroidJUnit Test
三、 模擬器中,使用Dev Tools
在模擬器中安裝該程序後,在Dev Tools工具中,選擇Instrumentation下顯示的與android:label同名的標籤運行單元測試。
四、 ThreadingTest單元測試的編寫
要使用ThreadingTest進行單元測試類的編寫,須要將全部的測試類寫到一個單獨的文件夾下,在進行編譯工程時,使用參數-filter將其從編譯路徑中排除,不對其進行插樁。以下圖所示:
被測源碼放在src文件夾中,單元測試驅動代碼放在test_src文件下。
其餘方面,須要在Android單元測試上進行的修改成:
一、引入ThreadingTestAndroid jar包,JavaParser-Android.jar和通訊包jeromq-0.3.0-SNAPSHOT.jar。
二、將繼承AndroidJUnit Test類換成繼承ThreadingTest Android JUnit Test類,ThreadingTest Android JUnit Test類中與AndroidJUnit Test相對應的類爲在類名前加TT。例如與InstrumentationTestCase相對應的類爲TTInstrumentationTestCase,TT AndroidJUnit Test類所在的包爲com.zoa.android.test。
三、在重載了setUp和tearDown函數的測試類中,須要分別調用super.setUp()和super.tearDown()函數,不然沒法對數據進行驚醒測試用例的區分,若是沒無需進行初始化和資源釋放則不用繼承setUp()和tearDown()函數。
四、其餘操做同Android JUnit Test操做相同。
以下爲一個與上面Android JUnitTest單元測試類相對應的TT Android JUnit Test單元測試類,添加背景色的爲須要注意的點,其餘的與原測試代碼相同,無需改動:
package calculator.xwg.test;
import java.util.ArrayList; import java.util.LinkedList; import com.zoa.android.test.TTActivityInstrumentationTestCase2; import android.app.AlertDialog; import android.app.Instrumentation; import android.widget.Button; import android.widget.ListView; import android.widget.TextView; import calculator.xwg.CalculatorMainActivity;
publicclass CalculatorMainActivityTestextends TTActivityInstrumentationTestCase2<CalculatorMainActivity> { …// 與上面相同的代碼和描述
@Override protectedvoid setUp()throws Exception { super.setUp(); //如重寫setUp函數,此句爲必須 …// 與上面相同的代碼和描述 } // end of setUp() method definition
@Override protectedvoid tearDown()throws Exception { …// 與上面相同的代碼和描述 super.tearDown(); //如重寫tearDown函數,此句爲必須 }
…// 與上面相同的代碼和描述 } |
五、 ThreadingTest單元測試的編譯
編寫完成TT Android JUnitTest 測試類以後,使用TT對工程進行編譯。
一、修改編譯文件,若是是單一編碼格式的工程,須要修改%TT_Path%/ant-build-a/android-instru_en.xml文件,多種編碼格式源碼的工程,則須要修改%TT_Path%/ant-build-a/android-instru_code_en.xml文件。修改內容爲,添加-filter參數,修改方法爲:
添加filterpath屬性,用於過濾不須要插樁的單元測試驅動類文件。
<propertylocation="不須要插樁編譯的路徑,這裏爲test_src路徑"name="filterpath"/>
…
<argline=""${propath}" "${TT}" -tt="${testpropath}"-encoding=${encoding}-filter="${filterpath}"-s="${testpropath}/src""/>
在運行JavaParser處,添加綠色背景參數。
注:demo版本中需手動修改,後續版本將對此進行改進。
二、打開TT,建立一個新的工程,與非單元測試工程的編譯相同。詳見:http://www.threadingtest.com/xwiki/bin/view/ZOA%7C4.演示/Android+程序測試配置
六、 ThreadingTest單元測試的運行
在真機中測試時,須要安裝Dev Tools工具。模擬器中附帶該工具。
將上一步中,TT編譯生成的APK文件,安裝到模擬器或真機中,安裝以後,會在Dev Tools工具下,Instrumentation中會出現與AndroidManifest.xml文件中android:label設置的同名的標籤,點擊可運行ThreadingTest單元測試。
在運行ThreadingTest單元測試時,須要打開TT接收數據,不然測試會阻塞在消息發送處而沒法進行。
模擬器測試步驟爲:
一、打開TT,選擇將要運行的測試的工程,而後打開實時監控界面,新建一個測試用例。
二、打開模擬器。
三、進行端口重定向
雙擊運行adb-android.bat,運行的命令爲:
adb forward tcp:15555tcp:15555
adb forward tcp:15556tcp:15556
adb forward tcp:15557tcp:15557
adb forward tcp:15558tcp:15558
其中adb須要在環境變量中配置好,若是未配置,也可手動切換到%Android_sdk%\platform-tools目錄下手動打開命令行窗口,運行上述命令。
若是運行adb forward –list出現:
其中,emulator-5554也多是其餘名稱,則說明配置成功。
四、勾選上測試用例,set Ip爲127.0.0.1,肯定後點擊開始。
五、打開DevTools,點擊Instrumentation進入,點擊工程中設置的標籤名,如Tests for TT calculator.
點擊後,開始運行單元測試。
此時TT實時監控界面會有數據的接收:
單元測試運行運行結束後,右擊測試用例樹右上角的刷新按鈕,選擇Update Testcase
會增長紅色框中的測試用例,首先是與勾選的測試用例同級同名的測試類型,而後依次爲測試類所在的包名,類名以及運行測試函數名。
在單元測試開始以前,被測程序的初始化數據會被保存在初始勾選的測試用例中,這裏爲UnitTest中。單元測試開始以後,產生的測試數據會被保存到,與測試驅動函數對應的測試用例當中。
TT單元測試完成。
真機測試步驟爲:
一、打開TT,選擇將要運行的測試的工程,而後打開實時監控界面,新建一個測試用例。
二、勾選上測試用例,set Ip爲真機的Ip地址,肯定後點擊開始。
三、同模擬器測試步驟5。
對移動端白盒測試技術或者性能測試感興趣,請加入羣符號執行 339834199
軟件試用申請官網:www.threadingtest.com