您如何跨多个活动测试Android应用程序?


80

我们正在构建一个复杂的Android应用程序,该应用程序由分布在许多活动中的许多屏幕和工作流程组成。我们的工作流程类似于您在银行的ATM机上看到的工作流程,例如,Activity需要登录后才能转换到主菜单Activity,该菜单可以根据用户的选择转换为其他活动。

由于我们有这么多的工作流程,因此我们需要创建跨多个活动的自动化测试,以便可以从头到尾地测试工作流程。例如,以ATM为例,我们想输入一个有效的PIN,验证是否将我们发送到主菜单,选择提取现金,验证我们在提​​取现金屏幕上,等等,等等,最后找到自己。返回主菜单或“注销”。

我们戏弄了Android(例如ActivityInstrumentationTestCase2)和Positron随附的测试API ,但似乎都无法测试单个API的范围Activity,尽管我们可以在这些工具中找到用于某些单元测试的实用工具,但它们还是成功了无法满足我们对跨多个活动的测试场景的需求。

我们对xUnit框架,脚本,GUI记录器/回放等开放,并希望获得任何建议。


2
从Android 4.1开始,Android提供了一个新的测试框架,该框架可以跨活动以及整个系统进行测试:developer.android.com/tools/testing/testing_ui.html
Christopher Orr 2012年

1
Robotium仅在几行中就可以满足这种需求。
2013年

Answers:


65

我对回答自己的赏金问题感到有点尴尬,但这是...

我对此进行了严格的搜索,无法相信任何地方都没有发布答案。我已经很近了。我现在肯定可以运行跨活动的测试,但是我的实现似乎存在一些计时问题,这些问题并不总是能够可靠地通过。这是我所知道的唯一成功地跨多个活动进行测试的示例。希望我的提取和匿名化不会引入错误。这是一种简化测试,其中我在登录活动中键入用户名和密码,然后观察到在不同的“欢迎”活动中显示了正确的欢迎消息:

package com.mycompany;

import android.app.*;
import android.content.*;
import android.test.*;
import android.test.suitebuilder.annotation.*;
import android.util.*;
import android.view.*;
import android.widget.*;

import static org.hamcrest.core.Is.*;
import static org.hamcrest.core.IsNull.*;
import static org.hamcrest.core.IsInstanceOf.instanceOf;
import static org.junit.Assert.*;
import static com.mycompany.R.id.*;

public class LoginTests extends InstrumentationTestCase {

   @MediumTest
   public void testAValidUserCanLogIn() {

      Instrumentation instrumentation = getInstrumentation();

      // Register we are interested in the authentication activiry...
      Instrumentation.ActivityMonitor monitor = instrumentation.addMonitor(AuthenticateActivity.class.getName(), null, false);

      // Start the authentication activity as the first activity...
      Intent intent = new Intent(Intent.ACTION_MAIN);
      intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
      intent.setClassName(instrumentation.getTargetContext(), AuthenticateActivity.class.getName());
      instrumentation.startActivitySync(intent);

      // Wait for it to start...
      Activity currentActivity = getInstrumentation().waitForMonitorWithTimeout(monitor, 5);
      assertThat(currentActivity, is(notNullValue()));

      // Type into the username field...
      View currentView = currentActivity.findViewById(username_field);
      assertThat(currentView, is(notNullValue()));
      assertThat(currentView, instanceOf(EditText.class));
      TouchUtils.clickView(this, currentView);
      instrumentation.sendStringSync("MyUsername");

      // Type into the password field...
      currentView = currentActivity.findViewById(password_field);
      assertThat(currentView, is(notNullValue()));
      assertThat(currentView, instanceOf(EditText.class));
      TouchUtils.clickView(this, currentView);
      instrumentation.sendStringSync("MyPassword");

      // Register we are interested in the welcome activity...
      // this has to be done before we do something that will send us to that
      // activity...
      instrumentation.removeMonitor(monitor);
      monitor = instrumentation.addMonitor(WelcomeActivity.class.getName(), null, false);

      // Click the login button...
      currentView = currentActivity.findViewById(login_button;
      assertThat(currentView, is(notNullValue()));
      assertThat(currentView, instanceOf(Button.class));
      TouchUtils.clickView(this, currentView);

      // Wait for the welcome page to start...
      currentActivity = getInstrumentation().waitForMonitorWithTimeout(monitor, 5);
      assertThat(currentActivity, is(notNullValue()));

      // Make sure we are logged in...
      currentView = currentActivity.findViewById(welcome_message);
      assertThat(currentView, is(notNullValue()));
      assertThat(currentView, instanceOf(TextView.class));
      assertThat(((TextView)currentView).getText().toString(), is("Welcome, MyUsername!"));
   }
}

这段代码显然不是很可读。实际上,我已经使用类似于英语的API将其提取到一个简单的库中,所以我可以这样说:

type("myUsername").intoThe(username_field);
click(login_button);

我已经对大约4项活动进行了深入测试,并且对这种方法行得通的情况感到满意,尽管正如我所说,似乎偶尔会出现计时问题,但我并未完全弄清楚。我仍然对听到跨活动进行测试的任何其他方式感兴趣。


3
您可能会尝试添加FlakyTest批注以在计时问题导致其失败时自动自动重复测试。确实不是解决方案,但在某些情况下是可行的解决方法。
卡尔·马纳斯特

感谢您撰写本文!我一直在寻找具有ActivityMonitors功能的产品进行测试。我只是找不到它们。
彼得·阿杰泰

据我所知,您在上面所做的任何事情都无法使用ActivityInstrumentationTestCase2
ericn 2014年

任何想法,在什么条件下,“ getInstrumentation()。waitForIdleSync();” 会陷入无限循环?在运行处理器板的Android 4.4.2_r2中,我在执行CTS测试时遇到了这个问题。
ArunJTS 2014年

我认为我的儿子@ pajato1找到并解决了您的计时问题。他的解决方法解决了我的问题。他是这样说的:“我只是在javadoc中注意到Instrumentation.startActivitySync()一直阻塞,直到新的Activity准备好然后返回它为止,因此好像不需要Monitor。删除它证明这是正确的。我从理论上讲,由于竞争状况,Monitor导致由startActivitySync()创建的Activity在某些情况下被重新启动了,我确实花了一些时间阅读android源代码,但是没有任何事情引起我竞争状况的发生。 ”
pajato0年

22

让我们看一下Robotium,
“它是一种开放源代码的测试框架,旨在使Android应用程序的自动黑匣子测试比开箱即用的Android工具测试明显更快,更容易。”

主页: http
: //www.robotium.org/来源:http : //github.com/jayway/robotium

请注意,Robotium项目由我工作的公司维护


您好,是否有用于此目的的记录器工具?我检查了许多网站,发现testdroid记录并运行了脚本。不幸的是,它不是免费软件,您知道录制过程中有哪些免费软件吗?
thndrkiss 2011年

@thndrkiss:我不知道任何这样的工具。如果您在Robotium论坛上提出问题,则可能会得到更好的答案。
JonasSöderström,2011年

2
Robotium可以挽救生命。这将使您的测试非常容易编写(您基本上是用通俗易懂的语言与它交谈:单击此按钮,然后按返回按钮,等等。)您可以测试任何东西,但是您不需要知道这些微小的细节。它至少具有两个主要优点:您可以测试您没有源代码的应用程序,并且它依赖于UI,这使其非常健壮(您更改控制器/模型要比查看更多...)
tiktak


4

我很惊讶没有人提到一些领先的自动化功能测试工具。与Robotium相比,这些不需要编写Java代码。

MonkeyTalk:由Gorilla Logic公司支持的开源工具。优点:为非技术用户提供记录和高级脚本语言,并且跨平台(包括iOS)。考虑到这些好处作为要求,我们发现这是最好的解决方案。它还允许自定义,而不仅仅是使用Javascript以其脚本语言完成的工作。

Calabash-Android:用于黄瓜风格功能的开源工具。优点:使用Gherkin语言编写功能,该语言是业务可读的,特定于领域的语言,可让您描述软件的行为而无需详细说明该行为的实现方式。类似的但不是确切的支持可用于Cucumber-ios中的iOS。记录功能不佳,因为它们会产生二进制输出。

其他一些参考:

  • 这是Robotium,Monkeytalk和Calabash之间的一些其他比较。它提到了TestDroid作为另一种可能性。
  • 博客提到了以上内容以及NativeDriver和Bot-bot。

3

我为Android创建了一个记录和回放工具,并使其在GitHub可用。它易于配置和使用,无需编程,可在真实设备(无需植根)上运行,并在播放测试时自动保存屏幕截图。


这看起来很有希望。对于那些看不到重点的人:这似乎是测试手势(轻击,拖动和其他操作)的一个很好的解决方案
tiktak 2013年

3

首先,将“ ActivityInstrumentationTestCase2”而不是“ InstrumentationTestCase”用作您的基类。我使用Robotium,并在多个活动中进行常规测试。我发现必须将登录活动指定为通用类型(以及构造函数的类参数)。

“ ActivityInstrumentationTestCase2”构造函数将忽略包参数,并且不需要它。不推荐使用该软件包的构造函数。

来自Javadocs:“ ActivityInstrumentationTestCase2(String pkg,Class activityClass)已弃用。请改用ActivityInstrumentationTestCase2(Class)”

使用推荐的基类可以使框架处理某些样板,例如开始活动。如有必要,通过调用“ getActivity()”来完成。


3

进行了一些修改,发现这很有用。首先getInstrumentation().waitForIdleSync()将治愈SingleShot所说的脆弱性,并且还InstrumentationTestCase具有lauchActivity可以代替开始活动线的功能。


2

您可以像这样避免薄片等待时间不同步:

final Button btnLogin = (Button) getActivity().findViewById(R.id.button);
Instrumentation instrumentation = getInstrumentation();

// Register we are interested in the authentication activity...
Instrumentation.ActivityMonitor aMonitor = 
        instrumentation.addMonitor(mynextActivity.class.getName(), null, false);

getInstrumentation().runOnMainSync(new Runnable() {
         public void run() {
             btnLogin.performClick();
         }
     });

getInstrumentation().waitForIdleSync();

//check if we got at least one hit on the new activity
assertTrue(getInstrumentation().checkMonitorHit(aMonitor, 1)); 


0

我没有亲自使用过它,但是ApplicationTestCase看起来可能正是您想要的。


不幸的是,没有例子表明确实如此。
SingleShot

是的,看起来您是对的...被这个名字欺骗了。我不知道这一点。到目前为止,我最好的方法是使用正电子的ActivityUnitTestCase来验证下一个活动是否已启动,但这并不能帮助您构建连贯的故事。另外,InstrumentationTestCase.launchActivity可能允许您启动任意数量的活动,但是我仍在尝试找出Instrumentation的东西。
Eric

0

接受的方法是否适用于来自不同应用程序,由不同证书签署的不同活动?如果没有,Robotium是测试同一应用程序中活动的最佳方法。


0

还有另一种使用ActivityInstrumentation类进行多项活动的方法。这是正常的自动化方案...首先将焦点集中在所需的对象上,然后发送一个简单的示例代码

button.requestFocus();
sendKeys(KeyEvent.KEYCODE_ENTER);

唯一的了解是,每个API调用都会对我们有所帮助。


0

该答案基于公认的答案,但经过修改以解决计时问题,对我而言,添加了六打测试后变得一致。@ pajato1因解决计时问题而获得赞誉,如已接受的答案注释中所引用。

/**
 * Creates a test Activity for a given fully qualified test class name.
 *
 * @param fullyQualifiedClassName The fully qualified name of test activity class.
 *
 * @return The test activity object or null if it could not be located.
 */
protected AbstractTestActivity getTestActivity(final String fullyQualifiedClassName) {
    AbstractTestActivity result = null;

    // Register our interest in the given activity and start it.
    Log.d(TAG, String.format("Running test (%s) with main class: %s.", getName(), fullyQualifiedClassName));
    instrumentation = getInstrumentation();

    Intent intent = new Intent(Intent.ACTION_MAIN);
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    intent.setClassName(instrumentation.getTargetContext(), fullyQualifiedClassName);
    // Wait for the activity to finish starting
    Activity activity = instrumentation.startActivitySync(intent);

    // Perform basic sanity checks.
    assertTrue("The activity is null!  Aborting.", activity != null);
    String format = "The test activity is of the wrong type (%s).";
    assertTrue(String.format(format, activity.getClass().getName()), activity.getClass().getName().equals(fullyQualifiedClassName));
    result = (AbstractTestActivity) activity;

    return result;
}

0

尝试猴子工具测试

步骤1:

打开android studio终端(工具->打开终端)

第2步:

为了使用monkey,打开命令提示符,然后naviagte进入以下目录。

 export PATH=$PATH:/home/adt-bundle-linux-x86-20140702/sdk/platform-tools

第三步:

将此猴子命令添加到终端,然后按Enter。

在模拟器中查看魔术。

adb shell monkey -p com.example.yourpackage -v 500

500-是频率计数或要发送进行测试的事件数。

您可以更改此计数。

更多参考,

http://www.tutorialspoint.com/android/android_testing.htm

http://androidtesting.blogspot.in/2012/04/android-testing-with-monkey-tool.html


拒绝投票的人必须说明拒绝投票的原因...这是有效的代码..以及官方测试方法。如果有任何错误,我准备纠正我的答案..
Ranjith Kumar
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.