RT 标题的错误,我想用 AS(Android Studio) 写单元 UI 测试的应该人人都碰到过吧? 什么? 你没碰到过? 厉害,小弟膜拜之……之后的不用看了~
大概猜测下 Instrumentation 是什么东西,反正就是在底层隐形的帮助我们测试的一个东西吧~
错误翻译过来大概就是Instrumentation 运行失败了,因为进程崩溃了

然后笔者仔细检查了测试逻辑,确认完全没有问题后 (如何确认:可以单个 Case 运行,如果单个运行是 OK 的,那么就可以基本确认测试逻辑是没有问题的),之后 FQ 到处查找原因,有说因为APP 使用了不正规的退出办法,比如 System.exit(0)这种的,也有说APP 本身就有 BUG(比如开发的代码写得有问题,正常的业务操作本身会导致 APP 崩溃)的,还有说需要在测试用例执行完毕后,需要 teardown(在 Junit4+Espresso 里是通过添加 @ After 的关键字)的,总之,推荐的方法很多 (当然,笔者也相信写这些文章的前辈们是真的遇到过这种问题而且通过推荐的方式解决了的)...

甚至 Stack Overflow 上有很多这样的问题,根本就没人回!为什么呢? 我认为一定程度上是因为 Google 没做到位的表现 (可能 Google 里面都是大神,一般写的都不会错,所以对这些开放信息不太重视吧),作为一个常规的正常的工具,是需要把尽可能多的信息或者关键信息抛出来的,而不会这样只抛一个外围错误...还推荐去 Logcat 看,没考虑到一旦 APP 的逻辑较重,那么控制台会一直打印信息,测试人员需要的关键 crash 信息会一闪而过,怎么捕捉到呢?

以上吐槽,祭奠这两天耗费在这个上面的时间。

正文:

  1. 对于以上的第一种可能的原因,我们可以检查开发代码,如果在 Activity(一般是 MainActivity) 里面写的形如如下形式的代码,则可以排除掉:

    @override
    protected void onDestroy(){
        super.onDestroy();
        LogUtil.w(MusicService.TAG_LIFE_CYCLE, "onDestroy e");
        android.os.Process.killProcess(android.os.Process.myPid());
    }
    
  2. 第二种可能的原因排除起来也简单, 就是人工执行测试步骤,如果 APP 没崩溃,则也可以排除掉了;

  3. 第三种可能的原因排除起来更简单,直接在测试类里添加@After来设计一个测试用例执行完成后的数据清除工作就行 (如果不需要清除,就可以什么都不写,留空就行)

下面是笔者亲自试验成功第 4 种的办法,供阅者参考。

  1. 在 app 的 build.gradle 里添加如下信息:
android{
          ......
        defaultConfig {
        .......
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"  
    }
        testOptions {
        execution 'ANDROID_TEST_ORCHESTRATOR'
    }
}

......
dependencies {
    androidTestUtil 'com.android.support.test:orchestrator:1.0.1'
    androidTestCompile 'com.android.support.test:rules:1.0.1'

    // Espresso dependencies
    androidTestImplementation 'com.android.support.test:runner:1.0.0'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.0'
    //其他需要用到的Espresso包
    ......
    androidTestCompile 'com.android.support.test.espresso:espresso-idling-resource:3.0.1'
}

注意:

  1. 以上包的版本需要根据具体的项目进行添加!笔者这里应该是最新的 (或者仅仅落后一丢丢的) 安卓支撑测试包;
  2. 如何验证这些包有效:只要 Sync 一次 gradle 会自动帮你找出各种包的依赖,如果有confict的,可以通过configurations.all来强制使用高/低版本的插件包,示例:
android {
    configurations.all{
        resolutionStrategy.force('com.android.support:support-annotations:25.4.0')
    }
}

依赖添加完了之后,如果 gradle sync 成功,那么就开始写用例了。写测试用例需要注意必要的等待时间,可以用 Idling Resource,也可以用 Thread.sleep 这种退而求其次的方式 (不推荐,但新手如果不了解 Idling Resource 如何使用,可以勉强用这个过度);另外,如果遇到这种错误:

test instrumentation process crashed. Check (包名.测试集名)#测试用例名.txt for details. 

时,这个错误文件在设备上/data/data/android.support.test.orchestrator/files/目录下面 (需要设备 root,否则会因为权限不够而无法导出或查看到此文件;还有可能文件内容是乱码,笔者还没研究如何查看乱码或者解码,后续碰到了该问题且有了办法会补上)

最后,贴两个正常执行完成成功的测试用例供参考:

package *.*.*;

import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import android.support.test.espresso.Espresso;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
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.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;
import org.junit.FixMethodOrder;
import org.junit.runners.MethodSorters;


@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@RunWith(AndroidJUnit4.class)
public class Login{
    @Rule
    public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(
            MainActivity.class);

    @Test
    public void loginFailed(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        onView(withId(R.id.tv_main_username)).perform(click());
        onView(withId(R.id.et_username)).perform(clearText());
        onView(withId(R.id.et_username)).perform(typeText("15323481573"));
        onView(withId(R.id.et_password)).perform(clearText());
        onView(withId(R.id.et_password)).perform(typeText("12345"));
        onView(withId(R.id.bt_user_login)).perform(click());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        onView(withId(R.id.tv_toast_login)).check(matches(withText("请正确输入密码")));
    }
    @Test
    public void loginSuccess(){

        onView(withId(R.id.tv_main_username)).perform(click());
        onView(withId(R.id.et_username)).perform(clearText());
        onView(withId(R.id.et_username)).perform(typeText("15323481573"));
        onView(withId(R.id.et_password)).perform(clearText());
        onView(withId(R.id.et_password)).perform(typeText("123456"));
        onView(withId(R.id.bt_user_login)).perform(click());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        onView(withId(R.id.tv_main_username)).check(matches(withText("Hello")));
    }
    @After
    public void clearData(){
    }
}

代码缺陷:
未使用Idling Resource而使用了绝对浪费时间的Thread.Sleep.

运行结果:

按以上的配置和代码用例写好后,运行时,Espresso 会在执行时,每次将 APP 重启且直接启动 MainActivity(也就是说如果这个 APP 有用于启动的 SplashActivity 之类的比如显示广告或者显示使用帮助之类的 Activity 时,会自动被跳过或者根本不会启动它),同时,笔者大概猜测了下,test:orchestrator 技术,实际上是在测试时为每个测试用例单独分配一个所谓的 Instrumentation,各自独立,为了做到 Case 之间完全独立不受影响,否则会出现多个测试用例一起运行时,前一个 Pass 后一个崩掉的情况。

参考来源:
Using Android Test Orchestrator
Test run failed: Instrumentation run failed due to 'Process crashed.'解析
How to see the Android Orchestrator log?


↙↙↙阅读原文可查看相关链接,并与作者交流