0

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) 

답변

0

이것은 robolectric의 잘못이 아니지만 mockito를 이해하는 데 실패한 것입니다.

이 문제를 해결할 수있었습니다. 그것은 robolectric의 문제는 아니지만 내 자신의 mockito 프레임 워크의 이해하지. 나는 그것이 스파이의 javadoc에 문제 2 귀결 어떻게 든 생각 :

Mockito가 전달 된 실제 인스턴스에 호출을 위임하지 않는 대신 실제로 그것의 복사본을 만듭니다. 따라서 실제 인스턴스와 이 상호 작용하는 경우 스파이가 상호 작용을 인식하고 실제 인스턴스 상태에 미치는 영향을 인식하지 못하게하십시오. 그 결과는 입니다. 스텁에 대해 스텁되지 않은 메서드가 호출되었지만 실제 인스턴스에는 호출되지 않으면 실제 인스턴스에 아무런 영향을주지 않습니다.

실제 인스턴스를 보유하고 있지는 않지만 올바르게 작동하지 않는 것처럼 보입니다. 내 스텁을 만들기 위해 TableManagerFragment를 직접 서브 클래 싱하는 일이 생겼다. 비록 그것이 왜 작동하지 않는지 나는 완전히 모르겠지만, Mockito를 사용해서는 안된다는 것은 불행한 일입니다. 궁극적으로 나는 Mockito의 스파이를 사용하여 단편 작업을 할 수 있다고 생각하지 않습니다. 누군가가 반대로 정보를 가질 수 있고 가지고 있다면, 나는 그것을 듣고 싶습니다! 내 인생이 더 쉬울거야.

관련 문제