安卓测试上下文菜单

3

我正在尝试使用ActivityUnitTestCase或ActivityInstrumentationTestCase2来测试我的活动。在这个活动中,我想要测试我的

public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
        menu.setHeaderTitle(R.string.lcMenuHeader);

        menu.add(Menu.NONE, EDIT_MENU_POS, EDIT_MENU_POS, R.string.lcEditAccount);
        menu.add(Menu.NONE, COPY_MENU_POS, COPY_MENU_POS, R.string.lcCopyAccount);
        menu.add(Menu.NONE, DELETE_MENU_POS, DELETE_MENU_POS, R.string.lcDeleteAccount);
}

我该如何测试菜单是否处于正确的状态 - 即包括3个项目和给定标题?我可以在我的ActivityInstrumentationTestCase2测试中使用以下代码来触发其创建:

textView.performLongClick();

但是我不知道如何把它找回来。

谢谢 斯蒂芬

2个回答

2
最终,我在ActivityUnitTestCase测试用例中使用了反射技术:
AccountEntryContextMenuMock mock = new AccountEntryContextMenuMock();
ContextMenu menu = (ContextMenu)Proxy.newProxyInstance(mLogInActivity.
    getClassLoader(), new Class<?>[] { ContextMenu.class }, mock);

mLogInActivity.onCreateContextMenu(menu, accList, null);
//Now query the mock that the right things happened

模拟菜单如下:

public class AccountEntryContextMenuMock implements InvocationHandler {

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {

        if (method.getName().equals("setHeaderTitle")) {
             //record it and make available for test
        }
        else if (method.getName().equals("add")) {
             //record it and make available for test
        }

        return null;
    }
}

1

最终我找到了一种真正测试上下文菜单和操作栏项目内容的方法。

在先决条件下,您需要创建一个特殊的Android平台,其中包含一些内部Android类,否则这些类是不可用的。Inazaruk编写了一篇优秀的tutorial来指导如何做到这一点。

为了测试上下文菜单和操作栏项目,我们将使用以下类:

  • ANDROID_SDK/sources/android-VERSION/com/android/internal/view/menu/MenuBuilder
  • ANDROID_SDK/sources/android-VERSION/com/android/internal/view/menu/MenuItemImpl

请注意,内部类可能随时更改。这可能会导致此单元测试失败!

为了访问这些类中的私有字段,我们将使用一个辅助函数:

/**
* Get the value for field using relfection calls. This can be used to
* access private fields in objects.
* 
* @param sourceObject
*            : the object containing the field
* @param fieldName
*            : the name of the field from which the value has to be
*             retrieved
* @return value of the field
*/
private Object getValueForDeclaredField(Object sourceObject,
        String fieldName) {

    // Check parameters
    if (sourceObject == null) {
        throw new RuntimeException(
                "Error in function getValueForDeclaredField. "
                 + "Source object can not be null.");
    }
    if (fieldName == null || fieldName.trim().isEmpty()) {
        throw new RuntimeException(
                "Error in function getValueForDeclaredField. "
                + "Fieldname can not be null or empty.");
    }

    // Get field definition from source
    Field field = null;
    try {
        field = sourceObject.getClass().getDeclaredField(fieldName);
    } catch (Exception e) {
        throw new RuntimeException(
                "Error in function getValueForDeclaredField. Field '"
                        + fieldName
                        + "' does not exist in class '"
                        + sourceObject.getClass().getName()
                        + "'. Android implementation may have been changed. "
                        + "Please check this reflection.");
    }
    field.setAccessible(true);

    Object targetObject = null;
    try {
        targetObject = field.get(sourceObject);
    } catch (IllegalAccessException e) {
        throw new RuntimeException(
                "Error in function getValueForDeclaredField. Value for field '"
                        + fieldName
                        + "' in class '"
                        + sourceObject.getClass().getName()
                        + "' can not be retrieved. "
                        + "Android implementation may have been changed. "
                        + "Please check this reflection.");
    }
    return targetObject;
}

使用上面的辅助函数,我们现在可以使用以下代码检查视图的上下文菜单的内容。
    final int EXP_NUMBER_OF_CONTEXT_ITEMS = 1;
    final String EXP_CONTEXT_ITEM_0_TITLE = "YOUR TITLE FOR CONTEXT ITEM 0";

    // As the contextMenu is build dynamically each time it is needed, we
    // have to build the context menu in order to be able to test its
    // contents.
    ContextMenuBuilder contextMenuBuilder = new ContextMenuBuilder(
            mActivity);
    assertNotNull("Context menu is found", contextMenuBuilder);

    // Function contextMenuBuilder.show has to be called in order to
    // dynamically add the contents for the selected view. 
    // Notes:
    // - the context menu will not be shown actually.
    // - callbacks have not been attached to this menu.
    MenuDialogHelper contextMenuDialogHelper = contextMenuBuilder.show(
            YOUR_VIEW_HERE, view.getWindowToken());
    assertNotNull("ContextMenuDialogHelper is created",
            contextMenuDialogHelper);

    // Retrieve the menu from the Menu Builder.
    ContextMenuBuilder menu = (ContextMenuBuilder) getValueForDeclaredField(
            contextMenuDialogHelper, "mMenu");
    assertNotNull("ContextMenu is available", menu);

    // Retrieve and test items in the context menu
    assertEquals("Number of items in context menu", 
        EXP_NUMBER_OF_CONTEXT_ITEMS, menu.size());
    assertEquals("Title of item 0", EXP_CONTEXT_ITEM_0_TITLE,
            menu.getItem(0).toString());

    // Cleanup the conetxtMenuDiaglogHelper.
    contextMenuDialogHelper.dismiss();

使用上面的辅助函数,我们现在可以使用以下代码检查操作栏和活动菜单的内容。
/*
 * IMPORTANT NOTICE: This test uses internal classes which are not available
 * in the default "android.jar" file. Please refer to link mentioned above
 * for creating this file.
 * 
 * After creating a version of "android.jar" including interal classes your
 * unit-test-project should be targeted to this specific android version.
 */
public void testActionBar() {
    final String EXP_TITLE_ACTION_BAR = "YOUR ACTION BAR TITLE HERE";
    final int EXP_NUMBER_OF_ACTION_ITEMS = 2;
    final String EXP_ACTION_0_TITLE = "YOUR TITLE FOR ACTION BAR ITEM 0";
    final String EXP_ACTION_1_TITLE = "YOUR TITLE FOR ACTION BAR ITEM 1";

    ActionBar actionBar = mActivity.getActionBar();
    assertNotNull("Action bar is defined", actionBar);
    // Other assertiong for actionBar here

    // Retrieve the action view for the action bar.
    ViewGroup mActionView = (ViewGroup) getValueForDeclaredField(actionBar,
            "mActionView"); 
    assertNotNull("Action view exists", mActionView);

    // Check if context menu exists for the action view
    MenuBuilder mOptionsMenu = (MenuBuilder) getValueForDeclaredField(
            mActionView, "mOptionsMenu");
    assertNotNull("Options menu exists", mOptionsMenu);

    // Check action items for the action view
    ArrayList<MenuItemImpl> mActionItems = (ArrayList<MenuItemImpl>) 
        getValueForDeclaredField(mOptionsMenu, "mActionItems");
    assertEquals("Number of action items", EXP_NUMBER_OF_ACTION_ITEMS,
        mActionItems.size());

    // Check title and visibility of first action item
    MenuItemImpl action = mActionItems.get(0);
    assertEquals("Title of action item 0", EXP_ACTION_0_TITLE, 
        action.getTitle());
    assertTrue("Action item 0 is visible", action.isVisible());
    // other assertions for first action item here

    // Check title and visibility of second action item
    // Item will only be shown if room, so do not assert that it is shown.
    action = mActionItems.get(1);
    assertEquals("Title of action item 1", EXP_ACTION_1_TITLE, 
        action.getTitle());
    // other assertions for second action item here

    // Check non action items for the action view
    ArrayList<MenuItemImpl> mNonActionItems = (ArrayList<MenuItemImpl>)
        getValueForDeclaredField(mOptionsMenu, "mNonActionItems");
    assertEquals("Number of non action items",
            mNonActionItems.size(), 1);
    // other assertions for non action items here
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接