您的位置:首页 > 移动开发 > Android开发

Android测试系列之Instrumented Unit Test-Espresso

2016-02-28 14:39 417 查看
Instrumented unit tests are unit tests that run on physical devices and emulators, instead of the Java Virtual Machine (JVM) on your local machine.

这是源于Google API文档的一句话,它告诉我们Instrument Unit Test区别于Unit Test的地方就是它能够在物理设备和虚拟机上运行。所以我们多来进行自动化的UI测试。

注意两点:

第一、以下所有关于Instrumented Unit Test讲解的测试环境都是在Android Studio

第二、android studio测试代码运行的Build Variants的Test ArtiFact的模式都是Android Instrumentation Test。

Google推荐了两种自动化UI测试的方式,第一种是利用Espresso,这种方式适用于单个应用程序的测,测试环境是android 2.2(API 8)及其以上;第二种是UiAutoMator,这种方式适用于多个应用程序的测试,测试环境是android 4.3(API 18)及其以上。

在自动化测试的测试中:

1. 找到我们要测试的对象

2. 测试对象的动作

3. 检查测试效果

下面我们学习这两种自动化测试的时候的核心就是这两步,只有熟悉了这两种自动化测试实现的方式,以后的事情处理起来就的得心应手啦。

**

Testing UI for a Single App(Espresso)

**

建立一个Espresso测试用例,有以下步骤:

1.利用@Rule注解方式指明要测试的Activity

2. 调用OnView()或者OnData()方法找到我们要测试的UI控件

3. 通过调用theViewInteraction.perform()或DataInteraction.perform()方法模拟一个用户操作

4. 使用ViewAssertions方法检查UI反映预期的状态或行为

下面就用两个简单的例子实现其测试功能:

第一个Demo:

Build.gradle

apply plugin: 'com.android.application'

android {
compileSdkVersion 23
buildToolsVersion "23.0.1"

defaultConfig {
applicationId "com.example.myapplication"
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName "1.0"

testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
lintOptions {
abortOnError false
}
productFlavors {
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
// App dependencies
compile 'com.android.support:support-annotations:23.0.1'
compile 'com.google.guava:guava:18.0'
// Testing-only dependencies
// Force usage of support annotations in the test app, since it is internally used by the runner module.
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'
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1'
testCompile 'junit:junit:4.12'
}


MainActivity.java

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends Activity implements View.OnClickListener {

// The TextView used to display the message inside the Activity.
private TextView mTextView;

// The EditText where the user types the message.
private EditText mEditText;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// Set the listeners for the buttons.
findViewById(R.id.changeTextBt).setOnClickListener(this);
findViewById(R.id.activityChangeTextBtn).setOnClickListener(this);

mTextView = (TextView) findViewById(R.id.textToBeChanged);
mEditText = (EditText) findViewById(R.id.editTextUserInput);
}

@Override
public void onClick(View view) {
// Get the text from the EditText view.
final String text = mEditText.getText().toString();

switch (view.getId()) {
case R.id.changeTextBt:
// First button's interaction: set a text in a text view.
mTextView.setText(text);
break;
case R.id.activityChangeTextBtn:
// Second button's interaction: start an activity and send a message to it.
Intent intent = ShowTextActivity.newStartIntent(this, text);
startActivity(intent);
break;
}
}
}


activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
android:padding="@dimen/activity_horizontal_margin"
tools:context=".MainActivity">

<TextView
android:id="@+id/textToBeChanged"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="@dimen/header_margin"
android:layout_marginTop="@dimen/header_margin"
android:text="@string/hello_world"
android:textAppearance="?android:attr/textAppearanceLarge"/>

<EditText
android:id="@+id/editTextUserInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:hint="@string/type_something"/>

<Button
style="?android:attr/buttonStyleSmall"
android:id="@+id/changeTextBt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/change_text"
android:layout_gravity="center_horizontal"/>

<Button
style="?android:attr/buttonStyleSmall"
android:id="@+id/activityChangeTextBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/open_activity_and_change_text"/>
</LinearLayout>


ShowTextActivity.java

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.widget.TextView;

import com.google.common.base.Strings;

/**
* Created by Administrator on 16-2-23.
*/
public class ShowTextActivity extends Activity {

// The name of the extra data sent through an {@link Intent}.
public final static String KEY_EXTRA_MESSAGE =
"com.example.myapplication.MESSAGE";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_show_text);

// Get the message from the Intent.
Intent intent = getIntent();
String message = Strings.nullToEmpty(intent.getStringExtra(KEY_EXTRA_MESSAGE));

// Show message.
((TextView)findViewById(R.id.show_text_view)).setText(message);
}

/**
* Creates an {@link Intent} for {@link ShowTextActivity} with the message to be displayed.
* @param context the {@link Context} where the {@link Intent} will be used
* @param message a {@link String} with text to be displayed
* @return an {@link Intent} used to start {@link ShowTextActivity}
*/
static protected Intent newStartIntent(Context context, String message) {
Intent newIntent = new Intent(context, ShowTextActivity.class);
newIntent.putExtra(KEY_EXTRA_MESSAGE, message);
return newIntent;
}
}


activity_show_text.xml

<?xml version="1.0" encoding="utf-8"?>
<merge 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"
tools:context=".ShowTextActivity">

<TextView
android:id="@+id/show_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world"
android:layout_gravity="center"/>

</merge>


叮叮叮当,我们的主角出场,测试用例

ChangeTextBehaviorTest.java

import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.LargeTest;

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 Administrator on 16-2-23.
*/
@RunWith(AndroidJUnit4.class)
@LargeTest
public class ChangeTextBehaviorTest {
public static final String STRING_TO_BE_TYPED = "espresso";

@Rule
public ActivityTestRule<MainActivity> mActivityRule=new ActivityTestRule<MainActivity>(MainActivity.class);
@Test
public void changeText_sameActivity(){
// Type text and then press the button.
onView(withId(R.id.editTextUserInput))
.perform(typeText(STRING_TO_BE_TYPED),closeSoftKeyboard());
onView(withId(R.id.changeTextBt))
.perform(click());
// Check that the text was changed.
onView(withId(R.id.textToBeChanged))
.check(matches(withText(STRING_TO_BE_TYPED)));
}
@Test
public void changeText_newActivity() {
// Type text and then press the button.
onView(withId(R.id.editTextUserInput)).perform(typeText(STRING_TO_BE_TYPED),
closeSoftKeyboard());
onView(withId(R.id.activityChangeTextBtn)).perform(click());

// This view is in a different Activity, no need to tell Espresso.
onView(withId(R.id.show_text_view)).check(matches(withText(STRING_TO_BE_TYPED)));
}
}


第二个Demo:

OtherActivity.java

import android.app.Activity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class OtherActivity extends Activity {
private List<Map<String,Object>> datas;
private MyAdapter myAdapter;
private ListView myListview;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_other);
initView();
initData();
setAdapter();
}
private void initView(){
myListview = (ListView) findViewById(R.id.my_lv);
}
private void initData(){
datas = new ArrayList<>();
for (int i = 0; i < 15; i++) {
Map<String,Object> map = new HashMap<>();
map.put("number","测试数据"+i);
datas.add(map);
}
}
private void setAdapter(){
myAdapter = new MyAdapter();
myListview.setAdapter(myAdapter);
}
class MyAdapter extends BaseAdapter{

@Override
public int getCount() {
return datas.size();
}

@Override
public Object getItem(int position) {
return datas.get(position);
}

@Override
public long getItemId(int position) {
return position;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView==null){
holder = new ViewHolder();
convertView= LayoutInflater.from(OtherActivity.this).inflate(R.layout.list_item,null);
holder.tv = (TextView) convertView.findViewById(R.id.rowContentTextView);
convertView.setTag(holder);
}else{
holder = (ViewHolder) convertView.getTag();
}
holder.tv.setText(datas.get(position).get("number").toString());
return convertView;
}
final class ViewHolder{
TextView tv;
}
}
}


activity_other.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/my_lv"
android:layout_width="match_parent"
android:layout_height="match_parent"></ListView>
</RelativeLayout>


叮叮叮当,主角出场(注意里面有hamcrest的应用,不了解的可以查查资料)

ListActivityTest.java

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

import android.support.test.espresso.DataInteraction;
import android.support.test.espresso.Espresso;
import android.support.test.espresso.action.ViewActions;
import android.support.test.espresso.matcher.ViewMatchers;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.LargeTest;

import java.util.Map;

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.doesNotExist;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isChecked;
import static android.support.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;

/**
* Created by Administrator on 16-2-25.
*/
@RunWith(AndroidJUnit4.class)
@LargeTest
public class LongListActivityTest {
private static final String TEXT_ITEM_30 = "item: 30";

private static final String TEXT_ITEM_30_SELECTED = "30";

private static final String TEXT_ITEM_60 = "item: 60";

// Match the last item by matching its text.
private static final String LAST_ITEM_ID = "测试数据14";

/**
* A JUnit {@link Rule @Rule} to launch your activity under test. This is a replacement
* for {@link ActivityInstrumentationTestCase2}.
* <p>
* Rules are interceptors which are executed for each test method and will run before
* any of your setup code in the {@link Before @Before} method.
* <p>
* {@link ActivityTestRule} will create and launch of the activity for you and also expose
* the activity under test. To get a reference to the activity you can use
* the {@link ActivityTestRule#getActivity()} method.
*/
@Rule
public ActivityTestRule<OtherActivity> mActivityRule = new ActivityTestRule<>(
OtherActivity.class);

/**
* Test that the list is long enough for this sample, the last item shouldn't appear.
*/
@Test
public void lastItem_NotDisplayed() {
// Last item should not exist if the list wasn't scrolled down.
onView(withText(LAST_ITEM_ID)).check(doesNotExist());
}

/**
* Check that the item is created. onData() takes care of scrolling.
*/
@Test
public void list_Scrolls() {
onRow(LAST_ITEM_ID).check(matches(isCompletelyDisplayed()));
}

/**
* Clicks on a row and checks that the activity detected the click.
*/
@Test
public void row_Click() {
// Click on one of the rows.
onRow(LAST_ITEM_ID).onChildView(withId(R.id.rowToggleButton)).perform(click());

// Check that the activity detected the click on the first column.
/* onView(ViewMatchers.withId(R.id.selection_row_value))
.check(matches(withText(TEXT_ITEM_30_SELECTED)));*/
}

/**
* Checks that a toggle button is checked after clicking on it.
*/
@Test
public void toggle_Click() {
// Click on a toggle button.
onRow(TEXT_ITEM_30).onChildView(withId(R.id.rowToggleButton)).perform(click());

// Check that the toggle button is checked.
onRow(TEXT_ITEM_30).onChildView(withId(R.id.rowToggleButton)).check(matches(isChecked()));
}

/**
* Make sure that clicking on the toggle button doesn't trigger a click on the row.
*/
@Test
public void toggle_ClickDoesntPropagate() {
// Click on one of the rows.
onRow(TEXT_ITEM_30).onChildView(withId(R.id.rowContentTextView)).perform(click());

// Click on the toggle button, in a different row.
onRow(TEXT_ITEM_60).onChildView(withId(R.id.rowToggleButton)).perform(click());

// Check that the activity didn't detect the click on the first column.
onView(ViewMatchers.withId(R.id.selection_row_value))
.check(matches(withText(TEXT_ITEM_30_SELECTED)));
}

/**
* Uses {@link Espresso#onData(org.hamcrest.Matcher)} to get a reference to a specific row.
* <p>
* Note: A custom matcher can be used to match the content and have more readable code.
* See the Custom Matcher Sample.
* </p>
*
* @param str the content of the field
* @return a {@link DataInteraction} referencing the row
*/
private static DataInteraction onRow(String str) {
return onData(hasEntry("number", str));
}

}


由于篇幅的原因,关于UiAutomator我就放在下一篇博客吧!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android 测试