Android官方UI自动化测试框架——Espresso

上周在美国山景城的Google公司,举办了一年一度的Google IO 大会,主要是发布一些新的产品和开发工具,以及接下来一年的发展方向。在其中也推出了Android Studio 2.2 预览版1,对其进行了强化,内置了部分新工具,比如Firebase、Espresso 测试记录器 、APK 分析器等等。正好无意中看到Espresso这部分,自己也没有使用过,那今天就来说说: Espresso ,也作为笔记记录一下

简介

Espresso 测试工具,相对于其他工具,API更加精确。并且规模更小、更简洁并且容易学习。它最初是2013年GTAC大会上推出的,目标是让开发者写出更简洁的针对APP的UI测试代码。虽然针对的是开发者(Developer),但是对于测试人员来说也是可以用的。

官方文档网址:

https://google.github.io/android-testing-support-library/docs/espresso/index.html

官方示例地址:

https://github.com/googlesamples/android-testing

兼容性

Espresso 支持以下API版本:

Froyo (API 8)
Gingerbread (API 10)
Ice Cream Sandwich (API 15)
Jelly Bean (API 16, 17 ,18)
KitKat (API 19)
Lollipop (API 21)

设置测试环境

为你避免对测试效果的影响,需要关闭虚拟机或者物理设备上的系统动画的使用:

在你的设备上,设置 -> 开发者选项 下关闭以下3个选项设置:

  • 窗口动画缩放 -> 关闭动画
  • 过渡动画缩放 -> 关闭动画
  • 动画程序时长缩放 -> 关闭动画

下载Espresso

  • 确保你在SDK里面的Extra下已经安装了最新版本的Android Support Repository
  • 打开你app的build.gradle文件,它通常不是位于第一个build.gradle文件,但是是app/build.gradle
  • 添加如下行到dependencies里面:
1
2
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
androidTestCompile 'com.android.support.test:runner:0.5'

设置 instrumentation runner

  • 在相同的build.gradle文件里,添加如下行到android.defaultConfig里面:
1
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

build.gradle 文件示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
apply plugin: 'com.android.application'

android {

compileSdkVersion 23
buildToolsVersion "23.0.3"
defaultConfig {
applicationId "com.example.shoewann.espressouiautomation"
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:23.4.0'
compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha1'
testCompile 'junit:junit:4.12'

// Testing-only dependencies
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
androidTestCompile 'com.android.support.test:runner:0.5'

// App's dependencies, including test
androidTestCompile 'com.android.support:support-annotations:23.4.0'
}

Espresso 基本使用方法

  • Espresso – 与视图(views)交互的入口,并暴露了一些视图(views)无关的API(例如回退按钮)。
  • ViewMatchers – 实现匹配器的一组对象。允许可以通过多次的onView方法,在层次图中找到目标视图(views)。
  • ViewActions – 对视图触发动作(例如点击)。
  • ViewAssertions – 用于插入测试关键点的一组断言,可用于判断某视图(view)的状态。

示例:

1
2
3
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

ViewMatcher : 寻找View

  • 根据ID找View :
1
withId(R.id.my_view)
  • 根据View的文本内容找View:
1
withText("my_view_text")

withText 內还可以使用其他过滤字串的方法,withContentDescription、startsWith、endsWith、equalToIgnoringCase、equalToIgnoringWhiteSpace、containsString、allOf、anyOf等等,在过滤中,也可以用 hasSibling() 、is() 、instanceOf() 等等来判断条件。如:

1
withText(startsWith("MY_VIEW_TEXT")

ViewAction : 操作View

使用 perform() 进行想要的 ViewAction,参数可带多个,常见的有:

  • Click()点击操作
1
onView(...).perform(click())
  • typeText() 、clearText() 键盘开关的操作
1
onView(...).perform(typeText("I'm shoewann;"),closeSoftKeyboard()) perform(clearText())
  • scrollTo() Scroll View 时,此 View 必须是继承 ScrollView 且 Visibilty 为 true 的控件,若为 listView 另有方法。
  • swiftLeft()、swiftRight() 向左右滑动
  • pressKey() 键盘事件操作 如:pressBack();可单独使用,或是用 EspressoKey Builder 创建 pressKey 连续顺序键盘操作。
1
2
3
4
5
6
7
8
EspressoKey.Builder builder = new EspressoKey.Builder();

builder.withKeyCode(KeyEvent.KEYCODE_VOLUME_UP);
builder.withKeyCode(KeyEvent.KEYCODE_MUTE);
builder.withKeyCode(KeyEvent.KEYCODE_POWER);
builder.withKeyCode(KeyEvent.KEYCODE_BACK);

pressKey(builder.build());

ViewAssertion : 验证 View

  • matches():指定 View 为存在
  • doesNotExist ():指定 View 为不存在
  • selectedDescendantsMatch():指定 View 的 Parent View 存在 方法內依然都是传人 ViewMatcher 來找到指定的 View,若 Assert 失敗,则会跳出 Exception 并結束 Test。

示例:

1
2
onView(withId(R.id.test_view))
.check(matches(withText("hello espresso !!")));

用onData处理列表view

示例:
有一个Spinner,当item选中后,将其值显示显示在TextView上

1.点击Spinner打开选项列表

1
onView(withId(R.id.spinner_simple)).perform(click());

2.点击在item “Americano”

1
onData(allOf(is(instanceOf(String.class)), is("Americano"))).perform(click());

3.验证TextView显示的是否为点击的item

1
2
onView(withId(R.id.spinnertext_simple))
.check(matches(withText(containsString("Americano"))));

espresso-cheat-sheet-2.1.0

(espresso-cheat-sheet-2.1.0)

添加Espresso的TestRunner.

1) 点击顶栏菜单Run->Edit Configurations; 2) 出现如下的窗口后,点击左上角绿色的“+”,选择“Android Tests”;

espresso_1

3) 修改新Configuration的名字,选中App Module,输入Runner,选择“Show chooer dialog”. 点击“OK”完成!

espresso_2

新建测试用例类.

Android Studio 在创建新项目的同时会默认的创建一个android test的测试用例类在app/androidTest/java下,你可以删除了自己创建一个新的,或者修改这个存在的测试类的类名和里面的内容。

espresso_2_1

开始编写——布局文件

我在这里创建了一个登录界面,来自动测试登录效果

espresso_3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.shoewann.espressouiautomation.MainActivity"
tools:layout_editor_absoluteX="0dp"
tools:layout_editor_absoluteY="81dp">

<EditText
android:layout_width="0dp"
android:layout_height="wrap_content"
android:inputType="textEmailAddress"
android:ems="10"
tools:layout_editor_absoluteX="8dp"
tools:layout_editor_absoluteY="16dp"
android:id="@+id/editText"
android:hint="请输入邮箱号"
app:layout_constraintLeft_toLeftOf="@+id/activity_main"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
tools:layout_constraintLeft_creator="1"
app:layout_constraintTop_toTopOf="@+id/activity_main"
android:layout_marginTop="16dp"
tools:layout_constraintTop_creator="1"
app:layout_constraintRight_toRightOf="@+id/activity_main"
android:layout_marginRight="8dp"
android:layout_marginEnd="8dp"
tools:layout_constraintRight_creator="1" />

<EditText
android:layout_width="0dp"
android:layout_height="wrap_content"
android:inputType="numberSigned"
android:ems="10"
tools:layout_editor_absoluteX="8dp"
tools:layout_editor_absoluteY="66dp"
android:id="@+id/editText2"
android:layout_margin="8dp"
android:hint="请输入密码"
app:layout_constraintLeft_toLeftOf="@+id/activity_main"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
tools:layout_constraintLeft_creator="1"
app:layout_constraintTop_toBottomOf="@+id/editText"
android:layout_marginTop="8dp"
tools:layout_constraintTop_creator="1"
app:layout_constraintRight_toRightOf="@+id/activity_main"
android:layout_marginRight="8dp"
android:layout_marginEnd="8dp"
tools:layout_constraintRight_creator="1" />

<Button
android:text="登录"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:layout_editor_absoluteX="16dp"
tools:layout_editor_absoluteY="123dp"
android:id="@+id/button"
android:layout_margin="8dp"
app:layout_constraintLeft_toLeftOf="@+id/activity_main"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
tools:layout_constraintLeft_creator="1"
app:layout_constraintTop_toBottomOf="@+id/editText2"
android:layout_marginTop="15dp"
tools:layout_constraintTop_creator="1" />

<TextView
android:text="状态:未登录"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:layout_editor_absoluteX="8dp"
tools:layout_editor_absoluteY="187dp"
android:id="@+id/textView"
app:layout_constraintLeft_toLeftOf="@+id/activity_main"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
tools:layout_constraintLeft_creator="1"
app:layout_constraintTop_toBottomOf="@+id/button"
android:layout_marginTop="16dp"
tools:layout_constraintTop_creator="1" />
</android.support.constraint.ConstraintLayout>

开始编写——代码源文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class MainActivity extends AppCompatActivity implements View.OnClickListener{

private EditText et_email,et_pwd;
private Button btn_login;
private TextView tv_status;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

initView();
initData();
}

public void initView(){
et_email= (EditText) findViewById(R.id.editText);
et_pwd= (EditText) findViewById(R.id.editText2);
btn_login=(Button) findViewById(R.id.button);
tv_status=(TextView)findViewById(R.id.textView);
}
public void initData(){
btn_login.setOnClickListener(this);
}

@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.button:
String getEmail=et_email.getText().toString();
String getPwd=et_pwd.getText().toString();
if(TextUtils.isEmpty(getEmail) || TextUtils.isEmpty(getPwd)){
ShowLoginFailed();
return;
}else if (!getEmail.equals("xuwang0402@gmail.com")|| !getPwd.equals("1234567890")){
ShowLoginFailed();
}else {
ShowLoginSuccessful();
}
break;
default:
break;
}
}
public void ShowLoginSuccessful(){

tv_status.setText("登录状态:登录成功,欢迎回来~");
}

public void ShowLoginFailed(){

tv_status.setText("登录状态:登录失败,帐号或密码不合法,请重试!");
}
}

开始编写——测试用例

1) 首先创建一个@Rule,ActivityTestRule用来指明被测试的Activity;
2) 测试用例的方法都是@Test的Annotation注解的,方法名字可以随意.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import android.support.test.filters.MediumTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;

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.clearText;
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;

/**
* Instrumentation test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@MediumTest
@RunWith(AndroidJUnit4.class)
public class MainActivityUITest {

private String testEmail_correct,testPwd_correct,tv_status_incorrect;
private String testEmail_incorrect,testPwd_incorrect,tv_status_correct;

@Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class);

@Before
public void initLogInAccountInfo(){

//incorrect
testEmail_incorrect="zhangsan@gmail.com";
testPwd_incorrect="123456";
tv_status_incorrect="登录状态:登录失败,帐号或密码不合法,请重试!";

//correct
testEmail_correct="xuwang0402@gmail.com";
testPwd_correct="1234567890";
tv_status_correct="登录状态:登录成功,欢迎回来~";
}
@Test
public void testLogInMethod(){

//incorrect
onView(withId(R.id.editText)).perform(typeText(testEmail_incorrect), closeSoftKeyboard());
onView(withId(R.id.editText2)).perform(typeText(testPwd_incorrect), closeSoftKeyboard());
onView(withId(R.id.button)).perform(click());
onView(withId(R.id.textView)).check(matches(withText(tv_status_incorrect)));
//clearEditText
onView(withId(R.id.editText)).perform(clearText());
onView(withId(R.id.editText2)).perform(clearText());
//correct
onView(withId(R.id.editText)).perform(typeText(testEmail_correct), closeSoftKeyboard());
onView(withId(R.id.editText2)).perform(typeText(testPwd_correct), closeSoftKeyboard());
onView(withId(R.id.button)).perform(click());
onView(withId(R.id.textView)).check(matches(withText(tv_status_correct)));

}
}

在这里我用了一个正确的帐号和一个错误的帐号来自动化模拟登录效果,系统会先自动输入错误的邮箱号码和密码,然后自动点击登录,然后自动获取到显示文本上的内容判断是否和我预期定义的一致,然后自动清除账号和密码输入框,重新自动输入正确的帐号密码,然后自动点击登录,然后自动获取到显示文本上的内容判断是否和我预期定义的一致,达到自动化测试的效果。

Espress推荐写测试用例的时候使用static import来简化代码.在Android Studio中,static import的快捷键是,当你输入了”onView”,然后按键盘的Alt+Enter会出现一个菜单选static import那个就OK了的!

运行测试用例,查看运行结果.

展开下拉列表,选择我们刚刚创建的测试类的对应模块的名字 “appTest”

espresso_4

espresso_5

可以看到所有测试已通过

0%