android 自动化测试:不借助电脑,在设备上直接运行测试
改进的EspressoFirst,可以从 app 启动 test
- 参考
- MainActivity.java
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 | package com.wispeedio.espressofirst; import android.content.ComponentName; import android.content.Intent; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.EditText; public class MainActivity extends AppCompatActivity { 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; case R.id.run_test: // 方法1,使用 startInstrumentation 来启动 Test Bundle bd = new Bundle(); // 指定执行特定类中的用例 //bd.putString("class", "com.wispeedio.espressofirst.MainActivityEspressoTest"); // bd 即使是空洞Bundle,也必须指定, // 否则报错:NullPointerException in onCreate() of AndroidJUnitRunner startInstrumentation( new ComponentName( "com.wispeedio.espressofirst.test", "android.support.test.runner.AndroidJUnitRunner"), null, bd); // 方法2,可能有用,也可能因为权限不够而失败 // 可以尝试添加 permission:"android.permission.INJECT_EVENTS" // try { // Process process = // Runtime.getRuntime().exec( // "adb shell am instrument -w com.wispeedio.espressofirst.test/android.support.test.runner.AndroidJUnitRunner"); // } catch(Exception e) { // Log.d("wiii", "am instrument error: " + e); // } break; } } } |
- activity_main.xml
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 | <?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" > <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"/> <Button android:id="@+id/run_test" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Run Test!" android:onClick="onClick"/> </LinearLayout> |
- SecondActivity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | package com.wispeedio.espressofirst; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.TextView; public class SecondActivity extends AppCompatActivity { @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); } } |
- activity_second.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 | <?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> |
- MainActivityEspressoTest.java
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 | package com.wispeedio.espressofirst; /** * Created by wi on 2018/1/5. */ import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; import android.util.Log; 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"))); } } |
- app/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 | apply plugin: 'com.android.application' android { compileSdkVersion 25 buildToolsVersion "25.0.3" defaultConfig { applicationId "com.wispeedio.espressofirst" minSdkVersion 15 targetSdkVersion 25 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']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:25.3.1' compile 'com.android.support.constraint:constraint-layout:1.0.2' testCompile 'junit:junit:4.12' } |
stackoverflow 3299006: running-android-unit-tests-from-the-command-line
https://stackoverflow.com/questions/3299006/running-android-unit-tests-from-the-command-line
1 2 | pm list instrumentation pm list packages |
Running Android Tests on a Device or Emulator
http://mattsnider.com/running-android-tests-on-a-device-or-emulator/
1 2 3 4 | adb -s <emulator name> shell am instrument -w -e <path to test file> <test package>:<test runner> # Here is an example from GmsCore adb -s emulator-5554 shell am instrument -w -e class com.google.android.gms.games.broker.PlayerAgentTest com.google.android.gms.test/android.support.test.runner.AndroidJUnitRunner |
1 2 3 4 | adb -s <emulator name> shell am instrument -w -e debug true -e <path to test file> <test package>:<test runner> # Here is an example from GmsCore adb -s emulator-5554 shell am instrument -w -e debug true -e class com.google.android.gms.games.broker.PlayerAgentTest com.google.android.gms.test/android.support.test.runner.AndroidJUnitRunner |
adb shell am instrument 命令详解
官网关于该命令的详解:https://developer.android.com/studio/command-line/adb.html?hl=zh-cn
1 Instrument是什么?
instrument为am命令的一个子命令。用于启动一个Instrumentation测试。首先连接手机或者模拟器,通过adb shell命令,进入shell层进行操作。
2 命令格式及参数解读(来自官网)
格式:instrument [options] component
目标 component 是表单 test_package/runner_class
,在UiAutomator2.0中,目标 component为:测试包名/android.support.test.runner.AndroidJUnitRunner
(即运行器固定:AndroidJUnitRunner类是一个JUnit测试运行器,允许运行JUnit 3或JUnit 4测试类在 Android 设备上,包括那些使用Espresso和UI Automator框架。)
各项参数:
1 2 3 4 5 6 | -r:以原始形式输出测试结果;该选项通常是在性能测试时与[-e perf true]一起使用。 -e name value:提供了以键值对形式存在的过滤器和参数。例如:-e testFile <filePath>(运行文件中指定的用例);-e package <packageName>(运行这个包中的所有用例)…… 有十几种。 -p file:将分析数据写入 file。 -w:测试运行器需要使用此选项。-w <test_package_name>/<runner_class> :<test_package_name>和<runner_class>在测试工程的AndroidManifest.xml中查找,作用是保持adb shell打开直至测试完成。 --no-window-animation:运行时关闭窗口动画。 --user user_id | current:指定仪器在哪个用户中运行;如果未指定,则在当前用户中运行。 |
3 怎么运行手机中现有的instrumentation, 并输出详细结果,同时将profiling性能数据写到本地文件中?
先列出手机中已安装的instrumentation:adb shell pm list instrumentation
adb shell am instrument XXX
4 命令的具体使用
比如com.le.tcauto.uitest.test是包含所有测试代码的应用的包名:(来自:http://blog.csdn.net/swordgirl2011/article/details/50881390)
-
运行所有的用例: adb shell am instrument -w com.le.tcauto.uitest.test/android.support.test.runner.AndroidJUnitRunner
-
运行一个类中的所有用例:
adb shell am instrument -w -r-e class com.letv.leview.setproxy
com.le.tcauto.uitest.test/android.support.test.runner.AndroidJUnitRunner -
运行类中的某个方法:adb shell am instrument -w -r
-e debug false -e class com.letv.leview.setproxy#testDemo
com.le.tcauto.uitest.test/android.support.test.runner.AndroidJUnitRunner -
运行多个类的所有用例:adb shell am instrument -w -r
-e debug false -e class com.letv.leview.setproxy,com.letv.leview.resetdate
com.le.tcauto.uitest.test/android.support.test.runner.AndroidJUnitRunner -
运行所有测试用例除了指定的类:adb shell am instrument -w -r
-e notClass com.letv.leview.setproxy
com.le.tcauto.uitest.test/android.support.test.runner.AndroidJUnitRunner -
运行所有测试除了指定的用例:adb shell am instrument -w -r
-e debug false -e class com.letv.leview.setproxy#testDemo
com.le.tcauto.uitest.test/android.support.test.runner.AndroidJUnitRunner -
运行文件中的所列的用例:adb shell am instrument -w
-e testFile /sdcard/tmp/testFile.txt
com.android.foo/com.android.test.runner.AndroidJUnitRunner 文件制定的 格式为:com.android.foo.FooClaseName#testMethodName -
运行指定测试切片的用例:adb shell am instrument -w
-e numShards 4 -e shardIndex 1
com.android.foo/android.support.test.runner.AndroidJUnitRunner -
运行指定注解的测试用例:adb shell am instrument -w
-e annotation com.android.foo.MyAnnotation
com.android.foo/android.support.test.runner.AndroidJUnitRunner。如果使用多个选项,则运行的用例为两者的交集,比如:“-e size large -e annotation com.android.foo.MyAnnotation”,将只运行同时含LargeTest和MyAnnotation注解的用例。 -
运行没有指定注解的用例:adb shell am instrument -w
-e notAnnotation com.android.foo.MyAnnotation
com.android.foo/android.support.test.runner.AndroidJUnitRunner,指定多个注解,用“,”隔开,e.g. adb shell am instrument -w -e notAnnotation com.android.foo.MyAnnotation,com.android.foo.AnotherAnnotation com.android.foo/android.support.test.runner.AndroidJUnitRunner -
以上所有参数也可以通过
<meta-data>
标签配置在AndroidManifest文件,比如<meta-data android:name="listener" android:value="com.foo.Listener"/>
,通过shell命令 传入的参数将覆盖AndroidManifest文件中配置的参数。
5 开始容易写错?
AS ——Select Run/Debug —— Configuration ——Edit Configuration ——配置 ——OK
运行完成,下方会显示该命令,然后copy过来。