草庐IT

android - 如何使用 Espresso 空闲资源进行网络调用

coder 2023-06-09 原文

我正在尝试使用 Espresso 来测试我的 UI。当我登录到我的应用程序时,我调用 Parse API(网络调用)来验证用户名和密码。如果一切顺利,用户将被引导至新 Activity 。我想对此进行测试,但我似乎无法使用空闲资源。

代码:

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("s@s.nl"), 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

当我运行测试时,正在填写用户名和密码,但从未调用过执行单击,几秒钟后我得到了异常。应该如何正确实现空闲资源?

编辑 --

我建议使用适用于 Android 的 Calabash。 Calabash 的工作原理类似,但不需要您更改应用代码进行测试。

最佳答案

就像其他答案所暗示的那样,countingIdlingResource 并不真正适用于您的用例。

我总是做的是添加一个接口(interface) - 我们称之为 ProgressListener - 作为具有等待资源的 Activity/fragment 的字段(异步后台工作,更长的网络 session ,等)以及每次显示或关闭进度时通知它的方法。

我假设您在 LoginActivity 中有凭据验证逻辑和对 Parse API 的调用,如果成功,它将调用 MainActivity 的 Intent 。

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 将等待您的登录过程完成,然后继续进行所有其他测试。

如果我不够清楚或者这正是您所需要的,请告诉我。

关于android - 如何使用 Espresso 空闲资源进行网络调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30733718/

有关android - 如何使用 Espresso 空闲资源进行网络调用的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  4. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  5. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  6. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  7. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  8. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  9. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  10. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

    在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

随机推荐