ListFragment에 대한 기본 테스트를 설정하려고하는데 심각한 문제가 발생합니다. Robolectric ListFragments에서 이상한 동작이 발생하여 장치에서 실행하기로 결정했습니다. Robolectric은 실제 장치에서 의도 한대로 정확하게 작동하는 것처럼 보이지만 Robolectric에서는 그렇지 않습니다. 우리는 non-support Fragments를 사용해야하기 때문에 2.3 스냅 샷 20140425.145412-162-jar-with-dependencies를 사용하고 있습니다.Robolectric ListFragment 동작이 장치에 따라 다르며 테스트하는 동안 null을 throw합니다.
장치에서 ListFragment를 실행하면 모든 것이 멋지게됩니다. Robolectric에서 실행하면 ListFragment$1.run(ListFragment.java:153)
에 null 포인터 예외가 발생합니다. 나 자신의 startFragment
방법을 추가하고 FragmentTestUtil
에 제공된 것을 사용하려고 시도했습니다.
내가 뭔가 잘못하고 있어도 기기에서 동작이 동일 할 것으로 예상되므로 버그 인 것 같습니다.
public class TableManagerFragment extends ListFragment {
private static final String TAG = TableManagerFragment.class.getSimpleName();
/** All the TableProperties that should be visible to the user. */
private List<TableProperties> mTableList;
private TablePropertiesAdapter mTpAdapter;
public TableManagerFragment() {
// empty constructor required for fragments.
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "[onCreate]");
this.mTableList = new ArrayList<TableProperties>();
this.setHasOptionsMenu(true);
this.setMenuVisibility(true);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
Log.d(TAG, "[onOptionsItemSelected] selecting an item");
return super.onOptionsItemSelected(item);
}
@Override
public View onCreateView(
LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState) {
Log.d(TAG, "[onCreateView]");
View view = inflater.inflate(
R.layout.fragment_table_list,
container,
false);
return view;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// call this here because we need a context.
List<TableProperties> newProperties = this.retrieveContentsToDisplay();
Log.e(TAG, "got newProperties list of size: " + newProperties.size());
this.setPropertiesList(newProperties);
this.mTpAdapter = new TablePropertiesAdapter(this.getPropertiesList());
this.setListAdapter(this.mTpAdapter);
this.mTpAdapter.notifyDataSetChanged();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.table_manager, menu);
super.onCreateOptionsMenu(menu, inflater);
}
/**
* Retrieve the contents that will be displayed in the list. This should be
* used to populate the list.
* @return
*/
List<TableProperties> retrieveContentsToDisplay() {
TableProperties[] tpArray = TableProperties.getTablePropertiesForAll(
getActivity(),
TableFileUtils.getDefaultAppName());
List<TableProperties> tpList = Arrays.asList(tpArray);
return tpList;
}
/**
* Get the list currently displayed by the fragment.
* @return
*/
List<TableProperties> getPropertiesList() {
return this.mTableList;
}
/**
* Update the contents of the list with the this new list.
* @param list
*/
void setPropertiesList(List<TableProperties> list) {
// We can't change the reference, which is held by the adapter.
this.getPropertiesList().clear();
for (TableProperties tp : list) {
this.getPropertiesList().add(tp);
}
}
}
그리고 fragment_table_list.xml
:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="#C0C0C0"
android:dividerHeight="1dp"
android:layout_weight="2"
android:drawSelectorOnTop="false" />
<TextView
android:id="@android:id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/no_data" />
</LinearLayout>
내 Robolectric 테스트 : 여기
내 ListFragment 클래스입니다@RunWith(RobolectricTestRunner.class)
public class TableManagerFragmentTest {
private TableManagerFragment fragment;
private Activity parentActivity;
public void setupFragmentWithNoItems() {
this.fragment = getSpy(new ArrayList<TableProperties>());
doGlobalSetup();
}
public void setupFragmentWithTwoItems() {
TableProperties tp1 = mock(TableProperties.class);
TableProperties tp2 = mock(TableProperties.class);
when(tp1.getDisplayName()).thenReturn("alpha");
when(tp2.getDisplayName()).thenReturn("beta");
List<TableProperties> listOfMocks = new ArrayList<TableProperties>();
listOfMocks.add(tp1);
listOfMocks.add(tp2);
this.fragment = getSpy(listOfMocks);
doGlobalSetup();
}
/**
* Does the setup required regardless of what the fragment is returning.
*/
public void doGlobalSetup() {
ShadowLog.stream = System.out;
// We need external storage available for accessing the database.
TestCaseUtils.setExternalStorageMounted();
startFragment(this.fragment);
this.parentActivity = this.fragment.getActivity();
// Have to call visible to get the fragment to think its been attached to
// a window.
ActivityController.of(this.parentActivity).visible();
}
/**
* Get a mocked TableManagerFragment that will return toDisplay when asked to
* retrieve TableProperties.
* @param toDisplay
* @return
*/
private TableManagerFragment getSpy(List<TableProperties> toDisplay) {
TableManagerFragment spy = spy(new TableManagerFragment());
doReturn(toDisplay).when(spy).retrieveContentsToDisplay();
return spy;
}
@Test
public void emptyViewIsVisibleWithoutContent() {
setupFragmentWithNoItems();
// We aren't retrieving any TableProperties, so it is empty.
// Weirdly, the List is also visible. Perhaps this is because the list view
// is always visible, just not taking up any screen real estate if there
// are no elements? Should investigate this when we have known elements.
View emptyView = this.fragment.getView().findViewById(android.R.id.empty);
assertThat(emptyView).isVisible();
}
@Test
public void listViewIsGoneWithoutContent() {
setupFragmentWithNoItems();
View listView = this.fragment.getView().findViewById(android.R.id.list);
assertThat(listView).isGone();
}
@Test
public void emptyViewIsGoneWithContent() {
setupFragmentWithTwoItems();
View emptyView = this.fragment.getView().findViewById(android.R.id.empty);
assertThat(emptyView).isGone();
}
@Test
public void listViewIsVisibleWithContent() {
setupFragmentWithTwoItems();
View listView = this.fragment.getView().findViewById(android.R.id.list);
assertThat(listView).isVisible();
}
@Test
public void hasCorrectMenuItems() {
setupFragmentWithNoItems();
ShadowActivity shadowActivity = shadowOf(parentActivity);
Menu menu = shadowActivity.getOptionsMenu();
assertThat(menu)
.hasSize(4)
.hasItem(R.id.menu_table_manager_export)
.hasItem(R.id.menu_table_manager_import)
.hasItem(R.id.menu_table_manager_sync)
.hasItem(R.id.menu_table_manager_preferences);
}
}
그리고 내 고장 추적 :
java.lang.NullPointerException
at android.app.ListFragment$1.run(ListFragment.java:153)
at org.robolectric.util.Scheduler.postDelayed(Scheduler.java:37)
at org.robolectric.shadows.ShadowLooper.post(ShadowLooper.java:207)
at org.robolectric.shadows.ShadowHandler.postDelayed(ShadowHandler.java:56)
at org.robolectric.shadows.ShadowHandler.post(ShadowHandler.java:51)
at android.os.Handler.post(Handler.java)
at android.app.ListFragment.ensureList(ListFragment.java:432)
at android.app.ListFragment.onViewCreated(ListFragment.java:203)
at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:843)
at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:1035)
at android.app.BackStackRecord.run(BackStackRecord.java:635)
at android.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1397)
at android.app.FragmentManagerImpl$1.run(FragmentManager.java:426)
at org.robolectric.util.Scheduler.postDelayed(Scheduler.java:37)
at org.robolectric.shadows.ShadowLooper.post(ShadowLooper.java:207)
at org.robolectric.shadows.ShadowHandler.postDelayed(ShadowHandler.java:56)
at org.robolectric.shadows.ShadowHandler.post(ShadowHandler.java:51)
at android.os.Handler.post(Handler.java)
at android.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1303)
at android.app.BackStackRecord.commitInternal(BackStackRecord.java:548)
at android.app.BackStackRecord.commit(BackStackRecord.java:532)
at org.robolectric.util.FragmentTestUtil.startFragment(FragmentTestUtil.java:14)
at org.opendatakit.tables.fragments.TableManagerFragmentTest.doGlobalSetup(TableManagerFragmentTest.java:60)
at org.opendatakit.tables.fragments.TableManagerFragmentTest.setupFragmentWithNoItems(TableManagerFragmentTest.java:38)
at org.opendatakit.tables.fragments.TableManagerFragmentTest.emptyViewIsVisibleWithoutContent(TableManagerFragmentTest.java:81)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:250)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:177)
at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)