在MPV应用程序中使用roboletric进行Android单元测试片段

时间:2016-09-08 10:43:43

标签: android unit-testing mvp robolectric android-testing

我正在使用经典的MVP方法重新发明我的应用程序。为了达到这个目的,我阅读了很多文章和教程,我最后提到的是最好的方法是:

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

所以我创建了这个类

查看界面

 public interface SignupEmailView extends BaseView {

        void fillEmail(String email);

        void onEmailInvalid(String error);

        void onDataValidated();
    }

PRESENTER INTERFACE

public interface SignupEmailPresenter {

    void initData(Bundle bundle);

    void validateData(String email);
}

查看实施

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。

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

感谢

1 个答案:

答案 0 :(得分:0)

实际上,您甚至不需要Roboelectric来进行任何这些测试。

如果每个片段/活动实现不同的视图接口,您可以实现伪视图并实例化那些而不是活动/片段。通过这种方式,您可以进行隔离测试。

如果您不想实现视图界面的所有方法,您可以使用Mockito并仅存储您的单元测试所需的那些。

如果您需要示例代码,请告诉我。