Exploramos los conceptos del patrón Model View Presenter en la primera parte de esta serie e implementamos nuestra propia versión del patrón en la segunda parte. Ahora es el momento de profundizar un poco más. En este tutorial, nos centramos en los siguientes temas:
Una de las mayores ventajas de adoptar el patrón MVP es que simplifica las pruebas unitarias. Entonces, escribamos pruebas para las clases de Modelo y Presentador que creamos e implementamos en la última parte de esta serie. Ejecutaremos nuestras pruebas utilizando Robolectric, un marco de prueba unitaria que proporciona muchos apéndices útiles para las clases de Android. Para crear objetos simulados, usaremos Mockito, que nos permite verificar si se han llamado ciertos métodos..
Editar el construir.gradle archivo de su módulo de aplicación y agregue las siguientes dependencias.
dependencies //… testCompile 'junit: junit: 4.12' // Establezca esta dependencia si desea usar el test de Hamcrest testCompile 'org.hamcrest: hamcrest-library: 1.1' testCompile "org.robolectric: robolectric: 3.0" testCompile 'org .mockito: mockito-core: 1.10.19 '
Dentro del proyecto src carpeta, crear la siguiente estructura de carpetas prueba / java / [nombre-paquete] / [nombre-aplicación]. A continuación, cree una configuración de depuración para ejecutar el conjunto de pruebas. Hacer clic Editar configuraciones ... en la cima.
Haga clic en el + botón y seleccione JUIT de la lista.
Conjunto Directorio de trabajo a $ MODULE_DIR $.
Queremos que esta configuración ejecute todas las pruebas unitarias. Conjunto Tipo de prueba a Todo en paquete e ingrese el nombre del paquete en el Paquete campo.
Comencemos nuestras pruebas con la clase Modelo. La prueba unitaria se ejecuta utilizando RobolectricGradleTestRunner.class
, que proporciona los recursos necesarios para probar las operaciones específicas de Android. Es importante anotar @Cofing
con las siguientes opciones:
@RunWith (RobolectricGradleTestRunner.class) // Cambie lo que sea necesario para su proyecto @Config (constants = BuildConfig.class, sdk = 21, manifest = "/src/main/AndroidManifest.xml") clase pública MainModelTest // escriba el pruebas
Queremos utilizar un DAO real (objeto de acceso a datos) para probar si los datos se están manejando correctamente. Para acceder a un Contexto
, usamos el RuntimeEnvironment.application
clase.
DAO privado mDAO; // Para probar el modelo, simplemente // puede crear el objeto y pasar // un simulacro de presentador y una instancia de DAO @Antes de public void setup () // El uso de RuntimeEnvironment.application nos permitirá a // acceder a un contexto y cree un DAO real // insertando datos que se guardarán temporalmente Context context = RuntimeEnvironment.application; mDAO = nuevo DAO (contexto); // El uso de un Presentador simulado permitirá verificar // si se llamaron ciertos métodos en el Presenter MainPresenter mockPresenter = Mockito.mock (MainPresenter.class); // Creamos una instancia de modelo utilizando una construcción que incluye // un DAO. Este constructor existe para facilitar las pruebas mModel = new MainModel (mockPresenter, mDAO); // Es necesario suscribirse a mNotes para los métodos de prueba // que dependen de arrayList mModel.mNotes = new ArrayList <> (); // Estamos reiniciando nuestro simulador de presentador para garantizar que // la verificación de nuestro método permanezca consistente entre los reinicios de las pruebas (mockPresenter);
Ahora es el momento de probar los métodos del modelo..
// Crear un objeto Note para usar en las pruebas private Note createNote (String text) Note note = new Note (); note.setText (texto); note.setDate ("alguna fecha"); nota de retorno // Verificar loadData @Test public void loadData () int notesSize = 10; // insertar datos directamente usando DAO para (int i = 0; i-1); // Verificar deleteNote @Test public void deleteNote () // Necesitamos agregar una nota en DB Note note = createNote ("testNote"); Nota insertada = mDAO.insertNota (nota); // agregar la misma nota dentro de mNotes ArrayList mModel.mNotes = new ArrayList <> (); mModel.mNotes.add (insertNote); // verifique si deleteNote devuelve los resultados correctos assertTrue (mModel.deleteNote (InsertNote, 0)); Tenga en cuenta fakeNote = createNote ("fakeNote"); assertFalse (mModel.deleteNote (fakeNote, 0));
Ahora puede ejecutar la prueba del modelo y comprobar los resultados. Siéntete libre de probar otros aspectos de la clase..
Centrémonos ahora en probar al presentador. También necesitamos Robolectric para esta prueba para hacer uso de varias clases de Android, como AsyncTask
. La configuración es muy similar a la prueba del modelo. Usamos las simulaciones de vista y modelo para verificar las llamadas de método y definir valores de retorno.
@RunWith (RobolectricGradleTestRunner.class) @Config (constants = BuildConfig.class, sdk = 21, manifest = "/src/main/AndroidManifest.xml") clase pública MainPresenterTest private MainPresenter mPresenter; MainModel privado mockModel; privado MVP_Main.RequiredViewOps mockView; // Para probar el Presentador, simplemente puede // crear el objeto y pasar las simulaciones del modelo y de la vista @Antes de public void setup () // Crear las simulaciones mockView = Mockito.mock (MVP_Main.RequiredViewOps.class); mockModel = Mockito.mock (MainModel.class, RETURNS_DEEP_STUBS); // Pasar las simulaciones a una instancia de Presenter mPresenter = new MainPresenter (mockView); mPresenter.setModel (mockModel); // Defina el valor que devolverá el Modelo // al cargar los datos cuando (mockModel.loadData ()). ThenReturn (true); restablecer (mockView);
Para probar los métodos del presentador, comencemos con el haga clic enNotaNota ()
operación, que es responsable de crear una nueva nota y registrarla en la base de datos usando un AsyncTask
.
@Test público void testClickNewNote () // Necesitamos simular un EditText EditText mockEditText = Mockito.mock (EditText.class, RETURNS_DEEP_STUBS); // el simulacro debería devolver una cadena cuando (mockEditText.getText (). toString ()). thenReturn ("Test_true"); // también definimos una posición falsa para que sea devuelta // por el método insertNote en el Modelo int arrayPos = 10; when (mockModel.insertNote (any (Note.class)). ThenReturn (arrayPos); mPresenter.clickNewNote (mockEditText); Verify (mockModel) .insertNote (any (Note.class)); (eq (arrayPos + 1)); Verify (mockView) .notifyItemRangeChanged (eq (arrayPos), anyInt ()); Verify (mockView, never ()). showToast (any (Toast.class));
También podríamos probar un escenario en el que el insertNote ()
método devuelve un error.
@Test público void testClickNewNoteError () EditText mockEditText = Mockito.mock (EditText.class, RETURNS_DEEP_STUBS); when (mockModel.insertNote (any (Note.class)). thenReturn (-1); when (mockEditText.getText (). toString ()). thenReturn ("Test_false"); when (mockModel.insertNote (any (Note.class)). thenReturn (-1); mPresenter.clickNewNote (mockEditText); verificar (mockView) .showToast (any (Toast.class));
Finalmente, probamos borrar nota()
Método, considerando tanto un resultado exitoso como un fracaso..
@Test público void testDeleteNote () when (mockModel.deleteNote (any (Note.class), anyInt ())). ThenReturn (true); int adapterPos = 0; int layoutPos = 1; mPresenter.deleteNote (new Note (), adapterPos, layoutPos); verificar (mockView) .showProgress (); verificar (mockModel) .deleteNote (any (Note.class), eq (adapterPos)); verificar (mockView) .hideProgress (); verificar (mockView) .notifyItemRemoved (eq (layoutPos)); verificar (mockView) .showToast (any (Toast.class)); @Test público void testDeleteNoteError () when (mockModel.deleteNote (any (Note.class), anyInt ())). ThenReturn (false); int adapterPos = 0; int layoutPos = 1; mPresenter.deleteNote (new Note (), adapterPos, layoutPos); verificar (mockView) .showProgress (); verificar (mockModel) .deleteNote (any (Note.class), eq (adapterPos)); verificar (mockView) .hideProgress (); verificar (mockView) .showToast (any (Toast.class));
La inyección de dependencia es una gran herramienta disponible para los desarrolladores. Si no está familiarizado con la inyección de dependencia, le recomiendo que lea el artículo de Kerry sobre el tema..
La inyección de dependencia es un estilo de configuración de objeto en el que los campos y los colaboradores de un objeto están configurados por una entidad externa. En otras palabras, los objetos están configurados por una entidad externa. La inyección de dependencia es una alternativa a que el objeto se configure por sí mismo. - Jakob Jenkov
En este ejemplo, la inyección de dependencia permite que el Modelo y el Presentador se creen fuera de la Vista, lo que hace que las capas de MVP se acoplen de forma más libre y aumente la separación de preocupaciones..
Usamos Dagger 2, una increíble biblioteca de Google, para ayudarnos con la inyección de dependencia. Si bien la configuración es sencilla, la daga 2 tiene muchas opciones geniales y es una biblioteca relativamente compleja..
Nos concentramos solo en las partes relevantes de la biblioteca para implementar MVP y no cubriremos la biblioteca con mucho detalle. Si desea obtener más información sobre Dagger, lea el tutorial de Kerry o la documentación proporcionada por Google.
Comience por actualizar el proyecto construir.gradle archivo añadiendo una dependencia.
dependencias // ... classpath 'com.neenbedankt.gradle.plugins: android-apt: 1.8'
A continuación, edite el proyecto build.dagger archivo como se muestra a continuación.
aplique el complemento: 'com.neenbedankt.android-apt' dependencies // el comando apt viene del complemento android-apt apt 'com.google.dagger: dagger-compiler: 2.0.2' compile 'com.google.dagger: dagger : 2.0.2 'proporcionado' org.glassfish: javax.annotation: 10.0-b28 '//…
Sincronice el proyecto y espere a que la operación se complete.
Comencemos creando una @Alcance
Para el Actividad
clases Crear un @anotación
con el nombre del alcance.
@Scope public @interface ActivityScope
A continuación, trabajamos en un @Módulo
Para el Actividad principal
. Si tiene múltiples actividades, debe proporcionar un @Módulo
para cada Actividad
.
@Module public class MainActivityModule actividad MainActivity privada; mainActivityModule público (actividad MainActivity) this.activity = activity; @Proporciona @ActivityScope MainActivity proporcionaMainActividad () actividad de retorno; @Proporciona @ActivityScope MVP_Main.ProvidedPresenterOps proporcionadoPresenterOps () MainPresenter presenter = new MainPresenter (actividad); Modelo de MainModel = nuevo MainModel (presentador); presenter.setModel (modelo); presentador de vuelta
También necesitamos un @Subcomponente
Para crear un puente con nuestra aplicación. @Componente
, que todavía necesitamos crear.
@ActivityScope @Subcomponent (modules = MainActivityModule.class) interfaz pública MainActivityComponent MainActivity inyect (actividad MainActivity);
Tenemos que crear un @Módulo
y un @Componente
Para el Solicitud
.
@Module public class AppModule aplicación privada; AppModule público (aplicación) this.application = application; @Provides @Singleton public Application proveeApplication () return application;
@Singleton @Component (modules = AppModule.class) interfaz pública AppComponent Application application (); MainActivityComponent getMainComponent (módulo MainActivityModule);
Por último, necesitamos un Solicitud
Clase para inicializar la inyección de dependencia..
la clase pública SampleApp extiende la aplicación public static SampleApp get (Context context) return (SampleApp) context.getApplicationContext (); @Override public void onCreate () super.onCreate (); initAppComponent (); appComponent appComponent privado; private void initAppComponent () appComponent = DaggerAppComponent.builder () .appModule (nuevo AppModule (this)) .build (); public AppComponent getAppComponent () return appComponent;
No olvides incluir el nombre de la clase en el manifiesto del proyecto..
Finalmente, podemos @Inyectar
nuestras clases de MVP. Los cambios que necesitamos hacer se hacen en el Actividad principal
clase. Cambiamos la forma en que se inicializan el modelo y el presentador. El primer paso es cambiar la MVP_Main.ProvidedPresenterOps
declaración de variable. Necesita ser público
y necesitamos añadir un @Inyectar
anotación.
@Inject public MVP_Main.ProvidedPresenterOps mPresenter;
Para configurar el MainActivityComponent
, agregue lo siguiente:
/ ** * Configurar @link com.tinmegali.tutsmvp_sample.di.component.MainActivityComponent * para crear una instancia e inyectar un @link MainPresenter * / private void setupComponent () Log.d (TAG, "setupComponent") ; SampleApp.get (this) .getAppComponent () .getMainComponent (new MainActivityModule (this)) .inject (this);
Todo lo que tenemos que hacer ahora es inicializar o reinicializar el Presentador, dependiendo de su estado en Mantenedor del estado
. Cambiar el setupMVP ()
Método y añadir lo siguiente:
/ ** * Configura el patrón de Model View Presenter. * Use un @link StateMaintainer para mantener las instancias de * Presenter y Model entre los cambios de configuración. * / private void setupMVP () if (mStateMaintainer.firstTimeIn ()) initialize (); else reinicializar (); / ** * Configurar la inyección @link MainPresenter y guardar enmStateMaintainer
* / private void initialize () Log.d (TAG, "initialize"); setupComponent (); mStateMaintainer.put (MainPresenter.class.getSimpleName (), mPresenter); / ** * Recuperar @link MainPresenter desdemStateMaintainer
o crea * un nuevo @link MainPresenter si la instancia se ha perdido demStateMaintainer
* / private void reinitialize () Log.d (TAG, "reinitialize"); mPresenter = mStateMaintainer.get (MainPresenter.class.getSimpleName ()); mPresenter.setView (esto); if (mPresenter == null) setupComponent ();
Los elementos MVP ahora se configuran de forma independiente desde la Vista. El código está más organizado gracias al uso de inyección de dependencia. Podría mejorar su código aún más usando la inyección de dependencia para inyectar otras clases, como DAO.
He enumerado una serie de problemas comunes que debe evitar al usar el patrón de Model View Presenter.
onDestroy ()
en el Presentador cada vez que se destruye la Vista. En algunos casos, puede ser necesario informar al presentador sobre una onStop
o un en pausa
evento.Llegó al final de esta serie en la que exploramos el patrón de presentador de vista de modelo. Ahora debería poder implementar el patrón MVP en sus propios proyectos, probarlo e incluso adoptar la inyección de dependencia. Espero que hayas disfrutado este viaje tanto como yo. espero verte pronto.