Android单元测试片段与机器人在MPV应用程序



我正在使用经典的MVP方法重塑我的应用程序。为了做到这一点,我读了很多文章和教程,我得出的结论是,最好的方法是:

  • 为演示者和视图创建一个接口
  • 使片段和活动实现视图接口
  • 创建一个演示器接口的实现,它在构造函数中接受它所管理的视图的一个实例,并在视图的实现中保存一个对演示器的引用。

我创建了这个类

视图界面

 public interface SignupEmailView extends BaseView {
        void fillEmail(String email);
        void onEmailInvalid(String error);
        void onDataValidated();
    }

主持人接口

public interface SignupEmailPresenter {
    void initData(Bundle bundle);
    void validateData(String email);
}

VIEW IMPLEMENTATION

public class FrSignup_email extends BaseSignupFragmentMVP implements IBackHandler, SignupEmailView {
        public static String PARAM_EMAIL = "param_email";
        @Bind(R.id.signup_step2_new_scrollview)
        ScrollView mScrollview;
        @Bind(R.id.signup_step2_new_lblTitle)
        SuperLabel mLblTitle;
        @Bind(R.id.signup_step2_new_lblSubtitle)
        TextView mLblSubtitle;
        @Bind(R.id.signup_step2_new_txtEmail)
        EditText mTxtEmail;
        @Bind(R.id.signup_step2_new_btnNext)
        Button mBtnNext;
        protected SignupActivityView mActivity;
        SignupEmailPresenter mPresenter;
        public FrSignup_email() {
            // Required empty public constructor
        }
        public static FrSignup_email newInstance(String email) {
            FrSignup_email fragment = new FrSignup_email();
            Bundle b = new Bundle();
            b.putString(PARAM_EMAIL, email);
            fragment.setArguments(b);
            return fragment;
        }
@Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            mActivity = (SignupActivityView) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString()
                    + " must implement IResetPasswordBridge");
        }
    }
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            View view = loadView(inflater, container, savedInstanceState, R.layout.fragment_signup_email);
            mPresenter = new SignupEmailPresenterImpl(this);
            ButterKnife.bind(this, view);
            return view;
        }
        @Override
        public final void onViewCreated(View view, Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            applyCircularReveal();
            mPresenter.initData(this.getArguments());
            mTxtEmail.setImeOptions(EditorInfo.IME_ACTION_NEXT);
            mTxtEmail.setOnEditorActionListener(new TextView.OnEditorActionListener() {
                @Override
                public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                    if (actionId == EditorInfo.IME_ACTION_NEXT) {
                        mPresenter.validateData(mTxtEmail.getText().toString());
                        return true;
                    }
                    return false;
                }
            });
            mTxtEmail.setOnTouchListener(new OnTouchCompoundDrawableListener_NEW(mTxtEmail, new OnTouchCompoundDrawableListener_NEW.OnTouchCompoundDrawable() {
                @Override
                public void onTouch() {
                    mTxtEmail.setText("");
                }
            }));
            mBtnNext.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mPresenter.validateData(mTxtEmail.getText().toString());
                }
            });
        }
        @Override
        public void fillEmail(String email) {
            mTxtEmail.setText(email);
        }
        @Override
        public void onEmailInvalid(String error) {
            displayError(error);
        }
        @Override
        public void onDataValidated() {
            changeFieldToValid(mTxtEmail);
            setEmail(mTxtEmail.getText().toString());
            // the activity shows the next fragment
            mActivity.onEmailValidated();
        }
        @Override
        public boolean doBack() {
            if (!isLoading()) {
                mActivity.onEmailBack();
            }
            return true;
        }
        @Override
        public void displayError(String error) {
            changeFieldToInvalid(mTxtEmail);
            mLblSubtitle.setText(error);
            mLblSubtitle.setTextColor(ContextCompat.getColor(getActivity(), R.color.field_error));
        }
    }

主持人实现

public class SignupEmailPresenterImpl implements SignupEmailPresenter {
    private SignupEmailView mView;
    public SignupEmailPresenterImpl(SignupEmailView view) {
        mView = view;
    }
    @Override
    public void initData(Bundle bundle) {
        if (bundle != null) {
            mView.fillEmail(bundle.getString(FrSignup_email.PARAM_EMAIL));
        }
    }
    @Override
    public void validateData(String password) {
        ValidationUtils_NEW.EmailStatus status = ValidationUtils_NEW.validateEmail(password);
        if (status != ValidationUtils_NEW.EmailStatus.VALID) {
            mView.onEmailInvalid(ValidationUtils_NEW.getEmailErrorMessage(status));
        } else {
            mView.onDataValidated();
        }
    }
}

现在这个片段由一个活动持有,这个活动实现了这个视图接口,并且有它自己的呈现者

public interface SignupActivityView extends BaseView {
    void onEmailValidated();
    void onPhoneNumberValidated();
    void onPasswordValidated();
    void onUnlockCodeValidated();
    void onResendCodeClick();
    void onEmailBack();
    void onPhoneNumberBack();
    void onPasswordBack();
    void onConfirmCodeBack();
    void onSignupRequestSuccess(boolean resendingCode);
    void onSignupRequestFailed(String errorMessage);
    void onTokenCreationFailed();
    void onUnlockSuccess();
    void onUnlockError(String errorMessage);
    void showTermsAndConditions();
    void hideTermsAndConditions();
}

我的想法是对每个项目单元进行单元测试,所以对于每个视图和演示器实现我想要一个单元测试,所以我想用roboletric对我的片段进行单元测试,例如我想测试如果我点击"NEXT"按钮并且电子邮件是正确的,主机活动的onEmailValidated()方法被调用。这是我的测试类

public class SignupEmailViewTest {
    private SignupActivity_NEW mActivity;
    private SignupActivity_NEW mSpyActivity;
    private FrSignup_email mFragment;
    private FrSignup_email mSpyFragment;
    private Context mContext;
    @Before
    public void setUp() {
        final Context context = RuntimeEnvironment.application.getApplicationContext();
        this.mContext = context;
        mActivity = Robolectric.buildActivity(SignupActivity_NEW.class).create().visible().get();
        mSpyActivity = spy(mActivity);
        mFragment = FrSignup_email.newInstance("");
        mSpyFragment =spy(mFragment);
        mSpyActivity.getFragmentManager()
                .beginTransaction()
                .replace(R.id.signupNew_fragmentHolder, mSpyFragment)
                .commit();
        mSpyActivity.getFragmentManager().executePendingTransactions();
    }
    @Test
    public void testEmailValidation() {
        assertTrue(mSpyActivity.findViewById(R.id.signup_step2_new_lblTitle).isShown());
        assertTrue(mSpyActivity.findViewById(R.id.signup_step2_new_lblSubtitle).isShown());
        mSpyActivity.findViewById(R.id.signup_step2_new_btnNext).performClick();
        assertTrue(((SuperLabel) mSpyActivity.findViewById(R.id.signup_step2_new_lblSubtitle)).getText().equals(mContext.getString(R.string.email_empty)));
        ((EditText) mSpyActivity.findViewById(R.id.signup_step2_new_txtEmail)).setText("aaa@bbb.ccc");
        mSpyActivity.findViewById(R.id.signup_step2_new_btnNext).performClick();
        verify(mSpyFragment).onDataValidated();
        verify(mSpyActivity).onEmailValidated();
    }
}

一切都很好,只是最后一个验证不起作用。注意,前面的验证是有效的,所以onEmailValidated肯定会被调用。

除了这个具体的案例,我还有一些观点要讨论:如果使用roboelectric,我被迫使用一个活动来实例化一个片段,我如何在完全隔离的情况下测试这个片段(这将是单元测试的目标)?我的意思是,如果我使用Robolectric.setupActivity(MyActivity.class)和活动实例化某处片段,它会加载活动和片段,这是好的,但如果活动管理碎片流呢?我如何测试第二个或第三个片段,而不手动导航到它?有人可以说使用虚拟活动并使用FragmentTestUtil.startFragment,但是在片段的onAttach()方法中实现了与父活动的桥接?是我走错路了,还是这个问题还没有解决?

谢谢

实际上你甚至不需要Roboelectric来做这些测试。

如果每个片段/活动实现了不同的视图接口,你可以实现假视图并实例化那些而不是活动/片段。这样就可以进行独立的测试。

如果你不想实现视图接口的所有方法,你可以使用Mockito,只保留单元测试需要的方法。

让我知道如果你需要样本代码。

相关内容

  • 没有找到相关文章

最新更新