Test et injection de dépendance avec Model View Presenter sur Android

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:

  • configuration de l'environnement de test et écriture des tests unitaires pour les classes MVP
  • implémentation du modèle MVP à l'aide d'une injection de dépendance avec Dagger 2
  • nous discutons des problèmes courants à éviter lors de l'utilisation de MVP sur Android

1. Tests unitaires

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.

Étape 1: Configuration

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.

Étape 2: Test du modèle

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.

Étape 3: Test du présentateur

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)); 

2. Injection de dépendance avec poignard 2

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..

Étape 1: Configuration de la dague 2

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.

Étape 2: Implémentation de MVP avec Dagger 2

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..

Étape 3: Injection de classes MVP

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-la mStateMaintainer * / private void initialize () Log.d (TAG, "initialize"); setupComponent (); mStateMaintainer.put (MainPresenter.class.getSimpleName (), mPresenter);  / ** * Récupérer @link MainPresenter à partir de mStateMaintainer ou crée * un nouveau @link MainPresenter si l'instance a été perdue mStateMaintainer * / 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..

3. Éviter les problèmes courants

J'ai énuméré un certain nombre de problèmes courants à éviter lors de l'utilisation du modèle Model View Presenter..

  • Vérifiez toujours si la vue est disponible avant de l’appeler. La vue est liée au cycle de vie de l'application et peut être détruite au moment de votre demande..
  • N'oubliez pas de transmettre une nouvelle référence à partir de la vue lorsqu'elle est recréée..
  • Appel 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.
  • Pensez à utiliser plusieurs présentateurs lorsque vous travaillez avec des vues complexes..
  • Lorsque vous utilisez plusieurs présentateurs, le moyen le plus simple de transmettre des informations entre eux consiste à adopter un type de bus d'événements..
  • Pour maintenir votre couche de vue aussi passive que possible, envisagez d'utiliser l'injection de dépendance pour créer les couches de présentation et de modèle en dehors de la vue..

Conclusion

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.