Android Studio 單元測試與模擬測試詳解
阿新 • • 發佈:2019-02-19
測試的分類
單元測試
位於:module-name/src/test/java/. 這些測試執行在本地JVM和沒有訪問Android框架api功能 設定環境: 您還需要為您的專案配置測試依賴使用標準api提供的JUnit 4的框架。如果您的測試需要與Android依賴關係,包括 當地Mockito(詳解見文章的下面內容)庫來簡化你的單元測試。 在你的應用程式的頂層。gradle檔案時,您需要指定這些庫的依賴關係: dependencies { // Required -- JUnit 4 framework testCompile 'junit:junit:4.12'(這個建立工程的時候已經存在) // Optional -- Mockito framework testCompile 'org.mockito:mockito-core:1.10.19' } 測試例子:(方法名字不用test開頭) 方法名可以採用一種格式,如: [測試的方法]_[測試的條件]_[符合預期的結果] public class AddTest { @Test public void add() { assertEquals(new MainActivity().add(2,3),5); } }
Mockito詳解: 1、驗證行為 @Test public void verify_behaviour(){ //模擬建立一個List物件 List mock = mock(List.class); //使用mock的物件 mock.add(1); mock.clear(); //驗證add(1)和clear()行為是否發生 verify(mock).add(1); verify(mock).clear(); } 2、模擬我們所期望的結果 @Test public void when_thenReturn(){ //mock一個Iterator類 Iterator iterator = mock(Iterator.class); //預設當iterator呼叫next()時第一次返回hello,第n次都返回world when(iterator.next()).thenReturn("hello").thenReturn("world"); //使用mock的物件 String result = iterator.next() + " " + iterator.next() + " " + iterator.next(); //驗證結果 assertEquals("hello world world",result); } @Test(expected = IOException.class) public void when_thenThrow() throws IOException { OutputStream outputStream = mock(OutputStream.class); OutputStreamWriter writer = new OutputStreamWriter(outputStream); //預設當流關閉時丟擲異常 doThrow(new IOException()).when(outputStream).close(); outputStream.close(); } 3、引數匹配 @Test public void with_arguments(){ Comparable comparable = mock(Comparable.class); //預設根據不同的引數返回不同的結果 when(comparable.compareTo("Test")).thenReturn(1); when(comparable.compareTo("Omg")).thenReturn(2); assertEquals(1, comparable.compareTo("Test")); assertEquals(2, comparable.compareTo("Omg")); //對於沒有預設的情況會返回預設值 assertEquals(0, comparable.compareTo("Not stub")); } 除了匹配製定引數外,還可以匹配自己想要的任意引數 @Test public void with_unspecified_arguments(){ List list = mock(List.class); //匹配任意引數 when(list.get(anyInt())).thenReturn(1); when(list.contains(argThat(new IsValid()))).thenReturn(true); assertEquals(1, list.get(1)); assertEquals(1, list.get(999)); assertTrue(list.contains(1)); assertTrue(!list.contains(3)); } private class IsValid extends ArgumentMatcher<List>{ @Override public boolean matches(Object o) { return o == 1 || o == 2; } } 需要注意的是如果你使用了引數匹配,那麼所有的引數都必須通過matchers來匹配 @Test public void all_arguments_provided_by_matchers(){ Comparator comparator = mock(Comparator.class); comparator.compare("nihao","hello"); //如果你使用了引數匹配,那麼所有的引數都必須通過matchers來匹配 verify(comparator).compare(anyString(),eq("hello")); //下面的為無效的引數匹配使用 //verify(comparator).compare(anyString(),"hello"); } 4、驗證確切的呼叫次數 @Test public void verifying_number_of_invocations(){ List list = mock(List.class); list.add(1); list.add(2); list.add(2); list.add(3); list.add(3); list.add(3); //驗證是否被呼叫一次,等效於下面的times(1) verify(list).add(1); verify(list,times(1)).add(1); //驗證是否被呼叫2次 verify(list,times(2)).add(2); //驗證是否被呼叫3次 verify(list,times(3)).add(3); //驗證是否從未被呼叫過 verify(list,never()).add(4); //驗證至少呼叫一次 verify(list,atLeastOnce()).add(1); //驗證至少呼叫2次 verify(list,atLeast(2)).add(2); //驗證至多呼叫3次 verify(list,atMost(3)).add(3); } 5、模擬方法體丟擲異常 @Test(expected = RuntimeException.class) public void doThrow_when(){ List list = mock(List.class); doThrow(new RuntimeException()).when(list).add(1); list.add(1); } 6、驗證執行順序 @Test public void verification_in_order(){ List list = mock(List.class); List list2 = mock(List.class); list.add(1); list2.add("hello"); list.add(2); list2.add("world"); //將需要排序的mock物件放入InOrder InOrder inOrder = inOrder(list,list2); //下面的程式碼不能顛倒順序,驗證執行順序 inOrder.verify(list).add(1); inOrder.verify(list2).add("hello"); inOrder.verify(list).add(2); inOrder.verify(list2).add("world"); } 7、確保模擬物件上無互動發生 @Test public void verify_interaction(){ List list = mock(List.class); List list2 = mock(List.class); List list3 = mock(List.class); list.add(1); verify(list).add(1); verify(list,never()).add(2); //驗證零互動行為 verifyZeroInteractions(list2,list3); } 8、找出冗餘的互動(即未被驗證到的) @Test(expected = NoInteractionsWanted.class) public void find_redundant_interaction(){ List list = mock(List.class); list.add(1); list.add(2); verify(list,times(2)).add(anyInt()); //檢查是否有未被驗證的互動行為,因為add(1)和add(2)都會被上面的anyInt()驗證到,所以下面的程式碼會通過 verifyNoMoreInteractions(list); List list2 = mock(List.class); list2.add(1); list2.add(2); verify(list2).add(1); //檢查是否有未被驗證的互動行為,因為add(2)沒有被驗證,所以下面的程式碼會失敗丟擲異常 verifyNoMoreInteractions(list2); } 9、使用註解來快速模擬 在上面的測試中我們在每個測試方法裡都mock了一個List物件,為了避免重複的mock,是測試類更具有可讀性,我們可以使用下面的註解方式來快速模擬物件: @Mock private List mockList; OK,我們再用註解的mock物件試試 @Test public void shorthand(){ mockList.add(1); verify(mockList).add(1); } 執行這個測試類你會發現報錯了,mock的物件為NULL,為此我們必須在基類中新增初始化mock的程式碼 public class MockitoExample2 { @Mock private List mockList; public MockitoExample2(){ MockitoAnnotations.initMocks(this); } @Test public void shorthand(){ mockList.add(1); verify(mockList).add(1); } } 或者使用built-in runner:MockitoJUnitRunner @RunWith(MockitoJUnitRunner.class) public class MockitoExample2 { @Mock private List mockList; @Test public void shorthand(){ mockList.add(1); verify(mockList).add(1); } } 更多的註解還有@Captor,@Spy,@InjectMocks 10、連續呼叫 @Test(expected = RuntimeException.class) public void consecutive_calls(){ //模擬連續呼叫返回期望值,如果分開,則只有最後一個有效 when(mockList.get(0)).thenReturn(0); when(mockList.get(0)).thenReturn(1); when(mockList.get(0)).thenReturn(2); when(mockList.get(1)).thenReturn(0).thenReturn(1).thenThrow(new RuntimeException()); assertEquals(2,mockList.get(0)); assertEquals(2,mockList.get(0)); assertEquals(0,mockList.get(1)); assertEquals(1,mockList.get(1)); //第三次或更多呼叫都會丟擲異常 mockList.get(1); } 11、使用回撥生成期望值 @Test public void answer_with_callback(){ //使用Answer來生成我們我們期望的返回 when(mockList.get(anyInt())).thenAnswer(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { Object[] args = invocation.getArguments(); return "hello world:"+args[0]; } }); assertEquals("hello world:0",mockList.get(0)); assertEquals("hello world:999",mockList.get(999)); } 12、監控真實物件 使用spy來監控真實的物件,需要注意的是此時我們需要謹慎的使用when-then語句,而改用do-when語句 @Test(expected = IndexOutOfBoundsException.class) public void spy_on_real_objects(){ List list = new LinkedList(); List spy = spy(list); //下面預設的spy.get(0)會報錯,因為會呼叫真實物件的get(0),所以會丟擲越界異常 //when(spy.get(0)).thenReturn(3); //使用doReturn-when可以避免when-thenReturn呼叫真實物件api doReturn(999).when(spy).get(999); //預設size()期望值 when(spy.size()).thenReturn(100); //呼叫真實物件的api spy.add(1); spy.add(2); assertEquals(100,spy.size()); assertEquals(1,spy.get(0)); assertEquals(2,spy.get(1)); verify(spy).add(1); verify(spy).add(2); assertEquals(999,spy.get(999)); spy.get(2); } 13、修改對未預設的呼叫返回預設期望值 @Test public void unstubbed_invocations(){ //mock物件使用Answer來對未預設的呼叫返回預設期望值 List mock = mock(List.class,new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { return 999; } }); //下面的get(1)沒有預設,通常情況下會返回NULL,但是使用了Answer改變了預設期望值 assertEquals(999, mock.get(1)); //下面的size()沒有預設,通常情況下會返回0,但是使用了Answer改變了預設期望值 assertEquals(999,mock.size()); } 14、捕獲引數來進一步斷言 @Test public void capturing_args(){ PersonDao personDao = mock(PersonDao.class); PersonService personService = new PersonService(personDao); ArgumentCaptor<Person> argument = ArgumentCaptor.forClass(Person.class); personService.update(1,"jack"); verify(personDao).update(argument.capture()); assertEquals(1,argument.getValue().getId()); assertEquals("jack",argument.getValue().getName()); } class Person{ private int id; private String name; Person(int id, String name) { this.id = id; this.name = name; } public int getId() { return id; } public String getName() { return name; } } interface PersonDao{ public void update(Person person); } class PersonService{ private PersonDao personDao; PersonService(PersonDao personDao) { this.personDao = personDao; } public void update(int id,String name){ personDao.update(new Person(id,name)); } } 15、真實的部分mock @Test public void real_partial_mock(){ //通過spy來呼叫真實的api List list = spy(new ArrayList()); assertEquals(0,list.size()); A a = mock(A.class); //通過thenCallRealMethod來呼叫真實的api when(a.doSomething(anyInt())).thenCallRealMethod(); assertEquals(999,a.doSomething(999)); } class A{ public int doSomething(int i){ return i; } } 16、重置mock @Test public void reset_mock(){ List list = mock(List.class); when(list.size()).thenReturn(10); list.add(1); assertEquals(10,list.size()); //重置mock,清除所有的互動和預設 reset(list); assertEquals(0,list.size()); } 運用樣例: @RunWith(MockitoJUnitRunner.class) public class UnitTestMock { private static final String FAKE_STRING = "HELLO WORLD"; @Mock Context mMockContext; @Test public void readStringFromContext_LocalizedString() { // Given a mocked Context injected into the object under test... when(mMockContext.getString(R.string.hello_word)) .thenReturn(FAKE_STRING); ClassUnderTest myObjectUnderTest = new ClassUnderTest(mMockContext); // ...when the string is returned from the object under test... String result = myObjectUnderTest.getHelloWorldString(); // ...then the result should be the expected one. assertThat(result, is(FAKE_STRING)); } }
2.模擬測試
位於:module-name/src/androidTest/java/. 這些測試必須執行在一個Android硬體裝置或一個Android模擬器。 引用: dependencies { androidTestCompile 'com.android.support:support-annotations:23.0.1' androidTestCompile 'com.android.support.test:runner:0.4.1' androidTestCompile 'com.android.support.test:rules:0.4.1' // Optional -- Hamcrest library androidTestCompile 'org.hamcrest:hamcrest-library:1.3' // Optional -- UI testing with Espresso androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1' // Optional -- UI testing with UI Automator androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.1' }
藉助到的類庫:
Espresso(內部測試) 測試框架提供了一組 API 來構建 UI 測試,用於測試應用中的使用者流。利用這些 API,您可以編寫簡潔、執行可靠的自動化 UI 測試。
Espresso 非常適合編寫白盒自動化測試,其中測試程式碼將利用所測試應用的實現程式碼詳情。
Espresso 測試框架的主要功能包括:
靈活的 API,用於目標應用中的檢視和介面卡匹配。
一組豐富的操作 API,用於自動化 UI 互動。如需瞭解詳細資訊,請參閱操作 API。
UI 執行緒同步,用於提升測試可靠性。
要求 Android 2.2(API 級別 8)或更高版本。
谷歌官方提供用於UI互動測試:
@RunWith(AndroidJUnit4.class)
public class EspressoExample {
@Rule
public ActivityTestRule<MainActivity> rule =
new ActivityTestRule(MainActivity.class, true,
// 這個引數為false,不讓SecondActivity自動啟動
// 如果為true,將會在所有@Before之前啟動,在最後一個@After之後關閉
false);
@Test
public void EsTest(){
Intent intent = new Intent();
// 啟動SecondActivity並傳入intent
rule.launchActivity(intent);
onView(withId(R.id.button)).perform(click()).check(matches(isDisplayed()));
// 按返回鍵
pressBack();
// 對於Id為R.id.button的View: 檢測內容是否是"Start new activity"
onView(withId(R.id.button)).check(matches(withText(("Start new activity"))));
// 對於Id為R.id.viewId的View: 檢測內容是否不包含"YYZZ"
onView(withId(R.id.button)).check(matches(withText(not(containsString("YYZZ")))));
// 對於Id為R.id.inputField的View: 輸入"NewText",然後關閉軟鍵盤
onView(withId(R.id.button)).perform(typeText("NewText"), closeSoftKeyboard());
// 對於Id為R.id.inputField的View: 清除內容
onView(withId(R.id.button)).perform(clearText());
}
}
啟動一個開啟Activity的Intent:
@RunWith(AndroidJUnit4.class)
public class SecondActivityTest {
@Rule
public ActivityTestRule<SecondActivity> rule =
new ActivityTestRule(SecondActivity.class, true,
// 這個引數為false,不讓SecondActivity自動啟動
// 如果為true,將會在所有@Before之前啟動,在最後一個@After之後關閉
false);
@Test
public void demonstrateIntentPrep() {
Intent intent = new Intent();
intent.putExtra("EXTRA", "Test");
// 啟動SecondActivity並傳入intent
rule.launchActivity(intent);
// 對於Id為R.id.display的View: 檢測內容是否是"Text"
onView(withId(R.id.display)).check(matches(withText("Test")));
}
}
非同步互動:
建議關閉裝置中”設定->開發者選項中”的動畫,因為這些動畫可能會是的Espresso在檢測非同步任務的時候產生混淆: 視窗動畫縮放
(Window animation scale)、過渡動畫縮放(Transition animation scale)、動畫程式時長縮放(Animator duration scale)。
針對AsyncTask,在測試的時候,如觸發點選事件以後拋了一個AsyncTask任務,在測試的時候直接onView(withId(R.id.update)).perform(click()),
然後直接進行檢測,此時的檢測就是在AsyncTask#onPostExecute之後。
// 通過實現IdlingResource,block住當非空閒的時候,當空閒時進行檢測,非空閒的這段時間處理非同步事情
public class IntentServiceIdlingResource implements IdlingResource {
ResourceCallback resourceCallback;
private Context context;
public IntentServiceIdlingResource(Context context) { this.context = context; }
@Override public String getName() { return IntentServiceIdlingResource.class.getName(); }
@Override public void registerIdleTransitionCallback( ResourceCallback resourceCallback) { this.resourceCallback = resourceCallback; }
@Override public boolean isIdleNow() {
// 是否是空閒
// 如果IntentService 沒有在執行,就說明非同步任務結束,IntentService特質就是啟動以後處理完Intent中的事務,理解關閉自己
boolean idle = !isIntentServiceRunning();
if (idle && resourceCallback != null) {
// 回撥告知非同步任務結束
resourceCallback.onTransitionToIdle();
}
return idle;
}
private boolean isIntentServiceRunning() {
ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
// Get all running services
List<ActivityManager.RunningServiceInfo> runningServices = manager.getRunningServices(Integer.MAX_VALUE);
// check if our is running
for (ActivityManager.RunningServiceInfo info : runningServices) {
if (MyIntentService.class.getName().equals(info.service.getClassName())) {
return true;
}
}
return false;
}
}
// 使用IntentServiceIdlingResource來測試,MyIntentService服務啟動結束這個非同步事務,之後的結果。
@RunWith(AndroidJUnit4.class)
public class IntegrationTest {
@Rule
public ActivityTestRule rule = new ActivityTestRule(MainActivity.class);
IntentServiceIdlingResource idlingResource;
@Before
public void before() {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
Context ctx = instrumentation.getTargetContext();
idlingResource = new IntentServiceIdlingResource(ctx);
// 註冊這個非同步監聽
Espresso.registerIdlingResources(idlingResource);
}
@After
public void after() {
// 取消註冊這個非同步監聽
Espresso.unregisterIdlingResources(idlingResource);
}
@Test
public void runSequence() {
// MainActivity中點選R.id.action_settings這個View的時候,會啟動MyIntentService
onView(withId(R.id.action_settings)).perform(click());
// 這時候IntentServiceIdlingResource#isIdleNow會返回false,因為MyIntentService服務啟動了
// 這個情況下,這裡會block住.............
// 直到IntentServiceIdlingResource#isIdleNow返回true,並且回調了IntentServiceIdlingResource#onTransitionToIdle
// 這個情況下,繼續執行,這時我們就可以測試非同步結束以後的情況了。
onView(withText("Broadcast")).check(matches(notNullValue()));
}
}
自定義匹配器:
// 定義
public static Matcher<View> withItemHint(String itemHintText) {
checkArgument(!(itemHintText.equals(null)));
return withItemHint(is(itemHintText));
}
public static Matcher<View> withItemHint(final Matcher<String> matcherText) {
checkNotNull(matcherText);
return new BoundedMatcher<View, EditText>(EditText.class) {
@Override
public void describeTo(Description description) {
description.appendText("with item hint: " + matcherText);
}
@Override
protected boolean matchesSafely(EditText editTextField) {
// 取出hint,然後比對下是否相同
return matcherText.matches(editTextField.getHint().toString());
}
};
}
// 使用onView(withItemHint("test")).check(matches(isDisplayed()));
UI Automator(跨應用測試)
UI Automator 測試框架提供了一組 API 來構建 UI 測試,用於在使用者應用和系統應用中執行互動。利用 UI Automator API,您可以執行在測
試裝置中開啟“設定”選單或應用啟動器等操作。UI Automator 測試框架非常適合編寫黑盒自動化測試,其中的測試程式碼不依賴於目標應用的內部實現詳情。
UI Automator 測試框架的主要功能包括:
用於檢查佈局層次結構的檢視器。如需瞭解詳細資訊,
在目標裝置上檢索狀態資訊並執行操作的 API。
支援跨應用 UI 測試的 API。如需瞭解詳細資訊,
要求 Android 4.3(API 級別 18)或更高版本。
依賴:androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.1'
例子:
@RunWith(AndroidJUnit4.class)
@LargeTest
public class UiautomatorMainActivityTest {
private UiDevice uiDevice;
private static final int LAUNCH_TIMEOUT = 5000;//執行的時間
private static final String APPLICATION_PACKAGE
= "xiaozhang.testuiautomator";
// @Before代替setUp方法,有多個依次執行 @After 代替tearDown方法 //uiautomatorviewer 自動化介面分析工具
@Before
public void startUiautomatorMainActivityHomeScreen() {
//初始化UiDevice例項
uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
//從主螢幕
uiDevice.pressHome();
//等待執行
final String launcherPackage = getLauncherPackageName();
assertThat(launcherPackage, notNullValue());
uiDevice.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)), LAUNCH_TIMEOUT);
//啟動測試應用
Context context = InstrumentationRegistry.getContext();//獲取上下文
final Intent intent = context.getPackageManager()
.getLaunchIntentForPackage("xiaozhang.testuiautomator");
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
context.startActivity(intent);
//等待應用程式出現
uiDevice.wait(//等待5000秒
Until.hasObject(By.pkg("xiaozhang.testuiautomator")//找到滿足條件的包,取第一個
.depth(0)), LAUNCH_TIMEOUT);
}
@Test
public void testChangeText_sameActivity() {
//點選button的操作,執行輸入文字,
uiDevice.findObject(By.res(APPLICATION_PACKAGE, "main_bt1"))
.click();
try {
uiDevice.sleep();
} catch (RemoteException e) {
e.printStackTrace();
}
//uiDevice.waitForIdle(6000);//空虛時間6秒
uiDevice.findObject(By.res(APPLICATION_PACKAGE, "main_edit"))
.setText("程式碼輸入的額");
//檢測文字的顯示內容
UiObject2 changedMain_EditText = uiDevice.wait //uidevice.wait 裝置等待500毫秒
(Until.findObject(By.res(APPLICATION_PACKAGE, "main_edit"))//通過Until工具查詢到,main_eidt
, 2000);
assertThat(changedMain_EditText.getText(), is("程式碼輸入的額"));
}
@Test //檢測測試條件是不是為空
public void checkPreconditions() {
assertThat(uiDevice, notNullValue());
}
@After
public void finishUiautomatorMainActivityHomeScreen() {
uiDevice = null;
}
/**
* 獲取執行的包
*
* @return
*/
private String getLauncherPackageName() {
//建立啟動intent
final Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
//使用manage獲取執行的包名
PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
ResolveInfo resolveInfo = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
return resolveInfo.activityInfo.packageName;
}
}
測試的主介面:
package xiaozhang.testuiautomator;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
/**
* Created by zhang on 2016/6/4.
*/
public class UIautomatorMainActivity extends Activity implements View.OnClickListener {
private Button main_bt1;
private EditText main_edit;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_uiautomator_main_layout);
main_bt1= (Button) findViewById(R.id.main_bt1);
main_bt1.setOnClickListener(this);
main_edit= (EditText) findViewById(R.id.main_edit);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.main_bt1:
main_edit.setText("這是點選按鈕,輸入的文字!");
break;
}
}
}
佈局檔案:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/main_bt1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="測試" />
<EditText
android:id="@+id/main_edit"
android:layout_width="match_parent"
android:layout_height="300dp"
android:gravity="start"
android:hint="內容" />
</LinearLayout>
AndroidJUnitRunner:
AndroidJUnitRunner 類是一個 JUnit 測試執行器,可讓您在 Android 裝置上執行 JUnit 3 或 JUnit 4 樣式測試類,
包括使用 Espresso 和 UI Automator 測試框架的裝置。測試執行器可以將測試軟體包和要測試的應用載入到裝置、執行測試
並報告測試結果。此類將替換 InstrumentationTestRunner 類,後者僅支援 JUnit 3 測試。
JUnit 支援
測試執行器與 JUnit 3 和 JUnit 4(最高版本為 JUnit 4.10)測試相容。不過,請勿在同一軟體包中混用 JUnit 3 和 JUnit 4 測試程式碼,
因為這可能會導致意外結果。如果要建立一個 JUnit 4 儀器測試類以在裝置或模擬器上執行,則測試類必須以 @RunWith(AndroidJUnit4.class) 註解作為字首。
以下程式碼段顯示瞭如何編寫 JUnit 4 儀器測試來驗證 CalculatorActivity 類中的 add 操作是否正常工作。
import android.support.test.runner.AndroidJUnit4;
import android.support.test.runner.AndroidJUnitRunner;
import android.test.ActivityInstrumentationTestCase2;
@RunWith(AndroidJUnit4.class)
public class CalculatorInstrumentationTest
extends ActivityInstrumentationTestCase2<CalculatorActivity> {
@Before
public void setUp() throws Exception {
super.setUp();
// Injecting the Instrumentation instance is required
// for your test to run with AndroidJUnitRunner.
injectInstrumentation(InstrumentationRegistry.getInstrumentation());
mActivity = getActivity();
}
@Test
public void typeOperandsAndPerformAddOperation() {
// Call the CalculatorActivity add() method and pass in some operand values, then
// check that the expected value is returned.
}
@After
public void tearDown() throws Exception {
super.tearDown();
}
}
訪問儀器資訊:
您可以使用 InstrumentationRegistry 類訪問與測試執行相關的資訊。此類包括 Instrumentation 物件、目標應用 Context 物件、測試
應用 Context 物件,以及傳遞到測試中的命令列引數。使用 UI Automator 框架編寫測試或編寫依賴於 Instrumentation 或 Context 對
象的測試時,此資料非常有用。
測試篩選:
在 JUnit 4.x 測試中,您可以使用註解對測試執行進行配置。此功能可將向測試中新增樣板檔案和條件程式碼的需求降至最低。除了 JUnit 4 支
持的標準註解外,測試執行器還支援 Android 特定的註解,包括:
@RequiresDevice:指定測試僅在物理裝置而不在模擬器上執行。
@SdkSupress:禁止在低於給定級別的 Android API 級別上執行測試。例如,要禁止在低於 18 的所有 API 級別上執行測試,請使用註解
@SDKSupress(minSdkVersion=18)。
@SmallTest、@MediumTest 和 @LargeTest:指定測試的執行時長以及執行頻率。
測試分片:
測試執行器支援將單一測試套件拆分成多個碎片,因此您可以將屬於同一碎片的測試作為一個組在同一 Instrumentation 例項下執行。每個分片由
一個索引號進行標識。執行測試時,使用 -e numShards 選項指定要建立的獨立分片數量,並使用 -e shardIndex 選項指定要執行哪個分片。
JUnit4使用Java5中的註解(annotation),以下是JUnit4常用的幾個annotation:
@Before:初始化方法 對於每一個測試方法都要執行一次(注意與BeforeClass區別,後者是對於所有方法執行一次)
@After:釋放資源 對於每一個測試方法都要執行一次(注意與AfterClass區別,後者是對於所有方法執行一次)
@Test:測試方法,在這裡可以測試期望異常和超時時間
@Test(expected=ArithmeticException.class)檢查被測方法是否丟擲ArithmeticException異常
@Ignore:忽略的測試方法
@BeforeClass:針對所有測試,只執行一次,且必須為static void
@AfterClass:針對所有測試,只執行一次,且必須為static void
一個JUnit4的單元測試用例執行順序為:
@BeforeClass -> @Before -> @Test -> @After -> @AfterClass;
每一個測試方法的呼叫順序為:
@Before -> @Test -> @After;