通过Espresso测试异步代码

作者: changer0 | 来源:发表于2018-04-14 14:13 被阅读13次

    本篇讲如何通过Espresso实现异步测试.

    概述

    如果没有框架的支持测试异步代码还是非常具有挑战性的 ! 在 Espresso 之前典型的做法就是等待预定的时间.或者在测试代码使用 CountDownLatch 类的实例, 并在异步处理完成时发出信号. 而 Espresso 使得异步测试变得容易很多,因为它自动检测 AsynchronousTask 后面的线程池.它还监视用户界面的事件队列, 仅在没有任务运行时才继续进行测试.

    示例

    下面示例根据官方的异步测试案例进行了简化处理:

    Espresso 提供了 IdlingResource 接口用来检视当前资源是否空闲.我们可以根据这个接口进行简单的封装让其更适合我们使用.

    在开始下面内容之前需要检查一下你的 Gradle 文件是否添加了 implementation 'com.android.support.test.espresso:espresso-intents:3.0.1' 依赖, 这个依赖是供 src/main/java 目录下访问的,用于我们接下来封装异步测试的工具类.

    src/main/java 目录下创建 SimpleCountingIdlingResource 类,实现 IdlingResource 接口. 该类实现整个项目异步请求个数的检视.

    package com.lulu.androidtestdemo.espresso.utils;
    
    import android.support.test.espresso.IdlingResource;
    import java.util.concurrent.atomic.AtomicInteger;
    /**
     * Created by zhanglulu on 2018/3/8.
     */
    public class SimpleCountingIdlingResource implements IdlingResource {
    
        private final String mResourceName;
    
        //这个counter值就像一个标记,默认为0
        private final AtomicInteger counter = new AtomicInteger(0);
    
        private volatile ResourceCallback resourceCallback;
    
        public SimpleCountingIdlingResource(String resourceName) {
            mResourceName = resourceName;
        }
    
        @Override
        public String getName() {
            return mResourceName;
        }
    
        @Override
        public boolean isIdleNow() {
            return counter.get() == 0;
        }
    
        @Override
        public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
            this.resourceCallback = resourceCallback;
        }
    
        //每当我们开始异步请求,把counter值+1
        public void increment() {
            counter.getAndIncrement();
        }
    
        //当我们获取到网络数据后,counter值-1;
        public void decrement() {
            int counterVal = counter.decrementAndGet();
            //如果这时counter == 0,说明异步结束,执行回调。
            if (counterVal == 0) {
                //
                if (null != resourceCallback) {
                    resourceCallback.onTransitionToIdle();
                }
            }
            if (counterVal < 0) {
                //如果小于0,抛出异常
                throw new IllegalArgumentException("Counter has been corrupted!");
            }
        }
    }
    

    在同一目录下添加 EspressoIdlingResource 类, 作为 SimpleCountingIdlingResource 的管理类,负责该类的创建和管理.

    public class EspressoIdlingResource {
        private static final String RESOURCE = "GLOBAL";
    
        private static SimpleCountingIdlingResource mCountingIdlingResource =
                new SimpleCountingIdlingResource(RESOURCE);
    
        public static void increment() {
            mCountingIdlingResource.increment();
        }
    
        public static void decrement() {
            mCountingIdlingResource.decrement();
        }
    
        public static IdlingResource getIdlingResource() {
            return mCountingIdlingResource;
        }
    }
    

    现在在我们的待测试的 Activity 中只需在异步代码开始时添加 EspressoIdlingResource.increment(), 结束时添加 EspressoIdlingResource.decrement() , 并添加 getIdlingResource() 方法方便我们在测试方法中调用.(可能这样做对代码有耦合,但是官方的做法确实如此).如果是在真正的项目中, 可以考虑将其放在BaseActivity中,下面就是待测试的 Activity.

    public class TestActivity extends AppCompatActivity {
    
        private TextView text;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_test);
            text = findViewById(R.id.text);
        }
    
        public void onButtonClick(View view) {
            EspressoIdlingResource.increment();
            Toast.makeText(this, "View Clicked", Toast.LENGTH_SHORT).show();
            switch (view.getId()) {
                case R.id.my_view:
                    new Thread(() -> {
                        try {
                            Thread.sleep(2000);
                            mHandler.sendEmptyMessage(0);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }).start();
                    break;
                case R.id.my_view2:
                    text.setText("Running");
                    break;
            }
    
        }
    
        private Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                text.setText("Done");
                if (!EspressoIdlingResource.getIdlingResource().isIdleNow()) {
                    EspressoIdlingResource.decrement();
                }
            }
        };
    
        @VisibleForTesting
        public IdlingResource getIdlingResource() {
            return EspressoIdlingResource.getIdlingResource();
        }
    }
    

    下面就是对应的测试方法, 检查 R.id.text 代码将会在子线程执行完成之后进行验证.

    /**
     * Created by zhanglulu on 2018/3/8.
     */
    @RunWith(AndroidJUnit4.class)
    public class EspressoAsyncTest {
        private IdlingResource idlingresource;
    
        @Rule
        public ActivityTestRule<TestActivity> activityRule = new ActivityTestRule(TestActivity.class);
    
        @Before
        public void before() {
            //调用Activity中我们已经设置好的getIdlingresource()方法,获取Idlingresource对象
            idlingresource = activityRule.getActivity().getIdlingResource();
            IdlingRegistry.getInstance().register(idlingresource);
        }
        @After
        public void after() {
            IdlingRegistry.getInstance().unregister(idlingresource);
        }
        @Test //is(String.class), is("Done")
        public void testAsync() {
            onView(withId(R.id.my_view)).perform(click());
            onView(withId(R.id.text)).check(matches(withText("Done")));
        }
    }
    
    敲行代码再睡觉

    相关文章

      网友评论

        本文标题:通过Espresso测试异步代码

        本文链接:https://www.haomeiwen.com/subject/kmllkftx.html