Kiểm thử UI Android với Espresso.

  1.  Espresso test framework

Espresso là một framework kiểm thử giao diện cho Android được xây dựng để viết các test case giao diện một cách dễ dàng và đáng tin cậy.

Được giới thiệu bời Google vào tháng 10, 2013. Từ phiên bản 2.0, Espresso đã được tích hợp vào Android Support Repository.

Espresso tự động đồng bộ các tương tác với giao diện người dùng trên application của chúng ta. Framwork này cũng đảm bảo rằng Activity tương ứng được start trước khi chạy các test case.Các test case sẽ đợi cho tới khi tất cả các activity background được hoàn tất.

Được thiết kếđể kiểm thử 1 ứng dụng độc lập nhưng nó cũng có thể dùng để kiểm thử across application. Nếu sử dụng để kiểm thử ngoài ứng dụng của bạn. Bạn chỉ có thể thực hiện kiểm thử hộp đen (black box testing), vì bạn không thể truy cập các class ngoài ứng dụng.

Espresso về cơ bản có 3 thành phần:

  • ViewMatcher – cho phép tìm đến view (Control) trong hệ thống cấp bậc view(Giao diện)
  • ViewActions –  cho phép thực hiện tương tác lên view
  • ViewAssertions – cho phép kiểm tra, xác nhận trạng thái của view

Cấu trúc của Esspresso test như sau:

Untitled.png

  1. – Tìm thành phần giao diện (view) tương ứng.
  2. – thực hiện tương tác trên giao diện (view)
  3. – xác nhận trạng thái giao diện (view).

Đoạn code sau sẽ chứng minh cách sử dụng Espresso framework.

import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;

// image more code here...

// test statement
onView(withId(R.id.my_view)) // withId(R.id.my_view) is a ViewMatcher
 .perform(click()) // click() is a ViewAction
 .check(matches(isDisplayed())); // matches(isDisplayed()) is a ViewAssertion

// new test
onView(withId(R.id.greet_button))
.perform(click())
.check(matches(not(isEnabled()));
 

Nếu  espresso không tìm thấy view tương ứng trong ViewMatcher, nó sẽ chứa tất cả hệ thống cấp bậc view  vào trong message lỗi. Rất dễ đàng chúng ta để phát hiện ra vấn đề gây lỗi.

2. Sử dụng Espresso.

2.1 cài đặt.

Mở Android SDK manager và cài đặt Android support repository.

espresso

2.2 Cấu hình Espresso trong Gradle build file

Để sử dụng Espresso, thêm dependency sau vào Gradle build file trên project của bạn

dependencies {
 compile fileTree(dir: 'libs', include: ['*.jar'])

 testCompile 'junit:junit:4.12'

 // Android runner and rules support
 androidTestCompile 'com.android.support.test:runner:0.5'
 androidTestCompile 'com.android.support.test:rules:0.5'

 // Espresso support
 androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
 exclude group: 'com.android.support', module: 'support-annotations'
 })

 // add this for intent mocking support
 androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.2'

 // add this for webview testing support
 androidTestCompile 'com.android.support.test.espresso:espresso-web:2.2.2'

}

Đảm bảo rằng android.support.test.runner.AndroidJUnitRunner đặt trong testInstrumentationRunner trong gradle build file. Và packagingOptions, bạn có thể loại trừ LICENSE.txt, phụ thuộc vào libraries bạn sử dụng. Như ví dụ dưới đây.


apply plugin: 'com.android.application'

android {
compileSdkVersion 23
buildToolsVersion "23.0.3"

defaultConfig {
applicationId "com.bk.haodv.espressofirst"
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName "1.0"
// android.support.test.runner.AndroidJUnitRunner
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
packagingOptions {
exclude 'LICENSE.txt'
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
//as before...
}

2.3 Cài đặt device

Chúng ta nên tắt hiệu ứng animation trên thiết bị để chạy Espresso test. Animation có thể gây khó hiểu Espresso khi chạy không tải.

image.png

3. Ứng dụng Espresso đầu tiên.

3.1 tạo project

Tạo 1 Android project tên là Espresso First với package name com.bk.haodv.espressofirst chọn mẫu Blank template.

Thay đổi activity_main.xml với các control

</pre>
<pre>&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.bk.haodv.esspressotwo.MainActivity"&gt;


    &lt;EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/edt_input"
        android:layout_alignParentTop="true"
        android:hint="please input text"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="52dp" /&gt;

    &lt;Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Start new activity"
        android:id="@+id/btn_start"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" /&gt;
&lt;/RelativeLayout&gt;
</pre>
<pre>

Tạo thêm 1 file giao diện activity_second.xml


&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/tools"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:paddingBottom="@dimen/activity_vertical_margin"
 android:paddingLeft="@dimen/activity_horizontal_margin"
 android:paddingRight="@dimen/activity_horizontal_margin"
 android:paddingTop="@dimen/activity_vertical_margin"
 android:gravity="center"
 tools:context="com.bk.haodv.esspressotwo.SecondActivity"&gt;

 &lt;TextView
 android:id="@+id/tv_result"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:text="Started"/&gt;

&lt;/RelativeLayout&gt;

Tạo 1 activity mới tên là SecondActivity với nội dung như sau:


public class SecondActivity extends AppCompatActivity {

     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_second);
         TextView viewById = (TextView) findViewById(R.id.tv_result);
         Bundle inputData = getIntent().getExtras();
         String input = inputData.getString(&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;input&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;);
         viewById.setText(input);
    }
}

Trong MainActivity class:


package com.bk.haodv.espressofirst;

import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.EditText;

public class MainActivity extends AppCompatActivity {
EditText editInput;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
}
private void initViews(){
editInput = (EditText) findViewById(R.id.edt_input);
}

public void onClick(View view){
switch (view.getId()){
case R.id.btn_change_text:
editInput.setText(&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;Lalala&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;);
break;
case R.id.btn_swith_activity:
Intent intent = new Intent(this, SecondActivity.class);
Bundle bundle = new Bundle();
bundle.putString(&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;input&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;, editInput.getText().toString());
intent.putExtras(bundle);
startActivity(intent);
break;
}
}
}

3.2 Cấu hình Espresso trong build.gradle


apply plugin: 'com.android.application'

android {
compileSdkVersion 23
buildToolsVersion &amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;23.0.3&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;

defaultConfig {
applicationId &amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;com.bk.haodv.espressofirst&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName &amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;1.0&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;
// android.support.test.runner.AndroidJUnitRunner
testInstrumentationRunner &amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;android.support.test.runner.AndroidJUnitRunner&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;
}
packagingOptions {
exclude 'LICENSE.txt'
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.1.1'
// Android runner and rules support
androidTestCompile 'com.android.support.test:runner:0.5'
androidTestCompile 'com.android.support.test:rules:0.5'
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2'
}

3.3 Tạo Esoresso test case.

Tạo clas MainActivityTest trong package androidTest để kiểm thử MainActivity.


package com.bk.haodv.espressofirst;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard;
import static android.support.test.espresso.action.ViewActions.typeText;
import static android.support.test.espresso.assertion.ViewAssertions.matches;

import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;

/**
* Created by T430 on 9/1/2016.
*/

@RunWith(AndroidJUnit4.class)
public class MainActivityTest {

@Rule
public ActivityTestRule&amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;MainActivity&amp;amp;amp;amp;amp;amp;amp;amp;amp;gt; mActivityRule =
new ActivityTestRule&amp;amp;amp;amp;amp;amp;amp;amp;amp;lt;&amp;amp;amp;amp;amp;amp;amp;amp;amp;gt;(MainActivity.class);

@Test
public void ensureTextChangesWork() {
// nhập vào chuỗi hello và nhấn vào button
onView(withId(R.id.edt_input))
.perform(typeText(&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;HELLO&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;), closeSoftKeyboard());
onView(withId(R.id.btn_change_text)).perform(click());

// Kiểm tra xem khi click button có đúng chuỗi &amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;Lalala&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot; hay không (kết quả mong đợi)
onView(withId(R.id.edt_input)).check(matches(withText(&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;Lalala&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;)));
}
@Test
public void switch_newActivity() {
//&amp;amp;amp;amp;amp;amp;amp;amp;nbsp;Nhập vào dòng NewText rồi nhấn button &amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;Change Text&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;
onView(withId(R.id.edt_input)).perform(typeText(&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;NewText&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;),
closeSoftKeyboard());
onView(withId(R.id.btn_swith_activity)).perform(click());

// check xem ở SecondActivity có set đúng string &amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;NewText&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot; truyền từ MainActivity sang

// TextView &amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;tv_result&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot; ở Activity khác, nhưng Espresso sẽ tự match được, không cần khai báo gì thêm
onView(withId(R.id.tv_result)).check(matches(withText(&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;NewText&amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;)));
}
}

3.4 Chạy các test case:

Chuột phải vào class MainActivityTest chọn Run:

test-pass

Như kết quả thông báo : đã pass cả 2 test case, nếu có test case nào đó không pass, sẽ hiển thị đường màu đỏ thay vì màu xanh và rất dễ dàng tìm ra test case sai để còn Fix :D.

4. Viết thêm các test case khác với Espresso.

4.1 . Vị trí của các class Espresso tests và các static imports cần thiết

Class Espresso tests phải được đặt trong app/src/androidTest folder.

Để dễ sử dụng các API của Espresso. Chúng ta nên thêm các static imports sau. Cho phép truy cập các method mà không cần tiền tố class.


import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard;
import static android.support.test.espresso.action.ViewActions.typeText;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;

4.2. Sử dụng ViewMatcher

Để tìm 1 thành phần giao diện (view) sửa dụng phương thức onView() với 1 view matcher sẽ lấy chính xác view cần thiết. Nếu sử dụng AdapterView , ta sử dụng onData()  thay vì onView() . Phương thức onView() sẽ trả về 1 đối tượng loại ViewInteraction. phương thức onData() sẽ trả về 1 đối tượng loại DataInteraction.

Bảng dưới đây mô tả các matchers dành sẵn:

2

4.3. Tương tác trên View

ViewInteraction DataInteraction cho phép tương tác thông qua 1 đối tượng thuộc loại ViewAction với phương thức perform. ViewActions class cung cấp các method helper cho các actions phổ biến như:

  • ViewActions.click
  • ViewActions.typeText()
  • ViewActions.pressKey()
  • ViewActions.clearText()

Phương thức perform trả về 1 đối tượng thuộc ViewInteraction . Bạn có thể thực hiện thêm nhiều action để xác nhận các kết quả mong đợi. có thể thêm 1 số tham số. ví dụ, bạn có thể thực hiện nhiều actions cùng 1 lúc…

4.4 Xác thực các kết quả test.

Gọi phương thức ViewInteraction.check() để xác định trạng thái của view đã thực hiện tương tác. Phương thức này mong đợi 1 đối tượng đầu vào ViewAssertion. ViewAssertions class cung cấp các phương thức helper  để tạo những đối tượng này.

  • matchers – Hamcrest matcher
  • doesNotExist – xác định rằng view đã chọn không tồn tại

Bạn cũng có thể sử dụng Hamcrest matchers (mạnh hơn), như ví dụ dưới đây :

3.png

  1. Matches đến view, có text bắt đầu với chuỗi “ABC”.
  2. Matches đến view  có text kết thúc bởi chuỗi “YYZZ”
  3. Matches đến text của view xác định bởi R.id có chứa nội dung mô tả chứa chuỗi “YYZZ” ở bất kỳ vị trí nào.
  4. Matches đến view có text bằng với chuỗi string tham số, bỏ qua hoa thường
  5. Matches đến view có text bằng với chuỗi string tham số, bỏ qua khoảng trắng
  6. Match đến view xác đinh bởi R.id mà text không chứa chuỗi “YYZZ”.

4.5. Truy cập các API

Thông qua phương thức InstrumentationRegistry.getTargetContext(), bạn có thể truy cập đến context hiện tại trên ứng dụng của ban. VÍ dụ, nếu muốn dùng id mà không cần dùng R.id, bạn có thể sửa dụng phương thức helper sau để xác định nó.


package testing.android.vogella.com.asynctask;

import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;

@RunWith(AndroidJUnit4.class)
public class EspressoTest {

@Rule
 public ActivityTestRule&amp;amp;amp;amp;amp;lt;MainActivity&amp;amp;amp;amp;amp;gt; mActivityRule =
 new ActivityTestRule&amp;amp;amp;amp;amp;lt;&amp;amp;amp;amp;amp;gt;(MainActivity.class);

@Test
 public void buttonShouldUpdateText(){
 onView(withId(R.id.update)).perform(click());
 onView(withId(getResourceId("Click"))).check(matches(withText("Done")));
 }

private static int getResourceId(String s) {
 Context targetContext = InstrumentationRegistry.getTargetContext();
 String packageName = targetContext.getPackageName();
 return targetContext.getResources().getIdentifier(s, "id", packageName);
 }
}

4.6. Cấu hình để start intent cho Activity

Nếu tham số thứ 3 là false trong ActivityTestRule, bạn có thể cấu hình cho intent để start activity. Hãy theo dõi ví dụ sau:


package com.vogella.android.testing.espressosamples;

import android.content.Intent;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;

@RunWith(AndroidJUnit4.class)
public class SecondActivityTest {

@Rule

// tham số thứ 3 set là false nghĩa là activity không được tự động start
 public ActivityTestRule&amp;amp;amp;amp;amp;lt;SecondActivity&amp;amp;amp;amp;amp;gt; rule =
 new ActivityTestRule(SecondActivity.class, true, false);

@Test
 public void demonstrateIntentPrep() {
 Intent intent = new Intent();
 intent.putExtra("EXTRA", "Test");
 rule.launchActivity(intent);
 onView(withId(R.id.display)).check(matches(withText("Test")));
 }
}

4.7. Ghi hình phiên kiểm thử Espresso UI.

Android studio cung cấp 1 options Run-> Record Espresso Test trên thanh menu cho phép ghi lại tương tác với ứng dụng của bạn và tạo test case Espresso từ nó.

Tính năng này xuất hiện từ bản Android studio 2.2 preview 3.

4

5.png

4.8. Cấu hình Activity để thực hiện Test

Bạn cũng có thể truy cập tới Activity bạn đang test và gọi các phương thức của nó. Ví dụ:


public class MainActivity extends Activity {

@Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 }
 public void configureMainActivity(String Uri) {
 // do something with this
 }
}

Phương thức configureMainActivity có thể được như sau:


import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(AndroidJUnit4.class)

public class ExampleInstrumentedTest {

@Rule
 public ActivityTestRule&amp;amp;amp;amp;amp;lt;MainActivity&amp;amp;amp;amp;amp;gt; mActivityRule =
 new ActivityTestRule&amp;amp;amp;amp;amp;lt;MainActivity&amp;amp;amp;amp;amp;gt;(MainActivity.class);

@Test
 public void useAppContext() throws Exception {
 MainActivity activity = mActivityRule.getActivity();
 activity.configureMainActivity("http://www.vogella.com");
 // do more
 }
}

Lưu ý: Chúng ta cũng có thể override các phương thức trong ActivityTestRule, ví dụ như các phương thứcbeforeActivityLaunchedafterActivityLaunched …

Bạn cũng có thể truy cập đến Activity hiện hành.


@Test
public void navigate() {

Activity instance = getActivityInstance();
 onView(withText("Next")).perform(click());
 Activity activity = getActivityInstance();
 boolean b = (activity instanceof SecondActivity);
 assertTrue(b);
 // do more
 }

public Activity getActivityInstance() { 
 final Activity[] activity = new Activity[1];
 InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable( ) {
 public void run() {
 Activity currentActivity = null;
 Collection resumedActivities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(RESUMED);
 if (resumedActivities.iterator().hasNext()){
 currentActivity = (Activity) resumedActivities.iterator().next();
 activity[0] = currentActivity;
 }
 }
 });

return activity[0];
 }

Phương thức getActivityInstance cho phép truy cập tới activity hiện tại đang active.

Lưu ý: ActivityLifecycleMonitorRegistry không phải là API vì vậy nó có thể thay đổi.

4.9 Chạy các Espresso tests

4.9.1 Sử dụng Android studio

Click vào class Espresso và chọn Run

6.png

4.9.2. Sử dụng gradle

Sử dụng connectedCheck task trong Gradle để chạy espresso test case.

7.png

4.10. Kiểm tra 1 Toast.

Ví dụ dưới đây, chúng ta sẽ click lên 1 list item và kiểm tra 1 Toast được hiển thị lên.


import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import static android.support.test.espresso.Espresso.onData;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.hasToString;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;

@RunWith(AndroidJUnit4.class)
public class MainActivityTestList {

@Rule
 public ActivityTestRule&amp;amp;amp;amp;amp;lt;MainActivity&amp;amp;amp;amp;amp;gt; rule = new ActivityTestRule&amp;amp;amp;amp;amp;lt;&amp;amp;amp;amp;amp;gt;(MainActivity.class);

@Test
 public void ensureListViewIsPresent() throws Exception {
 onData(hasToString(containsString("Frodo"))).perform(click());
 onView(withText(startsWith("Clicked:"))).
 inRoot(withDecorView(
 not(is(rule.getActivity().
 getWindow().getDecorView())))).
 check(matches(isDisplayed()));
 }
}

5. Mocking intents với Espresso Intents

Espresso cũng cung cấp 1 option để mock intents. Cho phép kiểm tra nếu 1 activity đã start intent và phản ứng đúng nếu nhận được các kết quả trả về từ intent là chính xác

Espresso intents được cung cấp bởi com.android.support.test.espresso:espresso-intents.  Để sự dụng, tấu hình trong build.gradle.

Nếu muôn sử dụng Espresso intent trong test case của bạn. SỬ dụng IntentTestRule thay vì ActivityTestRule.


import android.support.test.espresso.intent.rule.IntentsTestRule;
import android.support.test.runner.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.intent.Intents.intended;
import static android.support.test.espresso.intent.matcher.IntentMatchers.toPackage;
import static android.support.test.espresso.matcher.ViewMatchers.withId;

@RunWith(AndroidJUnit4.class)
public class TestIntent {

@Rule
 public IntentsTestRule&amp;amp;amp;amp;amp;lt;MainActivity&amp;amp;amp;amp;amp;gt; mActivityRule =
 new IntentsTestRule&amp;amp;amp;amp;amp;lt;&amp;amp;amp;amp;amp;gt;(MainActivity.class);

@Test
 public void triggerIntentTest() {
 onView(withId(R.id.button)).perform(click());
 intended(toPackage("testing.android.vogella.com.simpleactivity"));
 }

}

Tham khảo thêm tại:
https://code.google.com/p/android-test-kit/wiki/EspressoIntentsSetupInstructions.

6. Ứng dụng thứ 2: VIết test case cho Intent với Espresso.

6.1. Tạo 1 project tên là Espresso Two, MainActivity có 1 layout gồm 1 EditText và 1 Button,

Tạo thêm 1 SecondActivity với layout có 1 TextView hiển thị kết quả

khi nhấn vào button ở MainActivty, SecondActivity sẽ start và truyền 1 string extras vào intent với key là “URL” , chuỗi truyền là “haobk4dev.wordpress.com” chẳng hạn 🙂

MainActivity:


package com.bk.haodv.esspressotwo;

import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

Button btnStart;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 btnStart = (Button)findViewById(R.id.btn_start);
 btnStart.setOnClickListener(new View.OnClickListener() {
 @Override
 public void onClick(View view) {
 startNewActivity();
 }
 });
 }
 private void startNewActivity(){
 Intent intent = new Intent(getApplicationContext(), SecondActivity.class);
 intent.putExtra("URL", "haobk4dev.wordpress.com");
 startActivity(intent);
 }
}

6.2. Viết các test case.

Viết các test case cho project này:

  • Kiểm tra layout của MainActivity có chứ buttton không?
  • Đảm bảo rằng text trên button là “Start new activity”.
  • Đảm bảo rằng nếu phương thức getActiviy.startNewActivity() được gọi. Nó sẽ kích hoạt đúng intent, và intent phải chứa string extra (hasExtra(“URL”, “haobk4dev.wordpress.com”)).

 

6.3. Xác nhận các kết quả test

Test code của chúng ta cho các test case ở trên như sau:

package com.bk.haodv.esspressotwo;

/**
 * Created by T430 on 9/7/2016.
 */

import android.support.test.espresso.intent.rule.IntentsTestRule;
import android.support.test.runner.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static android.support.test.espresso.intent.Intents.intended;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasExtra;
import static android.support.test.espresso.intent.matcher.IntentMatchers.toPackage;
import static org.hamcrest.core.IsNull.notNullValue;

@RunWith(AndroidJUnit4.class)
public class TestIntent {

 @Rule
 public IntentsTestRule&amp;amp;amp;amp;lt;MainActivity&amp;amp;amp;amp;gt; mActivityRule = new IntentsTestRule&amp;amp;amp;amp;lt;MainActivity&amp;amp;amp;amp;gt;(MainActivity.class);

 @Test
 public void triggerIntentTest() {
 // kiểm tra xem có btn_stat trong layout của MainActivty hay ko?
 onView(withId(R.id.btn_start)).check(matches(notNullValue()));
 // CHeck xem text của button trên có phải là Start new activity hay ko?
 onView(withId(R.id.btn_start)).check(matches(withText("Start new activity")));
 // check xem click vào button có start intent và extras có chính xác hay ko?
 // click vao button
 onView(withId(R.id.btn_start)).perform(click());
 // check xem start dung activity hay ko?
 intended(toPackage("com.bk.haodv.esspressotwo"));
 // check xem truyen dung string extras không?
 intended(hasExtra("URL", "http://www.vogella.com"));
 }
}

7. Ứng dụng thứ 3: Kiểm thử chức năng cho các Activity.

7.1 Mục đích:

Chúng ta sẽ start Activity và đánh giá các kết quả test:

7.2. Viết các test case chức năng cho activities.


package com.bk.haodv.esspressotwo;

import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withText;

/**
 * Created by T430 on 9/7/2016.
 */
@RunWith(AndroidJUnit4.class)
public class TestSecondActivityIsStarted {

@Rule
 public ActivityTestRule&amp;amp;amp;lt;MainActivity&amp;amp;amp;gt; mActivityRule = new ActivityTestRule&amp;amp;amp;lt;MainActivity&amp;amp;amp;gt;(MainActivity.class);

@Test
 public void validateSecondActivity(){
 // click button trong MainActivity
 onView(withId(R.id.btn_start)).perform(click());
 // Check xem da start SecondActivity chinh xac
 onView(withId(R.id.tv_result)).check(matches(withText("Started")));

}
}

Để kiểm thử sự biến đổi của view,chúng ta tạo thêm  classs test cho SeconActivity class:


package com.bk.haodv.esspressotwo;

import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.Espresso.pressBack;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
/**
 * Created by T430 on 9/7/2016.
 */
@RunWith(AndroidJUnit4.class)
public class SecondActivityFunctionalTest {

@Rule
 public ActivityTestRule&amp;amp;amp;lt;MainActivity&amp;amp;amp;gt; mActivityRule = new ActivityTestRule&amp;amp;amp;lt;MainActivity&amp;amp;amp;gt;(MainActivity.class);

@Test
 public void validateSecondActivity(){
 // Click button tren MainActivity
 onView(withId(R.id.btn_start)).perform(click());
 // check textView tren SecondActivity
 onView(withId(R.id.tv_result)).check(matches(withText("Started")));
 pressBack();
 onView(withId(R.id.btn_start)).check(matches(withText("Start new activity")));
 }
}

8. Sử dụng Espresso để test asynchronous code

Kiểm thử đồng bộ mà không có framework hỗ trợ là khá khó khăn. Cách tiếp cận trước Espresso là đợi 1 khoảng thời gian xác định hoặc sử dụng 1 instance của CountDownLatch class trong class test case của chúng ta và dấu hiệu từ tiến trình đồng bộ khi hoàn thành. Espresso làm việc này đơn giản hơn nhiều, nó cũng giám sát hàng đợi sự kiện từ giao diện người dùng và tiếp tục chạy test của nó một khi không task nào đang chạy.

Nếu bạn sử dụng cá resources khác nhau, như 1 IntentService bạn cần implement 1 IdingResource. Sự thực thi này sẽ giám sát resource và đăng kí với Espresso Framework


import android.app.ActivityManager;
import android.content.Context;
import android.support.test.espresso.IdlingResource;

import java.util.List;

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() {
 boolean idle = !isIntentServiceRunning();
 if (idle &amp;amp;amp;amp;&amp;amp;amp;amp; resourceCallback != null) {
 resourceCallback.onTransitionToIdle();
 }
 return idle;
 }

private boolean isIntentServiceRunning() {
 ActivityManager manager =
 (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
 // Get all running services
 List&amp;amp;amp;lt;ActivityManager.RunningServiceInfo&amp;amp;amp;gt; 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;
 }
}


import android.app.Instrumentation;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.espresso.Espresso;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.notNullValue;

@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() {
 // this triggers our intent service, as we registered
 // Espresso for it, Espresso wait for it to finish
 onView(withId(R.id.action_settings)).perform(click());
 onView(withText("Broadcast")).check(matches(notNullValue()));
 }
}

9. Ứng dụng thứ 4: Kiểm thử asynchronous code với Espresso.

Tạo 1 project tên là Espresso Three, MainActivity gồm layout chứa 1 TextView, 1 Button

khi click vào button thì run asynch task. Chi tiết file MainActivity:


package com.bk.haodv.esspressothree;

import android.os.AsyncTask;
import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

Button btnStart;
TextView tvStatus;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnStart = (Button)findViewById(R.id.btn_start);
btnStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
tvStatus.setText("Running");
myTask.execute("test");
}
});
tvStatus = (TextView)findViewById(R.id.tv_status);
}
final AsyncTask<String, String, String> myTask = new AsyncTask<String, String, String>() {
@Override
protected String doInBackground(String... strings) {

for(int i = 1; i < 10; i++){
publishProgress("running... " + i) ;
SystemClock.sleep(500);
}
return "Long running stuff";
}

@Override
protected void onProgressUpdate(String... values) {
super.onProgressUpdate(values);
tvStatus.setText(values[0]);
}

@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
tvStatus.setText("Done");
}
};
}

Code đẻ test MainActivity:


package com.bk.haodv.esspressothree;

import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withText;

/**
* Created by T430 on 9/7/2016.
*/

@RunWith(AndroidJUnit4.class)
public class EspressoTest {
@Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<MainActivity>(MainActivity.class);

@Test
public void btnShouldUpdateText(){
// click vào button start
onView(withId(R.id.btn_start)).perform(click());
// Espresso sẽ đợi đến khi task thực hiện xong, check status của Textview có chuẩn xác ko
onView(withId(R.id.tv_status)).check(matches(withText("Done")));
}
}

10.Ứng dụng 5: Tạo 1 custom Espresso matcher.

Android cung cấp class BoundedMatcher cho phép chúng ta tạo Espresso view matchers như 1 loại View riêng biệt.

Hãy viết 1 matcher , để kiểm thử text hints trên 1 Edittext.


public static Matcher<View> withItemHint(String itemHintText) {
checkArgument(!(itemHintText.equals(null)));
return withItemHint(is(itemHintText));
}

public static Matcher<View> withItemHint(final Matcher<String> matcherText) {
// use preconditions to fail fast when a test is creating an invalid matcher.
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) {
return matcherText.matches(editTextField.getHint().toString());
}
};
}

Nó có thể được sử dụng như sau:


import static com.your.package.test.Matchers.withItemHint;
...
onView(withItemHint("test")).check(matches(isDisplayed()));

10. Nguồn tài liệu về Espresso

Android Espresso testing homepage

Git repository for the Android Testing Checkout the android-support-test branch for the testing libraries.

Advertisements

Trả lời

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Đăng xuất / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Đăng xuất / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Đăng xuất / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Đăng xuất / Thay đổi )

Connecting to %s