android 自动化测试
- Android Develop - Testing Support Library
- Android Develop - UI Automator
- Android Develop - Espresso
- Android Develop - Testing UI for Multiple Apps
- vogella.com - Android user interface testing with Espresso - Tutorial
- 《腾讯Android自动化测试实战》
自动化测试介绍
Android 自动化测试框架
- Monkey
- MonkeyRunner
- Instrumentation
- MonkeyRunner
- UIAutomator 【跨应用】
- Robotium 最后一次更新于2016年
基于 Instrumentation。
https://github.com/RobotiumTech/robotium - Robolectric
主要支持单元测试。 - Selendroid
- Espresso
- Calabash
- Appium
使用 Selenium 的 WebDriver JSON 协议 - Macaca(淘宝)
- https://macacajs.com/zh/
- 支持在移动端和PC端的Native, Hybrid, 移动端Web应用
- Airtest(网易)
自动化测试的基础知识
自动化测试基本步骤
自动化测试的使用场景
Monkey
启动脚本在 /system/bin
目录的 Monkey 文件。
jar包在 /system/framework/
目录的 Monkey.jar
1 | adb shell monkey [options] <event-count> |
常规的monkey测试是,随机事件流。但也可以自定义脚本来执行指定事件流。
monkey 自定义脚本编写
Monkey 脚本只能通过坐标的方式来定位点击、移动事件,需要提前获取坐标。
获取坐标信息的方法 : 开发人员选项 》显示指针位置
1 | monkey -f some-test.script |
some-test.script
:
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 | # 进入应用宝,输入 yyb 进行搜索的例子 # 启动测试 type = user count = 49 speed = 1.0 start data >> # 启动应用宝 LaunchActivity(com.tencent.qqdownloader, com.tencent.assistant.activity.SplashActivity) UserWait(2000) # 点击搜索 Tap(463,150,1000) UserWait(2000) # 输入字母 yyb DispatchString(yyb) UserWait(2000) # 点击搜索 Tap(960,150,1000) UserWait(2000) # 点击返回键返回首页 DispatchPress(KEYCODE_BACK) |
将执行文件push到手机后,执行
1 2 3 | adb push some-test.script /sdcard/ # 执行10次 adb shell monkey -f /sdcard/some-test.script -v 10 |
Monkey Script 的常见API
1 2 3 4 5 6 7 8 9 10 11 12 | DispatchPointer(long downTime, long eventTime, int action,float x, float y, float pressure, float size, int metaState,float xPrecision, float yPrecision, int device, int edgeFlags) DispatchTrackball same as DispatchPointer DispatchKey(long downTime, long eventTime, int action, int code,int repeat, int metaState, int device, int scancode) DispatchFlip(boolean keyboardOpen) DispatchPress(int keyCode) Tap(int x,int y) LaunchActivity(String pkg_name, String cl_name) UserWait(long sleeptime) LongPress(long sleeptime) DispatchPointer: This is used to perform task such as Dragging, tapping, clicking, even entering alphabet in target. DispatchTrackball: same as DispatchPointer Tap: This is used tap Android Device.ex any icon, application etc. in given coordinate. |
UiAutomator
一般测试流程
- 分析UI元素,获取元素属性
- 开发测试代码,模拟用户操作。
- 编译测试代码成jar,push到手机。
- 在手机上运行测试,搜集测试结果。
获取元素信息的方法
- 使用uiautomatorviewer工具
./sdk/tools/bin/uiautomatorviewer
开发
创建 Project
创建Java Project(而不是Android Project),加入依赖: android.jar
、uiautomator.jar
, 2个jar都在 ./sdk/platforms/android-xx
目录。
android-19 以上的新增 UiSelector.resourceId
系列方法,能通过resource id 定位元素。
开发用例
- 每个用例都继承自
UiAutomatorTestCase
setUp()
先于每个用例执行。进行用例执行的准备。tearDown()
后于每个用例执行。搜集用例结果,恢复环境。- 一个 Class 可以包含多个用例 testXXX(),但不建议耦合、存在顺序依赖关系。
Espresso
Espresso test 示例代码:
1 2 3 4 5 6 | @Test public void greeterSaysHello() { onView(withId(R.id.name_field)).perform(typeText("Steve")); onView(withId(R.id.greet_button)).perform(click()); onView(withText("Hello Steve!")).check(matches(isDisplayed())); } |
The core API is small, predictable, and easy to learn and yet remains open for customization. Espresso tests state expectations, interactions, and assertions clearly without the distraction of boilerplate content, custom infrastructure, or messy implementation details getting in the way.
Espresso tests run optimally fast! It lets you leave your waits, syncs, sleeps, and polls behind while it manipulates and asserts on the application UI when it is at rest.
Target Audience
Espresso is targeted at developers, who believe that automated testing is an integral part of the development lifecycle. While it can be used for black-box testing, Espresso’s full power is unlocked by those who are familiar with the codebase under test.
Packages
- espresso-core - Contains core and basic View matchers, actions, and assertions. See Basics and Recipes.
- espresso-web - Contains resources for WebView support.
- espresso-idling-resource - Espresso’s mechanism for synchronization with background jobs.
- espresso-contrib - External contributions that contain DatePicker, RecyclerView and Drawer actions, accessibility checks, and CountingIdlingResource.
- espresso-intents - Extension to validate and stub intents for hermetic testing.
You can learn more about the latest versions by reading the release notes.
如何在项目中使用 Espresso
配置Gradle
add the following dependency to the Gradle build file of your app.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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' } |
Ensure that the android.support.test.runner.AndroidJUnitRunner
is specified as value for the testInstrumentationRunner
parameter in the build file of your app. Via the packagingOptions you may have to exclude LICENSE.txt, depending on the libraries you are using.
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 | apply plugin: 'com.android.application' android { compileSdkVersion 22 buildToolsVersion '22.0.1' defaultConfig { applicationId "com.example.android.testing.espresso.BasicSample" minSdkVersion 10 targetSdkVersion 22 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } packagingOptions { exclude 'LICENSE.txt' } lintOptions { abortOnError false } } dependencies { // as before....... } |
Device settings
Exercise: A first Espresso test
Create project under test
Create a new Android project called Espresso First with the package name com.vogella.android.espressofirst. Use the Blank Template as basis for this project.
Change the generated activity_main.xml layout file to the following.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <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" > <EditText android:id="@+id/inputField" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/changeText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="New Button" android:onClick="onClick"/> <Button android:id="@+id/switchActivity" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Change Text" android:onClick="onClick"/> </LinearLayout> |
Create a new file called activity_second.xml.
1 2 3 4 5 6 7 8 9 10 11 12 | <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceLarge" android:text="Large Text" android:id="@+id/resultView" /> </LinearLayout> |
Create a new activity with the following code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | package com.vogella.android.espressofirst; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.TextView; public class SecondActivity extends Activity{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); TextView viewById = (TextView) findViewById(R.id.resultView); Bundle inputData = getIntent().getExtras(); String input = inputData.getString("input"); viewById.setText(input); } } |
Also adjust your MainActivity class.
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 | package com.vogella.android.espressofirst; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.EditText; public class MainActivity extends Activity { EditText editText; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); editText = (EditText) findViewById(R.id.inputField); } public void onClick(View view) { switch (view.getId()) { case R.id.changeText: editText.setText("Lalala"); break; case R.id.switchActivity: Intent intent = new Intent(this, SecondActivity.class); intent.putExtra("input", editText.getText().toString()); startActivity(intent); break; } } } |
Create your Espresso test
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 | package com.vogella.android.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; @RunWith(AndroidJUnit4.class) public class MainActivityEspressoTest { @Rule public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class); @Test public void ensureTextChangesWork() { // Type text and then press the button. onView(withId(R.id.inputField)) .perform(typeText("HELLO"), closeSoftKeyboard()); onView(withId(R.id.changeText)).perform(click()); // Check that the text was changed. onView(withId(R.id.inputField)).check(matches(withText("Lalala"))); } @Test public void changeText_newActivity() { // Type text and then press the button. onView(withId(R.id.inputField)).perform(typeText("NewText"), closeSoftKeyboard()); onView(withId(R.id.switchActivity)).perform(click()); // This view is in a different Activity, no need to tell Espresso. onView(withId(R.id.resultView)).check(matches(withText("NewText"))); } } |
在Android Sutdio里面,右击 MainActivityEspressoTest,选择 Run
利用 adb 运行 espresso
参考: https://developer.android.com/studio/test/command-line.html
1 2 3 | adb shell am instrument -w <test_package_name>/<runner_class> adb shell am instrument -w com.your-name.espressofirst.test/android.support.test.runner.AndroidJUnitRunner |