带测试的 MVP - 第 2 部分(测试 MVP 架构)
测试 MVP 架构
使用 Espresso 和 JUnit 测试 MVP
本节分为两部分,使用插桩测试测试 MVP UI,使用 junit 测试测试业务逻辑。本节重点测试项目特定逻辑,而不是测试框架是否正常工作。
测试 MoviePresenter
为了测试演示者的业务逻辑,我们需要模拟 UI 和存储库组件,因此主要重点将放在测试演示者及其与其他组件(如视图和存储库实例)的交互上。要应用模拟,我们将使用 mockito 框架。
设置
在module-name/src/test/java/包下创建的单元测试类在本地 JVM 上运行。这些测试本质上是快速的,并且没有 Android API 交互。
创建测试
要创建本地单元测试或仪表测试,您可以按照以下步骤为特定类或方法创建新的测试:
- 打开包含要测试的代码的 Java 文件。
- 单击要测试的类或方法,然后按 Ctrl+Shift+T。
- 在出现的菜单中,单击创建新测试。
- 在创建测试对话框中,编辑任何字段并选择要生成的任何方法,然后单击确定。
- 在“选择目标目录”对话框中,点击与要创建的测试类型相对应的源集:androidTest(用于仪表测试)或 test(用于本地单元测试)。然后点击“确定”。
测试演示者功能
- 接收用户事件
- 启动后台线程来检索响应
- 使用ArgumentCaptor捕获来自后台线程的响应
- 验证收到响应时模拟视图的行为
一步步解释
- 使用@Mock注释模拟诸如view和movieRepo之类的实例来测试presenter的完整功能。
- 带有@Before的方法用于设置测试环境和依赖项,在本例中,设置模拟框架并使用模拟实例初始化演示者。
- 创建一个argumentCaptor实例来捕获回调接口引用并为模拟视图实例提供响应。
- 创建并传递一个虚拟列表作为响应。
- 验证视图实例的方法调用是否成功。
MoviePresenterTest.java
public class MoviePresenterTest {
private static final Random RANDOM = new Random();
@Mock // 1
private MoviesListContract.View view;
@Mock // 1
private MovieRepo movieRepo;
private MoviePresenter presenter;
@Captor // 3
private ArgumentCaptor<MoviesListContract.OnResponseCallback> argumentCaptor;
@Before // 2
public void setUp(){
// A convenient way to inject mocks by using the @Mock annotation in Mockito.
// For mock injections , initMocks method needs to be called.
MockitoAnnotations.initMocks(this);
// get the presenter reference and bind with view for testing
presenter = new MoviePresenter(view,movieRepo);
}
@Test
public void loadMoviewList() throws Exception {
presenter.loadMoviewList();
verify(movieRepo,times(1)).getMovieList(argumentCaptor.capture());
argumentCaptor.getValue().onResponse(getList());
verify(view,times(1)).hideProgress();
ArgumentCaptor<List> entityArgumentCaptor = ArgumentCaptor.forClass(List.class);
verify(view).showMovieList(entityArgumentCaptor.capture());
assertTrue(entityArgumentCaptor.getValue().size() == 10);
}
@Test
public void OnError() throws Exception {
presenter.loadMoviewList();
verify(movieRepo,times(1)).getMovieList(argumentCaptor.capture());
argumentCaptor.getValue().onError("Error");
ArgumentCaptor<String> argumentCaptor = ArgumentCaptor.forClass(String.class);
verify(view,times(1)).showLoadingError(argumentCaptor.capture()); // 3
verify(view).showLoadingError(argumentCaptor.getValue()); // 4
}
private List<Movie> getList() {
ArrayList<Movie> movies = new ArrayList<>();
try {
movies.add(new Movie(RANDOM.nextInt(Integer.MAX_VALUE), "IT", Utility.convertStringToDate("2017-10-8"), 7.6, 127, Movie.Type.HORROR));
movies.add(new Movie(RANDOM.nextInt(Integer.MAX_VALUE), "Jumanji 2", Utility.convertStringToDate("2018-12-20"), 8.3, 111, Movie.Type.ACTION));
movies.add(new Movie(RANDOM.nextInt(Integer.MAX_VALUE), "The Dark Knight", Utility.convertStringToDate("2008-07-08"), 9.0, 152, Movie.Type.ACTION));
movies.add(new Movie(RANDOM.nextInt(Integer.MAX_VALUE), "Inception", Utility.convertStringToDate("2010-07-16"), 8.8, 148, Movie.Type.ACTION));
movies.add(new Movie(RANDOM.nextInt(Integer.MAX_VALUE), "The Green Mile", Utility.convertStringToDate("1999-12-10"), 8.5, 189, Movie.Type.DRAMA));
movies.add(new Movie(RANDOM.nextInt(Integer.MAX_VALUE), "Transcendence", Utility.convertStringToDate("2014-04-18"), 6.3, 120, Movie.Type.FICTION));
movies.add(new Movie(RANDOM.nextInt(Integer.MAX_VALUE), "Saving Private Ryan", Utility.convertStringToDate("1998-07-24"), 8.6, 169, Movie.Type.DRAMA));
movies.add(new Movie(RANDOM.nextInt(Integer.MAX_VALUE), "Whiplash", Utility.convertStringToDate("2015-02-20"), 8.5, 117, Movie.Type.DRAMA));
movies.add(new Movie(RANDOM.nextInt(Integer.MAX_VALUE), "The Raid", Utility.convertStringToDate("2011-04-13"), 7.6, 111, Movie.Type.ACTION));
movies.add(new Movie(RANDOM.nextInt(Integer.MAX_VALUE), "Burnt", Utility.convertStringToDate("2015-10-30"), 6.6, 111, Movie.Type.DRAMA));
} catch (ParseException e) {
e.printStackTrace();
}
return movies;
}
}
测试 MovieListActivity
为了对 UI 执行自动化测试,我们将使用 Espresso 框架执行插桩测试。Espresso 不了解任何异步操作或后台线程。为了让 Espresso 了解您应用的长时间运行的操作,我们将使用CountingIdlingResource。
MoviePresent必须增加空闲资源,以使 Espresso 了解空闲时间,如下所示
@Override
public void loadMoviewList() {
view.showProgress();
mclient.getMovieList(callback);
// required for espresso UI testing
EspressoTestingIdlingResource.increment();
}
并在收到响应时减少它。
使用实用程序类EspressoTestingIdlingResource以静态方式访问空闲资源
public class EspressoTestingIdlingResource {
private static final String RESOURCE = "GLOBAL";
private static CountingIdlingResource mCountingIdlingResource =
new CountingIdlingResource(RESOURCE);
public static void increment() {
mCountingIdlingResource.increment();
}
public static void decrement() {
mCountingIdlingResource.decrement();
}
public static IdlingResource getIdlingResource() {
return mCountingIdlingResource;
}
}
检查存储库中演示者代码的完整实现
一步步解释
- 在@Before注解的方法中注册空闲资源,让Espresso感知异步延迟。
- 编写测试来验证正在使用的默认空消息的可见性。
- 执行用户操作以启动数据检索任务。
- 检查屏幕上列表的可见性。
- 单击特定列表项。
- 验证包含电影详细信息的 toast 消息。
电影列表测试.java
@RunWith(AndroidJUnit4.class)
public class MovieListTest {
@Rule
public ActivityTestRule<MoviesListActivity> activityTestRule =
new ActivityTestRule<>(MoviesListActivity.class);
/**
* Register IdlingResource resource to tell Espresso when your app is in an
* idle state. This helps Espresso to synchronize test actions.
*/
@Before // 1
public void registerIdlingResource() {
IdlingRegistry.getInstance().register(EspressoTestingIdlingResource.getIdlingResource());
}
/**
* Unregister your Idling Resource so it can be garbage collected and does not leak any memory.
*/
@After
public void unregisterIdlingResource() {
IdlingRegistry.getInstance().unregister(EspressoTestingIdlingResource.getIdlingResource());
}
@Test // 2
public void checkStaticView() {
// verify default empty text message
onView(withId(R.id.swipe_msg_tv)).check(matches(isDisplayed()));
// |--------------------------|
//|----------------------------| find a view | using swipe_msg_tv id
//check visibility of view on screen <-------|
}
@Test
public void checkRecyclerViewVisibility() {
// 3. perform swipe
onView(withId(R.id.swipe_msg_tv)).check(matches(isDisplayed()));
onView(withId(R.id.swipe_container)).perform(swipeDown());
// verify swipe is displayed
onView(withId(R.id.swipe_msg_tv)).check(matches(not(isDisplayed())));
// 4 verify recycler view is displayed
onView(withId(R.id.movies_recycler_list)).check(matches(isDisplayed()));
// 5 perform click on item at 0th position
onView(withId(R.id.movies_recycler_list))
.perform(RecyclerViewActions.actionOnItemAtPosition(0, click()));
// 6 verify the toast text
MoviesListActivity activity = activityTestRule.getActivity();
onView(withText("Title : 'IT' Rating : '7.6'")).
inRoot(withDecorView(not(is(activity.getWindow().getDecorView())))).
check(matches(isDisplayed()));
}
}
奖金
Robolectric:Robolectric 是一个测试框架,可让您使用任何模拟器测试 Android 框架组件(活动、服务等)。它还能让您控制要测试的组件的生命周期。
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
请先 登录后发表评论 ~