本篇讲如何通过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")));
}
}

网友评论