在Android Studio中使用JUnit4進行單元測試(一)【有代碼配合】html
JUnit4註解解釋 java
1. @Test : 測試方法,測試程序會運行的方法,後邊能夠跟參數表明不一樣的測試,如(expected=XXException.class) 異常測試,(timeout=xxx)超時測試android
2. @Ignore : 被忽略的測試方法ios
3. @Before: 每個測試方法以前運行編程
4. @After : 每個測試方法以後運行數組
5. @BeforeClass: 全部測試開始以前運行網絡
6. @AfterClass: 全部測試結束以後運行架構
添加依賴:app
鼠標focus要測試的函數,鼠標右鍵菜單,運行該測試函數便可。框架
package com.lp.knightoneadmin.android_junit_tutorial.junit;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@Test public void testAdd(){ int sum = mCalculator.add(3, 4); Assert.assertEquals(7,sum); }
Goto: https://www.jianshu.com/p/03118c11c199 在Android Studio中進行單元測試和UI測試 - 1.概述 在Android Studio中進行單元測試和UI測試 - 2.建立新的Android Studio工程 在Android Studio中進行單元測試和UI測試 - 3.配置支持單元測試的工程 在Android Studio中進行單元測試和UI測試 - 4.建立第一個單元測試 在Android Studio中進行單元測試和UI測試 - 5.運行單元測試 在Android Studio中進行單元測試和UI測試 - 6.配置支持Instrumentation測試的工程 在Android Studio中進行單元測試和UI測試 - 7.爲app添加簡單的交互 在Android Studio中進行單元測試和UI測試 - 8.建立並運行Espresso測試 在Android Studio中進行單元測試和UI測試 - 9.祝賀!
你的測試代碼和你創建並運行Android Studio中的測試方式的結構 取決於 測試你正在執行的 類型。
下表總結了常見Android的測試類型:
Type | Subtype | Description |
Unit tests | Local Unit Tests | Unit tests that run on your local machine only. These tests are compiled to run locally on the JVM to minimize execution time. Use this approach to run unit tests that have no dependencies on the Android framework or have dependencies that mock objects can satisfy. |
Instrumented unit tests | Unit tests that run on an Android device or emulator. These tests have access toInstrumentation information, such as the Context of the app under test. Use this approach to run unit tests that have Android dependencies which mock objects cannot easily satisfy. | |
Integration Tests | Components within your app only | This type of test verifies that the target app behaves as expected when a user performs a specific action or enters a specific input in its activities. For example, it allows you to check that the target app returns the correct UI output in response to user interactions in the app’s activities. UI testing frameworks like Espresso allow you to programmatically simulate user actions and test complex intra-app user interactions. |
Cross-app Components | This type of test verifies the correct behavior of interactions between different user apps or between user apps and system apps. For example, you might want to test that your app behaves correctly when the user performs an action in the Android Settings menu. UI testing frameworks that support cross-app interactions, such as UI Automator, allow you to create tests for such scenarios. |
Instrumentation
Android Instrumentation在安卓系統上是一組控制函數或者是hooks(鉤子)。這些鉤子在本身的生命週期獨立的控制一個安卓組件,他們也控制着安卓如何加載應用程序。
下圖總結了Instrumentation的測試框架:
一般狀況下,Android的一個組件在運行在系統指定的生命週期中。舉個例子:
Android框架的API不提供對你的代碼直接調用這些回調函數,但你能夠經過Instrumentation來完成。
系統運行一個應用的全部組件都是在同一個進程中,你可讓某些組件(例如content providers)在單獨的進程中運行,可是你不能強制讓一個應用程序和另外一個已經運行的程序運行在同一個進程中。
Instrumentation能夠同時加載。一旦你的應用程序和你的測試程序在一個進程當中了,你的測試程序就能夠調用組件中的方法,而且在組件中修改和驗證變量。
看過上述內容,有點蒙!
仍是系統的學下爲好!
Ref: Android Testing Support【教學視頻】
測試體系金字塔:
4. Other |
3. End-to-end tests |
2. Integration, UI test |
1. Unit tests |
Ref: Android 自動化測試 Espresso篇:簡介&基礎使用【看上去比較靠譜】
提問:UI測試該怎麼搞?
其實相比較於單元測試,Espresso我我的理解更傾向於像自動化集成測試,
由於這個庫實質上是開發者進行測試的時候,在測試設備上面安裝了兩個apk(開發者通常的apk和AndroidTest apk),
而後模擬操做(好比點擊按鈕,滑動列表等等)在界面上,將開發者預期的結果和實際的結果進行對比。
劣勢:Espresso測試 對比 單元測試(UnitTest),前者耦合度太大。
優點:咱們可讓Espresso處理它拿手的UI界面測試;網絡請求等業務處理,咱們能夠交給其餘測試框架去處理,好比Mockito(後文再講)。
適合MVP架構:MVP模式將數據的展現處理交給了View,業務代碼交給了Model,咱們徹底能夠經過MVP模式,將測試代碼分開來測試。
寫一段代碼:
@OnClick({R.id.btn01, R.id.btn02}) public void onViewClicked(View view) { switch (view.getId()) { case R.id.btn01: tvContent.setVisibility(View.VISIBLE); tvContent.setText("hello espresso!"); break; case R.id.btn02: tvContent.setVisibility(View.VISIBLE); tvContent.setText("success"); et01.setText(""); break; } }
爲了測試這一段代碼,須要寫多少對應的測試代碼?
@RunWith(AndroidJUnit4.class) public class A01SimpleActivityTest { @Rule public ActivityTestRule<A01SimpleActivity> rule = new ActivityTestRule<>(A01SimpleActivity.class); @Test public void clickTest() { //tvContent是否默認不顯示 onView(ViewMatchers.withId(R.id.tv_content)) .check(matches(not(isDisplayed()))); //是否不可見 //檢查btn01的text,而後執行點擊事件 onView(withId(R.id.btn01)) .check(matches(withText("修改內容"))) .perform(click()); //檢查tv內容是否修改,而且是否可見 onView(withId(R.id.tv_content)) .check(matches(withText("hello espresso!"))) .check(matches(isDisplayed())); } @Test public void loginTest() throws Exception { //先清除editText的內容,而後輸入,而後關閉軟鍵盤,最後校驗內容 //這裏若是要輸入中文,使用replaceText()方法代替typeText() onView(withId(R.id.et_01)) .perform(clearText(), replaceText("你好 username"), closeSoftKeyboard()) .check(matches(withText("你好 username"))); //點擊登陸 onView(withId(R.id.btn02)) .perform(click()); //校驗內容 onView(withId(R.id.tv_content)) .check(matches(withText("success"))) .check(matches(isDisplayed())); onView(withId(R.id.et_01)) .check(matches(withText(""))) //內容是否爲"" .check(matches(withHint("請輸入帳戶名"))) //hint內容是否爲"請輸入帳戶名" .check(matches(withHint(containsString("帳戶名")))); //hint內容是否包含"帳戶名" } }
關於測試,是個體系,也是個不小的篇章,將另起隨筆學習。
如下是不錯的學習材料:
關於DataBinding,請見博客:Android MVVM+DataBinding結合Dagger2進行開發【該博客的系列文章不錯】
Ref: Crash Reporting - Android Firebase
1. 添加依賴:firebase-crash
我的理解的關鍵,仍是合理使用try...catch編程習慣的問題。
public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; private Button btnError1, btnError2, btnError3; private EditText mText1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
btnError1 = (Button) findViewById(R.id.btnError1); btnError2 = (Button) findViewById(R.id.btnError2); btnError3 = (Button) findViewById(R.id.btnError3); mText1 = (EditText) findViewById(R.id.editText); Log.d(TAG, "onCreate: starting."); FirebaseCrash.log("onCreate started."); btnError1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { FirebaseCrash.log("btnError1 Clicked."); //this will throw an exception and report it String text = null; mText1.setText(text.toString()); // null的toString拋出異常 --> fatal error, 直接致使"程序crash" } }); btnError2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { FirebaseCrash.log("btnError2 Clicked."); String filepath = "sdcard/made-up/filepath/"; try{ File file = new File(filepath); // path是假的,但本地程序已有處理,屬於non-fatal error,firebase也會得到記錄 InputStream inputStream = new FileInputStream(file); inputStream.read(); }catch (FileNotFoundException e){ FirebaseCrash.report(new Exception( "FileNotFoundException in btnError2. Probably the filepath:" + filepath )); } catch (IOException e) { FirebaseCrash.report(new Exception( e.toString() )); } } }); btnError3.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { btnError3.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { FirebaseCrash.log("btnError3 Clicked."); ArrayList<String> theList = new ArrayList<>(); theList.add("String 1"); theList.add("String 2"); theList.add("String 3"); //this will throw an exception because the index is out of bounds for (int i = 0; i <= theList.size(); i++){ Log.d(TAG, "onClick: theList: " + theList.get(i)); // 數組index溢出 --> fatal error } } }); } }); } }
firebase得到error報告:
常見異常種類:Goto 菜鳥教程 - Java 異常處理 or [Android] 01 - Java Basic for Android