Google が推奨する Android テスト フレームワークを使用しています: ActivityInstrumentationTestCase2. ランダムなテストの実行で次のエラーが発生しましたが、永続的な致命的なエラーが発生しました。これは、すべてのテストが成功することもあれば (幸いなことに!)、多くの場合、これら 3 つのエラーのいずれかでランダムに失敗することを意味します。もどかしく、テスト結果に自信が持てません。
問題を詳しく説明するために、テスト クラスの簡略化された疑似コードと以下の 3 つの問題を提供しました。両方のテスト ケースは互いに独立しています。
public class FirstActivityTest
extends ActivityInstrumentationTestCase2<FirstActivity> {
private FirstActivity mActivity;
private ActivityMonitor mActivityMonitor;
public FirstActivityTest () {
super(FirstActivity.class);
}
public void setUp() throws Exception {
super.setUp();
setActivityInitialTouchMode(false);
mActivity = getActivity();
assertNotNull("Cannot start test since target Activity is NULL!", mActivity);
mActivityMonitor = getInstrumentation().addMonitor(SecondActivity.class.getName(), null, false);
}
public void tearDown() throws Exception {
super.tearDown();
if(mActivity != null) {
mActivity.finish();
mActivity = null;
}
if(mActivityMonitor != null) {
getInstrumentation().removeMonitor(mActivityMonitor);
mActivityMonitor = null;
}
}
/**
* Open FirstActivity, enter a text and click submit button.
* Verifies SecondActivity is open.
*/
public void testA_HappyPath() {
Activity secondActivity = null;
try {
//(Omitted) Get edit text and enter a valid value
//(Omitted) Find submitButton view
//Click submit button
TouchUtils.clickView(this, submitButton);
//Wait for result and validate:
secondActivity = mActivityMonitor.waitForActivityWithTimeout(10000);
assertNotNull("Result SecondActivity should NOT be null!", secondActivity );
} finally {
//Clean up:
if(secondActivity != null) {
secondActivity .finish();
secondActivity = null;
}
}
}
/**
* Open FirstActivity, do NOT enter a text and click submit button.
* Verifies error message is returned.
*/
public void testB_SadPath() {
//(Omitted) Find submitButton view
//Click submit button
TouchUtils.clickView(this, submitButton);
//(Omitted) Validate error message is displayed
}
}
ここで、これら 2 つのテスト ケースを何度も実行しました (アルファベット順に実行します)。結果は次のとおりです。
- 両方のテスト ケースに合格、または
- ActivityMonitor.waitForActivityWithTimeout() が NULL SecondActivity を返したため、testA_HappyPath() は失敗しました。しかし、自分のデバイスを見ると、SecondActivity が正しく表示されています。どういうわけか、テストはそれに気づきませんでした。なんで?
- testA_HappyPath() が失敗すると、次の testB_SadPath() が setUp() > getActivity() の間に無期限にハングしていました。私は、tearDown() 内のすべてをシャットダウンしたと思います。なんで?
- testB_SadPath () は TouchUtils.clickView() で次のエラーで失敗することがよくありました。なんで?
役立つフィードバックをお待ちしております。ありがとう!
私はこれら 3 つの問題に数日間対処し、インターネット上で多くの提案を研究し、試行錯誤を繰り返しました。ただし、特定の問題をすぐに解決できるものは1つもありませんでした-見つけたものを組み合わせることで、上記の問題(1)と(2)を解決しましたが、まだ問題(3)が解決されていません。以下は、この作業を行うために私が行った修正の詳細です。
問題 (1) ActivityMonitor.waitForActivityWithTimeout() が NULL を返す
1.1。getActivit() の前に getInstrumentation().addMonitor() を宣言する必要があることを学びました。setUp() メソッドをどのように変更したかを確認してください。これにより、問題が何らかの形で修正されました。これが要件である理由を理解している方は、お知らせください。
1.2. エミュレーターでは、この呼び出しが NULL を返すことがあり、テストが失敗することがありました。待ち時間が短すぎたことが原因だとわかりました。したがって、待機時間を長くすると、ActivityMonitor の戻りが早すぎるのを防ぐことができます。
問題 (2) 次の testB_SadPath() は、setUp() > getActivity() の間に無期限にハングします。
2.1. 上で説明したように、これは前のテスト (testA_HappyPath) が失敗したときに発生しました。私は、tearDown() がすべてをクリーンアップし、次のテストを実行する準備ができていると考えました。何が起こったのかというと、testA は SecondActivity が画面に表示されるのを待っていましたが、ActivityMonitor.waitForActivityWithTimeout() が NULL を返したため、testA は失敗しました。teaDown() は問題なく実行されました。問題は、SecondActivity が実際に画面に表示されたが、そのメソッド インスタンス 'secondActivity' がまだ null だったため、finally ブロックでシャットダウンされないことです。SecondActivity が有効で画面に残っていると、次の getActivity() がハングしていました。これを修正するには、finally ブロックを変更して、SecondActivity が存在するかどうかを確認し、シャットダウンするようにしました。
これらの変更は、以下のコードに要約されています (setUp() および finally ブロックを参照してください)。
public class FirstActivityTest
extends ActivityInstrumentationTestCase2<FirstActivity> {
private FirstActivity mActivity;
private ActivityMonitor mActivityMonitor;
public FirstActivityTest () {
super(FirstActivity.class);
}
public void setUp() throws Exception {
super.setUp();
setActivityInitialTouchMode(false);
}
public void tearDown() throws Exception {
super.tearDown();
if(mActivity != null) {
mActivity.finish();
mActivity = null;
}
if(mActivityMonitor != null) {
getInstrumentation().removeMonitor(mActivityMonitor);
mActivityMonitor = null;
}
}
/**
* Open FirstActivity, enter a text and click submit button.
* Verifies SecondActivity is open.
*/
public void testA_HappyPath() {
mActivityMonitor = getInstrumentation().addMonitor(SecondActivity.class.getName(), null, false);
mActivity = getActivity();
assertNotNull("Cannot start test since target Activity is NULL!", mActivity);
Activity secondActivity = null;
try {
//(Omitted) Get edit text and enter a valid value
//(Omitted) Find submitButton view
//Click submit button
TouchUtils.clickView(this, submitButton);
//Wait for result and validate:
secondActivity = mActivityMonitor.waitForActivityWithTimeout(20000);
assertNotNull("Result SecondActivity should NOT be null!", secondActivity );
} finally {
//Clean up:
if(secondActivity == null) {
//If empty, wait longer because need to shut down the foreground activity, if any:
secondActivity = mActivityMonitor.waitForActivityWithTimeout(20000);
}
if(secondActivity != null) {
secondActivity .finish();
secondActivity = null;
}
}
}
/**
* Open FirstActivity, do NOT enter a text and click submit button.
* Verifies error message is returned.
*/
public void testB_SadPath() {
mActivity = getActivity();
assertNotNull("Cannot start test since target Activity is NULL!", mActivity);
//(Omitted) Find submitButton view
//Click submit button
TouchUtils.clickView(this, submitButton);
//(Omitted) Validate error message is displayed
}
}
問題 (3) testB_SadPath() は、次のエラーで TouchUtils.clickView() で失敗することがよくありました: 「別のアプリケーションへの注入には INJECT_EVENTS 権限が必要です」
この最後の問題はまだ解決できません:-(