2015-06-09 89 views
12

我試圖使用Espresso來測試我的用戶界面。當我登錄到我的應用程序時,我會調用Parse API(網絡調用)來驗證用戶名和密碼。如果一切順利,用戶將被引導至新的活動。我想測試這個,但我似乎不能用閒置的資源的東西。如何使用Espresso怠速資源進行網絡通話

代碼:

public class ApplicationTest extends ActivityInstrumentationTestCase2<LoginActivity> { 


private CountingIdlingResource fooServerIdlingResource; 

public ApplicationTest() { 
    super(LoginActivity.class); 
} 

@Before 
public void setUp() throws Exception { 
    super.setUp(); 
    injectInstrumentation(InstrumentationRegistry.getInstrumentation()); 
    getActivity(); 
    CountingIdlingResource countingResource = new CountingIdlingResource("FooServerCalls"); 
    this.fooServerIdlingResource = countingResource; 
    Espresso.registerIdlingResources(countingResource); 
} 


public void testChangeText_sameActivity() { 
    // Type text and then press the button. 
    onView(withId(R.id.username)) 
      .perform(typeText("[email protected]"), closeSoftKeyboard()); 
    onView(withId(R.id.password)) 
      .perform(typeText("s"), closeSoftKeyboard()); 

    if(performClick()) 
     onView(withId(R.id.main_relative_layout)) 
       .check(matches(isDisplayed())); 
    // Check that the text was changed. 
} 

public boolean performClick(){ 
    fooServerIdlingResource.increment(); 
    try { 
     onView(withId(R.id.login)).perform(click()); 
     return true; 
    } finally { 
     fooServerIdlingResource.decrement(); 
    } 
} 


@SuppressWarnings("javadoc") 
public final class CountingIdlingResource implements IdlingResource { 
    private static final String TAG = "CountingIdlingResource"; 
    private final String resourceName; 
    private final AtomicInteger counter = new AtomicInteger(0); 
    private final boolean debugCounting; 

    // written from main thread, read from any thread. 
    private volatile ResourceCallback resourceCallback; 

    // read/written from any thread - used for debugging messages. 
    private volatile long becameBusyAt = 0; 
    private volatile long becameIdleAt = 0; 

    /** 
    * Creates a CountingIdlingResource without debug tracing. 
    * 
    * @param resourceName the resource name this resource should report to Espresso. 
    */ 
    public CountingIdlingResource(String resourceName) { 
     this(resourceName, false); 
    } 

    /** 
    * Creates a CountingIdlingResource. 
    * 
    * @param resourceName the resource name this resource should report to Espresso. 
    * @param debugCounting if true increment & decrement calls will print trace information to logs. 
    */ 
    public CountingIdlingResource(String resourceName, boolean debugCounting) { 
     this.resourceName = checkNotNull(resourceName); 
     this.debugCounting = debugCounting; 
    } 

    @Override 
    public String getName() { 
     return resourceName; 
    } 

    @Override 
    public boolean isIdleNow() { 
     return counter.get() == 0; 
    } 

    @Override 
    public void registerIdleTransitionCallback(ResourceCallback resourceCallback) { 
     this.resourceCallback = resourceCallback; 
    } 

    /** 
    * Increments the count of in-flight transactions to the resource being monitored. 
    * <p/> 
    * This method can be called from any thread. 
    */ 
    public void increment() { 
     int counterVal = counter.getAndIncrement(); 
     if (0 == counterVal) { 
      becameBusyAt = SystemClock.uptimeMillis(); 
     } 

     if (debugCounting) { 
      Log.i(TAG, "Resource: " + resourceName + " in-use-count incremented to: " + (counterVal + 1)); 
     } 
    } 

    /** 
    * Decrements the count of in-flight transactions to the resource being monitored. 
    * <p/> 
    * If this operation results in the counter falling below 0 - an exception is raised. 
    * 
    * @throws IllegalStateException if the counter is below 0. 
    */ 
    public void decrement() { 
     int counterVal = counter.decrementAndGet(); 

     if (counterVal == 0) { 
      // we've gone from non-zero to zero. That means we're idle now! Tell espresso. 
      if (null != resourceCallback) { 
       resourceCallback.onTransitionToIdle(); 
      } 
      becameIdleAt = SystemClock.uptimeMillis(); 
     } 

     if (debugCounting) { 
      if (counterVal == 0) { 
       Log.i(TAG, "Resource: " + resourceName + " went idle! (Time spent not idle: " + 
         (becameIdleAt - becameBusyAt) + ")"); 
      } else { 
       Log.i(TAG, "Resource: " + resourceName + " in-use-count decremented to: " + counterVal); 
      } 
     } 
     checkState(counterVal > -1, "Counter has been corrupted!"); 
    } 

    /** 
    * Prints the current state of this resource to the logcat at info level. 
    */ 
    public void dumpStateToLogs() { 
     StringBuilder message = new StringBuilder("Resource: ") 
       .append(resourceName) 
       .append(" inflight transaction count: ") 
       .append(counter.get()); 
     if (0 == becameBusyAt) { 
      Log.i(TAG, message.append(" and has never been busy!").toString()); 
     } else { 
      message.append(" and was last busy at: ") 
        .append(becameBusyAt); 
      if (0 == becameIdleAt) { 
       Log.w(TAG, message.append(" AND NEVER WENT IDLE!").toString()); 
      } else { 
       message.append(" and last went idle at: ") 
         .append(becameIdleAt); 
       Log.i(TAG, message.toString()); 
      } 
     } 
    } 
} 

}

例外,現在我得到的是以下幾點:

ndroid.support.test.espresso.IdlingResourceTimeoutException: Wait for [FooServerCalls] to become idle timed out 

當我運行測試,用戶名和密碼都得到填補,但執行點擊從未被調用,並在幾秒後得到異常。我應該如何正確實施閒置資源?

編輯 -

我會推薦使用Calabash的Android。 Calabash的工作原理類似,但不需要您更改應用程序代碼進行測試。

回答

16

與其他答案一樣,countingIdlingResource並不適用於您的用例。

我一直做的是增加一個接口 - 我們稱之爲一個ProgressListener - 作爲活動/片段,有一個資源上(異步後臺工作,更長的網絡會議等)的方法要等待的場每次顯示或解散進度時通知它。

我假設你有你的憑據驗證邏輯和LoginActivity中的Parse API調用,如果成功,它將調用MainActivity的意圖。

public class LoginActivity extends AppCompatActivity { 

    private ProgressListener mListener; 

    ...  

    public interface ProgressListener { 
     public void onProgressShown();   
     public void onProgressDismissed(); 
    } 

    public void setProgressListener(ProgressListener progressListener) { 
     mListener = progressListener; 
    } 

    ... 

    public void onLoginButtonClicked (View view) { 
     String username = mUsername.getText().toString(); 
     String password = mPassword.getText().toString(); 

     // validate credentials for blanks and so on 

     // show progress and call parse login in background method 
     showProgress(); 
     ParseUser.logInInBackground(username,password, new LogInCallback() { 
        @Override 
        public void done(ParseUser parseUser, ParseException e) { 
         dismissProgress(); 
         if (e == null){ 
          // Success!, continue to MainActivity via intent 
          Intent intent = new Intent (LoginActivity.this, MainActivity.class); 
          intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 
          intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); 
          startActivity(intent); 
         } 
         else { 
          // login failed dialog or similar. 
         } 
        } 
       }); 
    } 

    private void showProgress() { 
    // show the progress and notify the listener 
    ... 
    notifyListener(mListener); 
    } 

    private void dismissProgress() { 
    // hide the progress and notify the listener   
    ... 
    notifyListener(mListener); 
    }   

    public boolean isInProgress() { 
    // return true if progress is visible 
    } 

    private void notifyListener(ProgressListener listener) { 
     if (listener == null){ 
      return; 
     } 
     if (isInProgress()){ 
      listener.onProgressShown(); 
     } 
     else { 
      listener.onProgressDismissed(); 
     } 
    } 
} 

然後,只需執行IdlingResource類並重寫它的方法進行溝通時,資源從繁忙的經過其ResourceCallBack

public class ProgressIdlingResource implements IdlingResource { 

    private ResourceCallback resourceCallback; 
    private LoginActivity loginActivity; 
    private LoginActivity.ProgressListener progressListener; 

    public ProgressIdlingResource(LoginActivity activity){ 
     loginActivity = activity; 

     progressListener = new LoginActivity.ProgressListener() { 
      @Override 
      public void onProgressShown() { 
      } 
      @Override 
      public void onProgressDismissed() { 
       if (resourceCallback == null){ 
        return ; 
       } 
      //Called when the resource goes from busy to idle. 
      resourceCallback.onTransitionToIdle(); 
      } 
     }; 

     loginActivity.setProgressListener (progressListener); 
    } 
    @Override 
    public String getName() { 
     return "My idling resource"; 
    } 

    @Override 
    public boolean isIdleNow() { 
     // the resource becomes idle when the progress has been dismissed 
     return !loginActivity.isInProgress(); 
    } 

    @Override 
    public void registerIdleTransitionCallback(ResourceCallback resourceCallback) { 
     this.resourceCallback = resourceCallback; 
    } 
} 

最後一步,閒着也是在測試的註冊您的自定義閒置資源setUp()方法:

Espresso.registerIdlingResources(new ProgressIdlingResource((LoginActivity) getActivity())); 

就是這樣!現在espresso會等待您的登錄過程完成,然後繼續進行所有其他測試。

請讓我知道,如果我不夠清楚,或者如果這正是你需要的。

+0

今晚我會試試這個!謝謝 !將回到你:) –

+0

祝你好運,它可能看起來像一點額外的工作,但日誌記錄是非常有用的我 – appoll

+0

我試過了,它的工作!我用我的代碼離開了!沒有意識到在我的「應用程序」代碼中實現任何代碼。它是一個真正的缺點,改變我的應用程序代碼,使我的測試工作。我有很多Parse API調用。我相信我的代碼將會變得一團糟!但那不是問題,你回答了我真正的問題。非常感謝你的幫忙 ! –

0

Espresso將在執行點擊(或任何視圖操作)之前輪詢閒置資源。但是你不會減少你的櫃檯直到之後的點擊。這是一個僵局。

我在這裏看不到任何快速修復;你的方法對我來說並不合適。想到幾種可能的替代方法:

  • 根據您用於聯網的庫,您可能會編寫一個空閒資源來檢查是否有正在進行的調用。
  • 如果在進行登錄調用時顯示進度指示器,則可以安裝等待消息的IdlingResource。
  • 您可以等待下一個活動啓動,或者某個視圖出現/消失。
1

另一種方法是擁有一個可以檢查您的活動的自定義閒置資源。我已創建了一個位置:

public class RequestIdlingResource implements IdlingResource { 
    private ResourceCallback resourceCallback; 
    private boolean isIdle; 

    @Override 
    public String getName() { 
     return RequestIdlingResource.class.getName(); 
    } 

    @Override 
    public boolean isIdleNow() { 
     if (isIdle) return true; 

     Activity activity = getCurrentActivity(); 
     if (activity == null) return false; 

     idlingCheck(activity); 

     if (isIdle) { 
      resourceCallback.onTransitionToIdle(); 
     } 
     return isIdle; 
    } 

    private Activity getCurrentActivity() { 
     final Activity[] activity = new Activity[1]; 
     java.util.Collection<Activity> activities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED); 
     activity[0] = Iterables.getOnlyElement(activities); 
     return activity[0]; 
    } 

    @Override 
    public void registerIdleTransitionCallback(
      ResourceCallback resourceCallback) { 
     this.resourceCallback = resourceCallback; 
    } 

    public void idlingCheck(Activity activity) 
    { 
     /* 
      Look up something (view or method call) on the activity to determine if it is idle or busy 

     */ 
    } 
} 

https://gist.github.com/clivejefferies/2c8701ef70dd8b30cc3b62a3762acdb7

我從這裏的靈感,這說明它如何能在測試中使用:

https://github.com/AzimoLabs/ConditionWatcher/blob/master/sample/src/androidTest/java/com/azimolabs/f1sherkk/conditionwatcherexample/IdlingResourceExampleTests.java

的好處是,您不必將任何測試代碼添加到您的實現類中。

相關問題