一、问题背景

刚开始用的是百度cafe搭建的框架,已经用了一些版本,最后的test版本在7.4的apk上能跑,但是在最新发布的7.5的版本上跑不了,直接提示nullPointer错误,通过打日志的方式发现local这个变量就是null,也就是说根本没有被初始化为正确的值,那么原因是什么呢?

二、问题排查

从两个方面进行排查:

1、我们的test程序:

因为本身用cafe搭建的已经有一段时间,而且当时因为用的是非root的手机,所以没有办法进行debug,所以就想本来cafe就是对robotium的一个封装,那就试试robotium的最简单的程序,看能不能找到问题原因。

从网上下载了robotium的最新的源码,notepad和notepadtest,这个导入过程也记录一下,下载下来的是一个zip的压缩包,里面包含两个工程,导入的时候,通过file-import-general-existing into workspace-next-select archive file,具体见下图:

然后Browse,找到正确的文件,确定finish即可。

官网给出的例子是有源码的,演变成没有源码的版本,NotePadTest中的主要内容如下:

/*
* This is an example test project created in Eclipse to test NotePad which is a sample
* project located in AndroidSDK/samples/android-11/NotePad
*
*
* You can run these test cases either on the emulator or on device. Right click
* the test project and select Run As --> Run As Android JUnit Test
*
* @author Renas Reda, renas.reda@robotium.com
*
*/ package com.robotium.test; import com.robotium.solo.Solo;
import android.test.ActivityInstrumentationTestCase2; public class NotePadTest extends ActivityInstrumentationTestCase2{ private Solo solo;
private static String packageName = "com.example.android.notepad";
private static String mainActivity = "com.example.android.notepad.NotesList"; private static Class<?> launchableActivity; static{
try{
launchableActivity = Class.forName(mainActivity);
}catch(ClassNotFoundException e){ throw new RuntimeException(e);
}
}; public NotePadTest(){
super(packageName, launchableActivity);
} @Override
public void setUp() throws Exception {
//setUp() is run before a test case is started.
//This is where the solo object is created.
solo = new Solo(getInstrumentation(), getActivity());
} @Override
public void tearDown() throws Exception {
//tearDown() is run after a test case has finished.
//finishOpenedActivities() will finish all the activities that have been opened during the test execution.
solo.finishOpenedActivities();
} public void testAddNote() throws Exception {
//Unlock the lock screen
solo.unlockScreen();
solo.clickOnMenuItem("Add note");
//Assert that NoteEditor activity is opened
solo.assertCurrentActivity("Expected NoteEditor activity", "NoteEditor");
//In text field 0, enter Note 1
solo.enterText(0, "Note 1");
solo.goBack();
//Clicks on menu item
solo.clickOnMenuItem("Add note");
//In text field 0, type Note 2
solo.typeText(0, "Note 2");
//Go back to first activity
solo.goBack();
//Takes a screenshot and saves it in "/sdcard/Robotium-Screenshots/".
solo.takeScreenshot();
boolean notesFound = solo.searchText("Note 1") && solo.searchText("Note 2");
//Assert that Note 1 & Note 2 are found
assertTrue("Note 1 and/or Note 2 are not found", notesFound);
} public void testEditNote() throws Exception {
// Click on the second list line
solo.clickLongInList(2);
solo.clickOnText("Edit title");
// Change orientation of activity
solo.setActivityOrientation(Solo.LANDSCAPE);
//In first text field (0), add test
solo.enterText(0, " test");
//solo.goBack();
solo.setActivityOrientation(Solo.PORTRAIT);
// (Regexp) case insensitive
boolean noteFound = solo.waitForText("(?i).*?note 1 test");
//Assert that Note 1 test is found
assertTrue("Note 1 test is not found", noteFound);
} public void testRemoveNote() throws Exception {
//(Regexp) case insensitive/text that contains "test"
solo.clickOnText("(?i).*?test.*");
//Delete Note 1 test
solo.clickOnMenuItem("Delete");
//Note 1 test should not be found
boolean noteFound = solo.searchText("Note 1 test");
//Assert that Note 1 test is not found
assertFalse("Note 1 Test is found", noteFound);
solo.clickLongOnText("Note 2");
//Clicks on Delete in the context menu
solo.clickOnText("Delete");
//Will wait 100 milliseconds for the text: "Note 2"
noteFound = solo.waitForText("Note 2", 1, 100);
//Assert that Note 2 is not found
assertFalse("Note 2 is found", noteFound);
}
}

接下来我们的程序也按照类似这样的形式改造,单步调试(单步调试的快捷键请自行查找,我的是F7进函数,F8继续下一步),Run as Android junit test的情况下,发现需要手机root才可以,即需要在adb shell下能够显示#号才可以,因此我们采用了AVD进行调试,具体问题就是在setup的时候,solo = new Solo(getInstrumentation(), getActivity())方法,在第二个参数getActivity()这里,进入getActivity()方法,一步步往下调试的时候,发现卡在了下面方法中的加黄色的代码语句这里:

/**
* Start a new activity and wait for it to begin running before returning.
* In addition to being synchronous, this method as some semantic
* differences from the standard {@link Context#startActivity} call: the
* activity component is resolved before talking with the activity manager
* (its class name is specified in the Intent that this method ultimately
* starts), and it does not allow you to start activities that run in a
* different process. In addition, if the given Intent resolves to
* multiple activities, instead of displaying a dialog for the user to
* select an activity, an exception will be thrown.
*
* <p>The function returns as soon as the activity goes idle following the
* call to its {@link Activity#onCreate}. Generally this means it has gone
* through the full initialization including {@link Activity#onResume} and
* drawn and displayed its initial window.
*
* @param intent Description of the activity to start.
*
* @see Context#startActivity
*/
public Activity startActivitySync(Intent intent) {
validateNotAppThread(); synchronized (mSync) {
intent = new Intent(intent); ActivityInfo ai = intent.resolveActivityInfo(
getTargetContext().getPackageManager(), 0);
if (ai == null) {
throw new RuntimeException("Unable to resolve activity for: " + intent);
}
String myProc = mThread.getProcessName();
if (!ai.processName.equals(myProc)) {
// todo: if this intent is ambiguous, look here to see if
// there is a single match that is in our package.
throw new RuntimeException("Intent in process "
+ myProc + " resolved to different process "
+ ai.processName + ": " + intent);
} intent.setComponent(new ComponentName(
ai.applicationInfo.packageName, ai.name));
final ActivityWaiter aw = new ActivityWaiter(intent); if (mWaitingActivities == null) {
mWaitingActivities = new ArrayList();
}
mWaitingActivities.add(aw); getTargetContext().startActivity(intent); do {
try {
mSync.wait(); //就是卡在了这里
} catch (InterruptedException e) {
}
} while (mWaitingActivities.contains(aw)); return aw.activity;
}
}

以上这段代码如注释中所说:这个方法是启动一个Activity让其运行起来,加了同步锁的synchronized (mSync),然后走到了最下面的mSync.wait()这里,就一直等待,因为前面有一个内容挡住了这个wait,所以它一直等不到就一直出不了循环;只有等待成功,从do-while循环中出来,才能够启动并且将Activity返回。

在自己排查的过程中从网上找了一个帖子,具体见这里:http://www.2cto.com/kf/201410/344608.html,以及stackoverflow上的一个问题:http://stackoverflow.com/questions/20860832/why-does-getactivity-block-during-junit-test-when-custom-imageview-calls-start,虽然我卡住的地方跟他不同,但得到了一些启示,同时从robotium的源码中发现,getActivity()这个参数也可以去掉,因为我去掉这个方法后,在test程序安装之后,手动将程序启动起来,发现test能够正常run起来,不会出现挂起;然后就将手动启动程序的方式改成了通过调用方法自动启动程序的方法,见下方:

protected void startActivity(){
Intent it = new Intent();
it.setClassName(packageName, mainActivity);
it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getInstrumentation().getContext().startActivity(it);
}

然后在solo的setup方法的new Solo()方法之前调用startActivity(),具体见下方:

@Before
protected void setUp() throws Exception {
startActivity();
solo = new Solo(getInstrumentation());
}

通过这种方法就可以暂时将case流程跑起来,但是在后面需要用到solo.waitForActivity("xxxx")以及solo.getCurrentActivity()方法也一样无法执行,这样的方式相当于放弃了很多solo中的比较好用的方法,所以需要再换一种思路。

上面的debug过程在7.4版本上重点过了一下,发现并没有卡在wait的位置,定位之后希望能从开发的代码上进行排查看能不能找到一些线索。

2、从开发的代码进行排查:

需要找到7.5与7.4的不同,确定修改了哪些内容可能导致出现这个问题,然而在你向另外一个人提问的时候,他一般会按照自己的理解说出自己认为的重点,并且不同模块由不同的人负责,甚至有些人会说不清自己改了什么,所以就找了开发组里对整个框架以及代码最熟悉的同事帮忙看这个问题。

三、与开发协调推进解决

因为手动启动并运行开发的程序是OK的,但是通过test程序run被测程序的时候就会根本跑不通基本流程,因此还是从最基本的内容查,包括LaunchActivity的相关,以及公共的一些跟启动相关的,开发哥哥在经过三天排查之后,终于发现了问题原因,注释掉一行插件初始化的代码就可以运行起来,加上就无法运行。

四、先把测试跑通

因为先自己本地down到代码,然后把存在问题的代码注释掉之后,先将测试跑起来。但是这个问题是因为robotium本身就是基于安卓底层去获取一些内容,在进行设置的时候,因为在同一个进程中,可能会与开发的一些调用方法或者设置存在冲突,排查起来还是需要大家一起努力(PS:前提是自己能够把问题定位出来)。

所以后来也用了appium跑了一下,结果能够跑通,appium基于client-server的机制,并不会出现以上的问题,那appium是否可能存在其他的问题呢?这个也需要再碰到再探讨。

robotium(及百度cafe)运行testcase之后程序挂起没有响应的原因调查及解决的更多相关文章

  1. 2018年1月12日--微信小程序----出错报告(40kb的原因)---并解决方法

    一. 报错的原因是因为,图片的文件太大了,已经超过了40kb...不能超过40kb. 在这里查看图片文件的大小:

  2. Flash Builder 调试器无法连接到正在运行的应用程序(57%)

    Flash Builder 调试器无法连接到正在运行的应用程序(57%),可能原因:     1,flashplayer不是debug版.     2,调试器(用debug版flashplayer随便 ...

  3. 解决Windows 8.1 应用商店中安装程序挂起的解决

    阅读目录: 在Windows 8.1系统的应用程序商店中安装程序时,总是提醒“挂起”状态.无法进行后续安装,看了下国内不少用户有这个困扰,特此总结自己的解决方案. 1. 确保Windows updat ...

  4. Robotium调用getActivity()导致程序挂起的方法

    1. 问题背景的叙述性说明 需要直接用在工作中没有项目的源代码robotium测试目标android平台launcher,该平台的基础上,当前日期的版本号android 4.4.2.之前我用来验证的可 ...

  5. Qt开发北斗定位系统融合百度地图API及Qt程序打包发布

    Qt开发北斗定位系统融合百度地图API及Qt程序打包发布 1.上位机介绍 最近有个接了一个小型项目,内容很简单,就是解析北斗GPS的串口数据然后输出经纬度,但接过来觉得太简单,就发挥了主观能动性,增加 ...

  6. hadoop1.2.1配置与运行子串统计程序

    一.虚拟机版本 VirtualBox-4.3.30 二.操作系统 CentOS-6.7-x86_64-bin-DVD1.iso 下载地址1:http://www.centoscn.com/Centos ...

  7. Selenium启动Chrome浏览器提示“请停用以开发者模式运行的扩展程序”的解决办法

    安装了python selenium,运行下面代码: 1 from selenium import webdriver 2 3 browser = webdriver.Chrome() 4 brows ...

  8. 玩儿转物联网IoT - 在Beagle Bone Black上运行node.js 程序

    物联网(IoT)技术方兴未艾,智能手环,智能血压计,智能眼镜甚至智能鞋垫都开始进入我们的生活,各种智能设备层出不穷,世界已经到了一个"人有多大胆,地有多大产"的时代,不玩儿点物联网 ...

  9. 正在运行的android程序,按home键之后退回到桌面,在次点击程序图标避免再次重新启动程序解决办法

    正在运行的android程序,按home键之后退回到桌面,在次点击程序图标避免再次重新启动程序解决办法 例如:一个android程序包含两个Activity,分别为MainActivity和Other ...

随机推荐

  1. flume整合kafka

    # Please paste flume.conf here. Example: # Sources, channels, and sinks are defined per # agent name ...

  2. 在MyBook Live上部署svn

    一直以来都在用svn管理源代码,美中不足的是由于svn服务器端部署在本地,无法实现在异地迁入迁出程序,因此考虑将svn服务器部署在我的MyBook Live上. 一.部署svn 1.分别执行以下2条命 ...

  3. Java值传递以及引用的传递、数组的传递!!

    转(http://blog.csdn.net/niuniu20008/article/details/2953785) 许多编程语言都有2种方法将参数传递给方法------按值传递和按引用传递. 与其 ...

  4. Linux内核模块设计

    内核的设计有两种方式:单内核和微内核,两者各有优劣,关于两者的比较可以参见wiki.windowds和Solaris采用微内核结构. Linux内核采用单内核结构,设计比较简单,但单内核的理念是把所有 ...

  5. Xenko基础API笔记2-Enum Keys按键

    Name   Description A The 'a' key. Add The 'add' key. Apps The 'apps' key. Attn The 'attn' key. B The ...

  6. Spring集成Memcached三种方式(一)

    转载:http://blog.csdn.net/u013725455/article/details/52102170 Memcached Client目前有3种: Memcached Client ...

  7. leetcode 206

    206. Reverse Linked List Reverse a singly linked list. 翻转一个单链表. 代码如下: /** * Definition for singly-li ...

  8. MacBook下如何安装mysql-python

    解决方法: 先把之前装的卸载干净:pip uninstall mysql-pythonbrew uninstall mysql-connector-c 现在设置下mysql_config路径:首先修改 ...

  9. 转:MyBean的安装

    1.下载MyBean源码包.可以到https://git.oschina.net/ymofen/delphi-framework-MyBean下载Zip压缩包,也可以用Git客户端下载. 2.将框架源 ...

  10. sql 递归子查询

    select (SELECT (Case A1.AreaAbbr WHEN '' THEN A1.AreaName Else A1.AreaAbbr  END)+ ' ' FROM AreaLang ...