Я изобретаю свое приложение, используя классический подход MVP. Для этого я прочитал много много статей и учебных пособий, а также то, что я пришел с в том, что лучший способ заключается в следующем:фрагмент тестирования блока Android с roboletric в приложении MPV
- создать интерфейс для ведущего и один для представления
- делает фрагменты и действия реализуют интерфейсы представлений
- создают реализацию интерфейса презентатора, который принимает в конструкторе экземпляр представления, которым он управляет, и удерживает ссылку на презентатора внутри реализации представления.
Так что я создал этот класс
VIEW ИНТЕРФЕЙС
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 РЕАЛИЗАЦИЯ
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, и, например, я хочу проверить, что если я нажму кнопку «СЛЕДУЮЩИЙ» и правильно по электронной почте, метод 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("[email protected]");
mSpyActivity.findViewById(R.id.signup_step2_new_btnNext).performClick();
verify(mSpyFragment).onDataValidated();
verify(mSpyActivity).onEmailValidated();
}
}
все работает хорошо, это только последняя проверка, которая не работает. Обратите внимание, что предыдущая проверка работает, поэтому onEmailValidated вызывается точно.
Помимо этого конкретного случая, я должен обсудить несколько вопросов: Если с roboeletric я вынужден использовать операцию для создания экземпляра фрагмента, как я могу проверить фрагмент в полной изоляции (что было бы целью единичных тестов)? Я имею в виду, если я использую Robolectric.setupActivity(MyActivity.class)
, и действие создает экземпляр где-то фрагмента, он будет загружать активность и фрагмент, что хорошо, но что делать, если активность управляет потоком фрагментов? Как я могу проверить второй или третий фрагмент без ручной навигации? Кто-то может сказать использовать фиктивную активность и использовать FragmentTestUtil.startFragment
, но что в методе onAttach()
фрагмента реализован мост с родительской активностью? Является ли это не так, или эти проблемы еще не решены?
благодаря
Вы отлаживали свой тест, чтобы подтвердить, что фрагмент использует объект активности шпиона? – nenick