Nous avons exploré les concepts du modèle Model View Presenter dans la première partie de cette série et nous avons implémenté notre propre version du modèle dans la seconde partie. Il est maintenant temps de creuser un peu plus profondément. Dans ce tutoriel, nous nous concentrons sur les sujets suivants:
L'un des principaux avantages de l'adoption du modèle MVP est qu'il simplifie les tests unitaires. Écrivons donc des tests pour les classes Model et Presenter que nous avons créées et implémentées dans la dernière partie de cette série. Nous allons exécuter nos tests en utilisant Robolectric, un framework de tests unitaires qui fournit de nombreux stubs utiles pour les classes Android. Pour créer des objets fantaisie, nous allons utiliser Mockito, ce qui nous permet de vérifier si certaines méthodes ont été appelées.
Modifier le build.gradle fichier de votre module d'application et ajouter les dépendances suivantes.
dependencies //… testCompile 'junit: junit: 4.12' // Définissez cette dépendance si vous souhaitez utiliser la correspondance de Hamcrest testCompile 'org.hamcrest: hamcrest-library: 1.1' testCompile "org.robolectric: robolectric: 3.0" testCompile "org .mockito: mockito-core: 1.10.19 '
À l'intérieur du projet src dossier, créer la structure de dossiers suivante test / java / [nom du paquet] / [nom de l'application]. Ensuite, créez une configuration de débogage pour exécuter la suite de tests. Cliquez sur Modifier les configurations… au sommet.
Clique le + bouton et sélectionnez JUnit de la liste.
Ensemble Directeur de travail à $ MODULE_DIR $.
Nous voulons que cette configuration exécute tous les tests unitaires. Ensemble Type de test à Tout en paquet et entrez le nom du paquet dans le champ Paquet champ.
Commençons nos tests avec la classe Model. Le test unitaire fonctionne en utilisant RobolectricGradleTestRunner.class
, qui fournit les ressources nécessaires pour tester des opérations spécifiques à Android. Il est important d'annoter @Cofing
avec les options suivantes:
@RunWith (RobolectricGradleTestRunner.class) // Modifie ce qui est nécessaire pour votre projet @Config (constantes = BuildConfig.class, sdk = 21, manifest = "/src/main/AndroidManifest.xml"), classe publique MainModelTest // écrire le tests
Nous voulons utiliser un véritable DAO (objet d'accès aux données) pour vérifier si les données sont traitées correctement. Pour accéder à un Le contexte
, nous utilisons le RuntimeEnvironment.application
classe.
DAO mDAO privé; // Pour tester le modèle, vous pouvez simplement // créer l'objet et passer // une maquette du présentateur et une instance DAO @Before public void setup () // L'utilisation de RuntimeEnvironment.application permettra // nous d'accéder à un contexte et créer un DAO réel // en insérant des données à enregistrer temporairement Contexte context = RuntimeEnvironment.application; mDAO = nouveau DAO (contexte); // Utiliser une maquette Le présentateur permettra de vérifier // si certaines méthodes ont été appelées dans le présentateur MainPresenter mockPresenter = Mockito.mock (MainPresenter.class); // Nous créons une instance de modèle à l'aide d'une construction qui inclut // un DAO. Ce constructeur existe pour faciliter les tests mModel = new MainModel (mockPresenter, mDAO); // La souscription de mNotes est nécessaire pour les méthodes de test // qui dépend de la liste de tableaux arrayMistM.melotes = new ArrayList <> (); // Nous réinitialisons notre simulateur de présentation pour garantir que // notre vérification de méthode reste cohérente entre les tests reset (mockPresenter);
Il est maintenant temps de tester les méthodes du modèle.
// Créer un objet Note à utiliser dans les tests privés Note Note createNote (String text) Note note = new Note (); note.setText (text); note.setDate ("une date"); note de retour; // Vérifie loadData @Test public void loadData () int notesSize = 10; // insertion directe de données à l'aide de DAO pour (int i = 0; i-1); // Vérifier deleteNote @Test public void deleteNote () // Nous devons ajouter une note dans la base de données Note note = createNote ("testNote"); Note InsertNote = mDAO.insertNote (note); // ajoute la même note dans mNotes ArrayList mModel.mNotes = new ArrayList <> (); mModel.mNotes.add (InsertNote); // vérifier si deleteNote renvoie les résultats corrects assertTrue (mModel.deleteNote (InsertNote, 0)); Note fakeNote = createNote ("fakeNote"); assertFalse (mModel.deleteNote (fakeNote, 0));
Vous pouvez maintenant exécuter le test de modèle et vérifier les résultats. N'hésitez pas à tester d'autres aspects de la classe.
Essayons maintenant de tester le présentateur. Nous avons également besoin de Robolectric pour que ce test utilise plusieurs classes Android, telles que AsyncTask
. La configuration est très similaire au test de modèle. Nous utilisons View et Model mock pour vérifier les appels de méthode et définir les valeurs de retour..
@RunWith (RobolectricGradleTestRunner.class) @Config (constantes = BuildConfig.class, sdk = 21, manifest = "/src/main/AndroidManifest.xml") classe publique MainPresenterTest private MainPresenter mPresenter; mockModel privé MainModel; MVP_Main.RequiredViewOps privé mockView; // Pour tester le présentateur, vous pouvez simplement // créer l'objet et passer le modèle et la vue mock @Before public void setup () // Création du mock mockView = Mockito.mock (MVP_Main.RequiredViewOps.class); mockModel = Mockito.mock (MainModel.class, RETURNS_DEEP_STUBS); // passe les simulacres à une instance de Presenter mPresenter = new MainPresenter (mockView); mPresenter.setModel (mockModel); // Définit la valeur à renvoyer par Model // lors du chargement de données when (mockModel.loadData ()). ThenReturn (true); réinitialiser (mockView);
Pour tester les méthodes du présentateur, commençons par le clickNewNote ()
responsable de créer une nouvelle note et de l’enregistrer dans la base de données à l’aide d’un AsyncTask
.
@Test public void testClickNewNote () // Nous devons nous moquer d'un EditText EditText mockEditText = Mockito.mock (EditText.class, RETURNS_DEEP_STUBS); // la maquette doit renvoyer une chaîne lorsque (mockEditText.getText (). toString ()). thenReturn (“Test_true"); // nous définissons également une fausse position // renvoyée par la méthode insertNote dans Model int arrayPos = 10; quand (mockModel.insertNote (any (Note.class))). ThenReturn (arrayPos); mPresenter.clickNewNote (mockEditText); verify (mockModel) .insertNote (any (Note.class)); vérifier (mockView). (eq (arrayPos + 1)); verify (mockView) .notifyItemRangeChanged (eq (arrayPos), anyInt ()); verify (mockView, jamais ()). showToast (any (Toast.class));
Nous pourrions également tester un scénario dans lequel insertNote ()
méthode retourne une erreur.
@Test public 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); verify (mockView) .showToast (any (Toast.class));
Enfin, nous testons deleteNote ()
méthode, en considérant à la fois un résultat positif et un résultat négatif.
@Test public void testDeleteNote () when (mockModel.deleteNote (any (Note.class), anyInt ())). ThenReturn (true); int adapterPos = 0; int layoutPos = 1; mPresenter.deleteNote (new Note (), adapterPos, layoutPos); verify (mockView) .showProgress (); verify (mockModel) .deleteNote (any (Note.class), eq (adapterPos)); verify (mockView) .hideProgress (); verify (mockView) .notifyItemRemoved (eq (layoutPos)); verify (mockView) .showToast (any (Toast.class)); @Test public void testDeleteNoteError () when (mockModel.deleteNote (any (Note.class), anyInt ())). ThenReturn (false); int adapterPos = 0; int layoutPos = 1; mPresenter.deleteNote (new Note (), adapterPos, layoutPos); verify (mockView) .showProgress (); verify (mockModel) .deleteNote (any (Note.class), eq (adapterPos)); verify (mockView) .hideProgress (); verify (mockView) .showToast (any (Toast.class));
L'injection de dépendance est un excellent outil à la disposition des développeurs. Si vous n'êtes pas familier avec l'injection de dépendance, je vous recommande fortement de lire l'article de Kerry sur le sujet..
L'injection de dépendance est un style de configuration d'objet dans lequel les champs et les collaborateurs d'un objet sont définis par une entité externe. En d'autres termes, les objets sont configurés par une entité externe. L'injection de dépendance est une alternative à la configuration de l'objet. - Jakob Jenkov
Dans cet exemple, l'injection de dépendance permet de créer le modèle et le présentateur en dehors de la vue, ce qui rend les couches MVP plus faiblement couplées et augmente la séparation des problèmes..
Nous utilisons Dagger 2, une bibliothèque géniale de Google, pour nous aider avec l'injection de dépendance. Bien que la configuration soit simple, Dagger 2 offre de nombreuses options intéressantes et constitue une bibliothèque relativement complexe..
Nous nous concentrons uniquement sur les parties pertinentes de la bibliothèque pour implémenter MVP et ne traiterons pas la bibliothèque de manière très détaillée. Si vous voulez en savoir plus sur Dagger, lisez le tutoriel de Kerry ou la documentation fournie par Google..
Commencez par mettre à jour le projet build.gradle fichier en ajoutant une dépendance.
dépendances // ... classpath 'com.neenbedankt.gradle.plugins: android-apt: 1.8'
Ensuite, éditez le projet build.dagger fichier comme indiqué ci-dessous.
apply plugin: 'com.neenbedankt.android-apt' dépendances // la commande apt provient du plugin android-apt apt 'com.google.dagger: dagger-compiler: 2.0.2' compiler 'com.google.dagger: dagger : 2.0.2 'a fourni' org.glassfish: javax.annotation: 10.0-b28 '//…
Synchronisez le projet et attendez que l'opération soit terminée.
Commençons par créer un @Portée
pour le Activité
Des classes. Créer un @annotation
avec le nom de la portée.
@Scope public @interface ActivityScope
Ensuite, nous travaillons sur un @Module
pour le Activité principale
. Si vous avez plusieurs activités, vous devez fournir un @Module
pour chaque Activité
.
@Module public class MainActivityModule private MainActivity activity; public MainActivityModule (activité MainActivity) this.activity = activity; @Provides @ActivityScope MainActivity, fourMainActivity () return activity; @Provides @ActivityScope MVP_Main.ProvidedPresenterOps providedPresenterOps () MainPresenter presenter = new MainPresenter (activité); Modèle MainModel = new MainModel (présentateur); presenter.setModel (modèle); retourner le présentateur;
Nous avons aussi besoin d'un @Subcomponent
créer un pont avec notre application @Composant
, que nous avons encore besoin de créer.
@ActivityScope @Subcomponent (modules = MainActivityModule.class) interface publique MainActivityComponent MainActivity inject (activité MainActivity);
Nous devons créer un @Module
et un @Composant
pour le Application
.
@Module public class AppModule application privée; public AppModule (Application application) this.application = application; @Provides @Singleton public Application includesApplication () return application;
@Singleton @Component (modules = AppModule.class) interface publique AppComponent Application application (); MainActivityComponent getMainComponent (module MainActivityModule);
Enfin, nous avons besoin d’un Application
classe pour initialiser l'injection de dépendance.
Classe publique SampleApp étend Application public statique SampleApp get (contexte de contexte) return (SampleApp) context.getApplicationContext (); @Override public void onCreate () super.onCreate (); initAppComponent (); private AppComponent appComponent; void privé initAppComponent () appComponent = DaggerAppComponent.builder () .appModule (new AppModule (this)) .build (); public AppComponent getAppComponent () return appComponent;
N'oubliez pas d'inclure le nom de la classe dans le manifeste du projet..
Enfin, nous pouvons @Injecter
nos classes de MVP. Les changements que nous devons faire se font dans le Activité principale
classe. Nous changeons la manière dont le modèle et le présentateur sont initialisés. La première étape consiste à changer le MVP_Main.ProvidedPresenterOps
déclaration de variable. Il doit être Publique
et nous devons ajouter un @Injecter
annotation.
@Inject public MVP_Main.ProvidedPresenterOps mPresenter;
Pour configurer le MainActivityComponent
, ajoutez ce qui suit:
/ ** * Configuration du @link com.tinmegali.tutsmvp_sample.di.component.MainActivityComponent * pour instancier et injecter un @link MainPresenter * / private void () Log.d (TAG, "setupComponent") ; SampleApp.get (this) .getAppComponent () .getMainComponent (new MainActivityModule (this)) .inject (this);
Il ne reste plus qu’à initialiser ou à réinitialiser le présentateur, en fonction de son état. StateMaintainer
. Changer la setupMVP ()
méthode et ajouter ce qui suit:
/ ** * Configuration du modèle Vue modèle présentateur. * Utilisez un @link StateMaintainer pour gérer les instances * Presenter et Model entre les modifications de configuration. * / private void setupMVP () if (mStateMaintainer.firstTimeIn ()) initialize (); else reinitialize (); / ** * Configurez l'injection @link MainPresenter et enregistrez-lamStateMaintainer
* / private void initialize () Log.d (TAG, "initialize"); setupComponent (); mStateMaintainer.put (MainPresenter.class.getSimpleName (), mPresenter); / ** * Récupérer @link MainPresenter à partir demStateMaintainer
ou crée * un nouveau @link MainPresenter si l'instance a été perduemStateMaintainer
* / private void reinitialize () Log.d (TAG, "reinitialize"); mPresenter = mStateMaintainer.get (MainPresenter.class.getSimpleName ()); mPresenter.setView (this); if (mPresenter == null) setupComponent ();
Les éléments MVP sont maintenant configurés indépendamment de la vue. Le code est plus organisé grâce à l'utilisation de l'injection de dépendance. Vous pourriez améliorer votre code encore plus en utilisant l'injection de dépendance pour injecter d'autres classes, telles que DAO..
J'ai énuméré un certain nombre de problèmes courants à éviter lors de l'utilisation du modèle Model View Presenter..
onDestroy ()
dans le présentateur à chaque fois que la vue est détruite. Dans certains cas, il peut être nécessaire d’informer le présentateur d’une modification onStop
ou un onPause
un événement.Vous avez atteint la fin de cette série dans laquelle nous avons exploré le modèle Présentateur de vue modèle. Vous devriez maintenant pouvoir implémenter le modèle MVP dans vos propres projets, le tester et même adopter l'injection de dépendance. J'espère que vous avez apprécié ce voyage autant que moi. J'espère te voir bientot.