Wir haben die Konzepte des Model View Presenter-Musters im ersten Teil dieser Serie untersucht und im zweiten Teil unsere eigene Version des Musters implementiert. Es ist jetzt an der Zeit, etwas tiefer zu graben. In diesem Tutorial konzentrieren wir uns auf folgende Themen:
Einer der größten Vorteile der Verwendung des MVP-Musters besteht darin, dass das Testen von Einheiten vereinfacht wird. Schreiben wir also Tests für die Klassen Model und Presenter, die wir im letzten Teil dieser Serie erstellt und implementiert haben. Wir werden unsere Tests mit Robolectric durchführen, einem Framework für Gerätetests, das viele nützliche Stubs für Android-Klassen bietet. Um Scheinobjekte zu erstellen, verwenden wir Mockito, mit dem wir überprüfen können, ob bestimmte Methoden aufgerufen wurden.
Bearbeiten Sie die build.gradle Datei Ihres App-Moduls und fügen Sie die folgenden Abhängigkeiten hinzu.
Abhängigkeiten //… testCompile 'junit: junit: 4.12' // Legen Sie diese Abhängigkeit fest, wenn Sie Hamcrest verwenden möchten, die mit testCompile 'org.hamcrest: hamcrest-library: 1.1' testCompile "org.robolectric: robolectric: 3.0" testCompile "org .mockito: Mockito-Kern: 1.10.19 '
Innerhalb des Projekts src Mappe, Erstellen Sie die folgende Ordnerstruktur test / java / [Paketname] / [Anwendungsname]. Erstellen Sie anschließend eine Debug-Konfiguration, um die Testsuite auszuführen. Klicken Konfigurationen bearbeiten… oben.
Drücke den + Taste und wählen Sie JUnit von der Liste.
einstellen Arbeitsverzeichnis zu $ MODULE_DIR $.
Wir möchten, dass diese Konfiguration alle Komponententests ausführt. einstellen Testart zu Alles im Paket und geben Sie den Paketnamen in das Feld ein Paket Feld.
Beginnen wir unsere Tests mit der Model-Klasse. Der Gerätetest läuft mit RobolectricGradleTestRunner.class
, die Ressourcen zum Testen von Android-spezifischen Vorgängen bereitstellt. Es ist wichtig zu kommentieren @Cofing
mit folgenden Optionen:
@RunWith (RobolectricGradleTestRunner.class) // Ändern, was für Ihr Projekt erforderlich ist @Config (Konstanten = BuildConfig.class, sdk = 21, manifest = "/src/main/AndroidManifest.xml") öffentliche Klasse MainModelTest // schreiben Tests
Wir möchten ein echtes DAO (Data Access Object) verwenden, um zu testen, ob die Daten korrekt verarbeitet werden. Zugriff auf eine Kontext
, wir nehmen das RuntimeEnvironment.application
Klasse.
privates DAO-mDAO; // Um das Modell zu testen, können Sie einfach // das Objekt erstellen und // einen Presenter-Mock und eine DAO-Instanz übergeben @Before public void setup () // Durch die Verwendung von RuntimeEnvironment.application erhalten Sie Zugriff auf einen Kontext und eine echte DAO erstellen // Daten einfügen, die temporär gespeichert werden Context context = RuntimeEnvironment.application; mDAO = neues DAO (Kontext); // Mit einem Mock Presenter kann // überprüft werden, ob bestimmte Methoden in Presenter aufgerufen wurden MainPresenter mockPresenter = Mockito.mock (MainPresenter.class); // Wir erstellen eine Modellinstanz mithilfe einer Konstruktion, die // ein DAO enthält. Dieser Konstruktor dient zur Erleichterung von Tests. MModel = new MainModel (mockPresenter, mDAO); // Die Subskription von mNotes ist für Testmethoden erforderlich, // die von der arrayList abhängt mModel.mNotes = new ArrayList <> (); // Wir setzen unseren Mock Presenter zurück, um sicherzustellen, dass // die Überprüfung der Methode zwischen den Tests konstant bleibt (mockPresenter);
Es ist jetzt an der Zeit, die Methoden des Modells zu testen.
// Notizobjekt erstellen, das in privaten Tests des Tests verwendet wird Hinweis createNote (String text) Note note = new Note (); note.setText (Text); note.setDate ("some date"); Rückschein // Verify loadData @Test public void loadData () int notesSize = 10; // Einfügen von Daten direkt mit DAO for (int i = 0; i-1); // Überprüfe deleteNote @Test public void deleteNote () // Wir müssen eine Notiz in DB hinzufügen. Note note = createNote ("testNote"); Hinweis insertNote = mDAO.insertNote (note); // füge dieselbe Notiz in mNotes hinzu ArrayList mModel.mNotes = new ArrayList <> (); mModel.mNotes.add (insertNote); // Vergewissern Sie sich, dass deleteNote die korrekten Ergebnisse zurückgibt. assertTrue (mModel.deleteNote (insertNote, 0)); Hinweis fakeNote = createNote ("fakeNote"); assertFalse (mModel.deleteNote (fakeNote, 0));
Sie können jetzt den Modelltest ausführen und die Ergebnisse überprüfen. Fühlen Sie sich frei, um andere Aspekte der Klasse zu testen.
Konzentrieren wir uns nun darauf, den Presenter zu testen. Für diesen Test benötigen wir auch Robolectric, um verschiedene Android-Klassen wie z AsyncTask
. Die Konfiguration ist dem Model-Test sehr ähnlich. Wir verwenden View- und Model-Mocks, um Methodenaufrufe zu überprüfen und Rückgabewerte zu definieren.
@RunWith (RobolectricGradleTestRunner.class) @Config (Konstanten = BuildConfig.class, sdk = 21, manifest = "/src/main/AndroidManifest.xml") öffentliche Klasse MainPresenterTest private MainPresenter mPresenter; privates MainModel mockModel; private MVP_Main.RequiredViewOps-Ansicht; // Um den Presenter zu testen, können Sie einfach // das Objekt erstellen und die Modell- und Ansicht-Mocks übergeben. @Before public void setup () // Erstellen der Mocks mockView = Mockito.mock (MVP_Main.RequiredViewOps.class); mockModel = Mockito.mock (MainModel.class, RETURNS_DEEP_STUBS); // Die Mocks an eine Presenter-Instanz übergeben mPresenter = new MainPresenter (mockView); mPresenter.setModel (mockModel); // Definiere den Wert, der von Model zurückgegeben werden soll, // wenn Daten geladen werden, wenn (mockModel.loadData ()). ThenReturn (true); zurücksetzen (mockView);
Um die Methoden des Presenters zu testen, beginnen wir mit der clickNewNote ()
Vorgang, der für das Erstellen einer neuen Notiz verantwortlich ist, und registriert sie in der Datenbank mit einem AsyncTask
.
@Test public void testClickNewNote () // Wir müssen einen EditText verspotten EditText mockEditText = Mockito.mock (EditText.class, RETURNS_DEEP_STUBS); // Der Mock sollte einen String zurückgeben, wenn (mockEditText.getText (). toString ()). thenReturn ("Test_true"); // außerdem definieren wir eine falsche Position, die // von der insertNote-Methode in model int arrayPos = zurückgegeben wird 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))
Wir könnten auch ein Szenario testen, in dem die insertNote ()
Methode gibt einen Fehler zurück.
@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));
Zum Schluss testen wir deleteNote ()
Methode, sowohl ein erfolgreiches als auch ein erfolgloses Ergebnis.
@Test public void testDeleteNote () when (mockModel.deleteNote (any (Note.class), anyInt ())). ThenReturn (true); int adapterPos = 0; int layoutPos = 1; mPresenter.deleteNote (neue 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 (neue Note (), adapterPos, layoutPos); verify (mockView) .showProgress (); verify (mockModel) .deleteNote (any (Note.class), eq (adapterPos)); verify (mockView) .hideProgress (); verify (mockView) .showToast (any (Toast.class));
Abhängigkeitsinjektion ist ein hervorragendes Werkzeug für Entwickler. Wenn Sie nicht mit der Abhängigkeitsinjektion vertraut sind, empfehle ich Ihnen dringend, den Artikel von Kerry zu diesem Thema zu lesen.
Abhängigkeitsinjektion ist eine Art der Objektkonfiguration, bei der die Felder und Mitbearbeiter eines Objekts von einer externen Entität festgelegt werden. Objekte werden also von einer externen Entität konfiguriert. Die Abhängigkeitsinjektion ist eine Alternative zur Konfiguration des Objekts. - Jakob Jenkov
In diesem Beispiel ermöglicht die Abhängigkeitsinjektion, dass das Modell und der Presenter außerhalb der Ansicht erstellt werden, wodurch die MVP-Schichten locker gekoppelt werden und die Trennung der Probleme zunimmt.
Wir verwenden Dagger 2, eine großartige Bibliothek von Google, um uns bei der Abhängigkeitsabhängigkeit zu helfen. Während das Setup einfach ist, hat Dolch 2 viele coole Optionen und es ist eine relativ komplexe Bibliothek.
Wir konzentrieren uns nur auf die relevanten Teile der Bibliothek, um MVP zu implementieren, und behandeln die Bibliothek nicht ausführlich. Wenn Sie mehr über Dagger erfahren möchten, lesen Sie das Tutorial von Kerry oder die von Google bereitgestellte Dokumentation.
Beginnen Sie mit der Aktualisierung des Projekts build.gradle Datei durch Hinzufügen einer Abhängigkeit.
Abhängigkeiten //… classpath 'com.neenbedankt.gradle.plugins: android-apt: 1.8'
Bearbeiten Sie als Nächstes das Projekt build.dagger Datei wie unten gezeigt.
plugin anwenden: 'com.neenbedankt.android-apt' Abhängigkeiten // apt Der Befehl stammt vom android-apt plugin apt 'com.google.dagger: dagger-compiler: 2.0.2' compile 'com.google.dagger: dagger : 2.0.2 'bereitgestellt' org.glassfish: javax.annotation: 10.0-b28 '//…
Synchronisieren Sie das Projekt und warten Sie, bis der Vorgang abgeschlossen ist.
Beginnen wir mit dem Erstellen eines @Umfang
für die Aktivität
Klassen. Ein ... kreieren @Anmerkung
mit dem Namen des Bereichs.
@Scope public @interface ActivityScope
Als nächstes arbeiten wir an einem @Modul
für die Hauptaktivität
. Wenn Sie mehrere Aktivitäten haben, sollten Sie eine @Modul
für jeden Aktivität
.
@Module öffentliche Klasse MainActivityModule private MainActivity-Aktivität; public MainActivityModule (MainActivity-Aktivität) this.activity = activity; @Provides @ActivityScope MainActivity offersMainActivity () return activity; @Provides @ActivityScope MVP_Main.ProvidedPresenterOps providedPresenterOps () MainPresenter presenter = neuer MainPresenter (activity); MainModel-Modell = neues MainModel (Präsentator); presenter.setModel (Modell); Moderator zurückgeben;
Wir brauchen auch eine @Subcomponent
eine Brücke mit unserer Anwendung erstellen @Komponente
, was wir noch schaffen müssen.
@ActivityScope @Subcomponent (modules = MainActivityModule.class) öffentliche Schnittstelle MainActivityComponent MainActivity inject (MainActivity-Aktivität);
Wir müssen eine erstellen @Modul
und ein @Komponente
für die Anwendung
.
@Module öffentliche Klasse AppModule private Anwendung Anwendung; public AppModule (Anwendung) this.application = Anwendung; @Provides @Singleton public Application makesApplication () Anwendung zurückgeben;
@Singleton @Component (modules = AppModule.class) öffentliche Schnittstelle AppComponent Application application (); MainActivityComponent getMainComponent (Modul MainActivityModule);
Schließlich brauchen wir eine Anwendung
Klasse, um die Abhängigkeitseinspritzung zu initialisieren.
public class SampleApp erweitert Application public static SampleApp get (Kontext-Kontext) return (SampleApp) context.getApplicationContext (); @Override public void onCreate () super.onCreate (); initAppComponent (); private AppComponent appComponent; private void initAppComponent () appComponent = DaggerAppComponent.builder () .appModule (new AppModule (this)) .build (); public AppComponent getAppComponent () return appComponent;
Vergessen Sie nicht, den Klassennamen in das Manifest des Projekts aufzunehmen.
Endlich können wir @Injizieren
unsere MVP-Klassen. Die Änderungen, die wir vornehmen müssen, werden in der Datenbank vorgenommen Hauptaktivität
Klasse. Wir ändern die Art und Weise, wie Model und Presenter initialisiert werden. Der erste Schritt ist das Ändern der MVP_Main.ProvidedPresenterOps
variable Aussage. Es muss sein Öffentlichkeit
und wir müssen eine hinzufügen @Injizieren
Anmerkung.
@Inject public MVP_Main.ProvidedPresenterOps mPresenter;
Einrichten MainActivityComponent
, Folgendes hinzufügen:
/ ** * Richten Sie die @link com.tinmegali.tutsmvp_sample.di.component.MainActivityComponent * ein, um einen @link MainPresenter * / private void setupComponent () Log.d (TAG, "setupComponent") zu instanziieren und einzufügen. ; SampleApp.get (this) .getAppComponent () .getMainComponent (new MainActivityModule (this)) .inject (this);
Jetzt müssen wir nur noch den Presenter initialisieren oder neu initialisieren, abhängig von seinem Status StateMaintainer
. Ändere das setupMVP ()
Methode und fügen Sie Folgendes hinzu:
/ ** * Modellansicht-Präsentatormuster einrichten. * Verwenden Sie einen @link StateMaintainer, um die Instanzen * Presenter und Model zwischen Konfigurationsänderungen zu verwalten. * / private void setupMVP () if (mStateMaintainer.firstTimeIn ()) initialize (); else reinitialize (); / ** * Richten Sie die @link MainPresenter -Injektion ein und speichert siemStateMaintainer
* / private void initialize () Log.d (TAG, "initialize"); setupComponent (); mStateMaintainer.put (MainPresenter.class.getSimpleName (), mPresenter); / ** * Stellen Sie @link MainPresenter wieder hermStateMaintainer
oder erstellt * einen neuen @link MainPresenter, falls die Instanz verloren gegangen istmStateMaintainer
* / private void reinitialize () Log.d (TAG, "reinitialize"); mPresenter = mStateMaintainer.get (MainPresenter.class.getSimpleName ()); mPresenter.setView (this); if (mPresenter == null) setupComponent ();
Die MVP-Elemente werden jetzt unabhängig von der Ansicht konfiguriert. Der Code ist dank der Verwendung der Abhängigkeitsinjektion besser organisiert. Sie können Ihren Code noch weiter verbessern, indem Sie mithilfe der Abhängigkeitseinspritzung andere Klassen wie DAO einfügen.
Ich habe eine Reihe von Problemen aufgelistet, die Sie bei der Verwendung des Model View Presenter-Musters vermeiden sollten.
onDestroy ()
im Presenter jedes Mal, wenn die Ansicht zerstört wird. In einigen Fällen kann es erforderlich sein, den Presenter über eine onStop
oder ein onPause
Veranstaltung.Sie haben das Ende dieser Serie erreicht, in der wir das Model View Presenter-Muster untersucht haben. Sie sollten jetzt in der Lage sein, das MVP-Muster in Ihren eigenen Projekten zu implementieren, zu testen und sogar die Abhängigkeitsinjektion zu übernehmen. Ich hoffe, dass Sie diese Reise genauso genossen haben wie ich. ich hoffe wir sehen uns bald.