1. 程式人生 > >Android Studio 單元測試與模擬測試詳解

Android Studio 單元測試與模擬測試詳解

測試的分類

單元測試

位於: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;