web-dev-qa-db-ja.com

複数のアクティビティ間でAndroidアプリケーションをテストするにはどうすればよいですか?

複雑なAndroid多くの画面と多数のアクティビティにまたがるワークフローで構成されるアプリケーションを作成しています。ワークフローは銀行のATMマシンで表示されるものと似ています。たとえば、Activityでログインすると、メインメニューActivityに移行し、ユーザーの選択に基づいて他のアクティビティに移行できます。

非常に多くのワークフローがあるので、ワークフローを端から端までテストできるように、複数のアクティビティにまたがる自動テストを作成する必要があります。たとえば、ATMの例を使用して、有効なPINを入力し、メインメニューに送られることを確認し、引き出しを選択し、引き出し画面にいることを確認するなど、最終的に自分自身を見つけます。メインメニューに戻るか、「ログアウト」します。

Android(例:ActivityInstrumentationTestCase2)および Positron を使用しますが、単一のActivityの範囲を超えてテストすることはできないようです。複数のアクティビティにまたがるテストシナリオのニーズを満たしません。

私たちは、xUnitフレームワーク、スクリプト、GUIレコーダー/再生などにオープンであり、アドバイスをいただければ幸いです。

79
SingleShot

私自身の報奨金の質問に答えるのは少し気まずい気がしますが、ここでは...

私はこれについて高いところと低いところを検索しましたが、どこにも答えが公開されていないとは信じられません。私は非常に近づいてきました。現在、アクティビティにまたがるテストを確実に実行できますが、私の実装には、テストが常に確実にパスしないタイミングの問題があるようです。これは、複数のアクティビティにわたるテストが成功していることを知っている唯一の例です。うまくいけば、それを抽出して匿名化してもエラーが発生しないことを願っています。これは単純なテストで、ログインアクティビティにユーザー名とパスワードを入力し、別の「welcome」アクティビティで適切なウェルカムメッセージが表示されることを確認します。

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つのアクティビティの深さまでテストし、アプローチが機能していることに満足していますが、先ほど言ったように、時折タイミングの問題があるように見えます。私はまだ、アクティビティ全体でテストする他の方法を聞くことに興味があります。

65
SingleShot

Robotiumを見てください
'Androidアプリケーションの自動ブラックボックステストをAndroidインストルメンテーションテストよりもはるかに高速かつ簡単にするために作成されたオープンソーステストフレームワークすぐに使用できます。」

ホームページ: http://www.robotium.org/
出典: http://github.com/jayway/robotium

Robotiumプロジェクトは、私が働いている会社によって管理されていることに注意してください

22

常にRobotiumを使用できます。 Seleniumと同様ですが、Android向けのブラックボックステストをサポートしています。 Robotium.orgで見つけることができます

8
Renas

主要な自動化された機能テストツールについて言及している人がいないことに驚いています。 Robotiumと比較して、これらはJavaコードを書く必要はありません。

MonkeyTalk:Gorilla Logic社が支援するオープンソースツール。長所:録音だけでなく、上級レベルのスクリプト言語を非技術ユーザーでも簡単に提供し、クロスプラットフォーム(iOSを含む)です。これらの利点を要件として考えると、これが最良のソリューションであることがわかりました。また、JavaScriptを使用してスクリプト言語で行えることを超えて カスタマイズ を許可します。

Calabash-Android :Cucumberスタイルの機能のためのオープンソースツール。長所:ビジネス向けのドメイン固有言語であるGherkin言語で機能を記述します。これにより、ソフトウェアの動作を実装方法を詳しく説明することなく記述できます。 iOSでは、同様の、ただし厳密ではないサポートが cucumber-ios で利用できます。記録機能は、バイナリ出力を生成するため、あまり優れていません。

その他の参照のカップル:

  • Robotium、Monkeytalk、Calabash間のいくつかの 追加の比較 があります。別の可能性として TestDroid に言及しています。
  • この blog は、上記に加えてNativeDriverとBot-botに言及しています。
4
John Lehmann

Android=の記録再生ツールを作成し、 GitHub で利用できるようにしました。設定と使用が簡単で、プログラミングが不要で、実際のデバイスに対して実行できます( (ルート化する必要はありません)、テストをプレイするときにスクリーンショットを自動的に保存します。

3

まず、基本クラスとして「InstrumentationTestCase」ではなく「ActivityInstrumentationTestCase2」を使用します。 Robotiumを使用し、複数のアクティビティで定期的にテストしています。ログインアクティビティをジェネリック型(およびコンストラクターのクラス引数)として指定する必要があることがわかりました。

'ActivityInstrumentationTestCase2'コンストラクターは、パッケージ引数を無視し、それを必要としません。パッケージを受け取るコンストラクタは非推奨です。

Javadocsから:「ActivityInstrumentationTestCase2(String pkg、Class activityClass)このコンストラクタは非推奨です。代わりにActivityInstrumentationTestCase2(Class)を使用してください」

推奨される基本クラスを使用すると、フレームワークはアクティビティの開始など、特定の定型文を処理できます。これは、必要に応じて「getActivity()」の呼び出しによって行われます。

3
Lew Bloch

いくつかの変更を加えると、これが便利であることがわかりました。まず、getInstrumentation().waitForIdleSync()は、SingleShotが言うフレークネスを解消します。また、InstrumentationTestCaseには、アクティビティの開始行を置き換えることができるlauchActivity関数があります。

3
typingduck

フレークの待ち時間が同期しないようにするには、次のようにします。

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

私はほぼ同じことに取り組んでおり、おそらくこの質問に対する受け入れられた答えのバリエーションで行くでしょうが、私は遭遇しましたCalculuongitHubソリューションの検索中。

1
Peter Ajtai

受け入れられたアプローチは、さまざまな証明書で署名されたさまざまなアプリケーションのさまざまなアクティビティで機能しますか?そうでない場合、Robotiumは同じアプリケーション内でアクティビティをテストする最適な方法です。

0
user643154

この回答は、受け入れられた回答に基づいていますが、タイミングの問題を解決するために修正されました。これは、半ダースのテストを追加した後、一貫したものになりました。 @ 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
pajato0

私は個人的に使用していませんが、ApplicationTestCaseはあなたが探しているもののようです。

0
Eric

ActivityInstrumentationクラスを使用して複数のアクティビティを実行する別の方法があります。通常の自動化シナリオ...まず、必要なオブジェクトのフォーカスを取得し、次にサンプルコードとしてSimpleキーを送信します。

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

唯一のことは、すべてのAPI呼び出しが役立つことを理解することです。

0
sandeep