【问题标题】:How do you test an Android application across multiple Activities?如何跨多个活动测试 Android 应用程序?
【发布时间】:2009-11-18 22:30:23
【问题描述】:

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

由于我们有如此多的工作流程,我们需要创建跨越多个活动的自动化测试,以便我们可以端到端地测试工作流程。例如,使用 ATM 示例,我们想输入一个有效的 PIN,验证将我们发送到主菜单,选择提取现金,验证我们是否在提取现金屏幕上等等,最终找到自己返回主菜单或“退出”。

我们玩弄了 Android 附带的测试 API(例如 ActivityInstrumentationTestCase2)和 Positron,但似乎都无法测试超出单个 Activity 的范围,虽然我们可以找到一些在这些工具中用于某些单元测试的实用程序,它们将无法满足我们对跨越多个活动的测试场景的需求。

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

【问题讨论】:

标签: android automated-tests integration-testing android-testing


【解决方案1】:

回答我自己的悬赏问题有点尴尬,但这里是......

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

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 项活动进行了深度测试,并且对这种方法的工作感到满意,尽管正如我所说,似乎偶尔会出现一个我还没有完全弄清楚的时间问题。我仍然有兴趣了解跨活动的任何其他测试方式。

【讨论】:

  • 您可以尝试添加 FlakyTest 注释以在计时问题导致测试失败时自动重复测试。确实不是解决方案,但在某些情况下是可行的解决方法。
  • 感谢您撰写本文!我正在寻找具有 ActivityMonitors 功能的东西来进行测试。我只是找不到他们。
  • 据我所知,您在上面所做的任何事情都无法使用ActivityInstrumentationTestCase2
  • 任何想法,在什么条件下,'getInstrumentation().waitForIdleSync();'会进入无限循环吗?在运行处理器板的 android 4.4.2_r2 中,我在执行 CTS 测试时遇到了这个问题。
  • 我认为我的儿子@pajato1 发现并解决了您的计时问题。他的修复解决了我的问题。他是这样说的:“我刚刚在 javadoc 中注意到 Instrumentation.startActivitySync() 一直阻塞,直到新的 Activity 准备好然后返回它,所以似乎不需要 Monitor 。删除它证明这是正确的。我的理论上是 Monitor 导致 startActivitySync() 创建的 Activity 在某些情况下由于竞争条件而重新启动。我确实花了一些时间阅读 android 源代码,但没有任何东西跳出来作为竞争条件的原因。 "
【解决方案2】:

看看 Robotium
'一个开源测试框架,它使 Android 应用程序的自动黑盒测试比使用 Android 检测工具更快、更容易开箱即用的测试。'

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

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

【讨论】:

  • 嗨,有没有录音工具呢?我检查了许多网站,发现 testdroid 记录了脚本并运行它。不幸的是它不是免费软件,你知道有什么免费软件可以进行录制吗?
  • @thndrkiss:我不知道有任何这样的工具。如果您在 Robotium 论坛上提出问题,您可能会得到更好的答案。
  • Robotium 是救生员。它将使您的测试非常容易编写(您基本上是用简单的英语与它交谈:单击此按钮,按后退按钮等)您可以测试任何东西,但您不需要知道微小的细节。它至少有两个主要好处:您可以测试您没有源代码的应用程序,并且它依赖于使其非常强大的 UI(您更改控制器/模型比您的视图更多......)
【解决方案3】:

您始终可以使用 Robotium。它像 Selenium 一样支持黑盒测试,但适用于 Android。你可以在 Robotium.org 上找到它

【讨论】:

【解决方案4】:

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

MonkeyTalk:由 Gorilla Logic 公司支持的开源工具。优点:为非技术用户提供录制和更高级的脚本语言,并且是跨平台的(包括 iOS)。鉴于这些好处作为要求,我们发现这是最好的解决方案。它还允许customization 超出使用 Javascript 的脚本语言所能完成的工作。

Calabash-Android:Cucumber 风格的开源工具。优点:使用 Gherkin 语言编写功能,该语言是业务可读的领域特定语言,可让您描述软件的行为,而无需详细说明该行为是如何实现的。在cucumber-ios 中为iOS 提供了类似但不完全的支持。记录能力不如他们好,因为它们会产生二进制输出。

其他几个参考:

  • 这里有一些additional comparisons 在 Robotium 之间, Monkeytalk 和葫芦。它提到TestDroid 作为另一个 可能性。
  • 这个blog提到了上面加上NativeDriver和Bot-bot。

【讨论】:

    【解决方案5】:

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

    【讨论】:

    • 这看起来很有希望。对于那些不明白这一点的人:这似乎是测试手势(点击、拖动和其他东西)的一个很好的解决方案
    【解决方案6】:

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

    “ActivityInstrumentationTestCase2”构造函数忽略包参数并且不需要它。接受包的构造函数已弃用。

    来自 Javadocs: "ActivityInstrumentationTestCase2(String pkg, Class activityClass) 此构造函数已弃用。改用 ActivityInstrumentationTestCase2(Class)"

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

    【讨论】:

      【解决方案7】:

      经过一些修改后发现这很有用。 首先getInstrumentation().waitForIdleSync() 将治愈 SingleShot 所说的片状 还有InstrumentationTestCase 有一个lauchActivity 函数可以替换开始活动行。

      【讨论】:

        【解决方案8】:

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

        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)); 
        

        【讨论】:

          【解决方案9】:

          我正在做几乎相同的事情,我可能会对这个问题的公认答案做出改变,但我确实遇到了 Calculuon (gitHub) 在我寻找解决方案的过程中。

          【讨论】:

            【解决方案10】:

            我没有亲自使用过它,但 ApplicationTestCase 看起来可能是您正在寻找的。​​p>

            【讨论】:

            • 不幸的是,没有任何例子表明情况如此。
            • 是的,看起来你是对的......被名字欺骗了。我想不通这个。到目前为止,我最好的方法是使用 positron 的 ActivityUnitTestCase 来验证下一个活动是否开始,但这并不能帮助您构建连贯的故事。或者,InstrumentationTestCase.launchActivity 可能允许您启动任意数量的活动,但我仍在尝试找出 Instrumentation 的东西。
            【解决方案11】:

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

            【讨论】:

              【解决方案12】:

              还有另一种方法可以使用 ActivityInstrumentation 类进行多项活动。 它是一个正常的自动化场景...... 首先获取您想要的任何对象的焦点,然后发送一个密钥 就那么简单 示例代码

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

              只有了解每个 API 调用对我们有帮助。

              【讨论】:

                【解决方案13】:

                此答案基于已接受的答案,但经过修改以解决对我来说在添加大约六个测试后变得一致的时间问题。 @pajato1 因解决时间问题而受到赞誉,如已接受的答案 cmets 中所述。

                /**
                 * 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;
                }
                

                【讨论】:

                  【解决方案14】:

                  试试 Monkey 工具测试

                  第 1 步:

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

                  第 2 步:

                  为了使用 monkey ,打开命令提示符并导航到以下目录。

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

                  第 3 步:

                  将此猴子命令添加到终端并按回车..

                  看看你的模拟器的魔力。

                  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

                  【讨论】:

                  • 投反对票的人必须说出投反对票的原因......这是工作代码......也是官方测试方法。如果有任何错误,我准备纠正我的答案..
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2015-01-03
                  • 1970-01-01
                  相关资源
                  最近更新 更多