Am explorat conceptele modelului Model View Presenter în prima parte a acestei serii și am implementat versiunea proprie a modelului în a doua parte. Acum este momentul sa sapi putin mai adanc. În acest tutorial, ne concentrăm pe următoarele subiecte:
Unul dintre cele mai mari avantaje ale adoptării modelului MVP este acela că simplifică testarea unităților. Deci, să scriem teste pentru clasele Model și Presenter create și implementate în ultima parte a acestei serii. Vom face testele noastre folosind Robolectric, un cadru de testare unitar care oferă multe piese utile pentru clasele Android. Pentru a crea obiecte machete, vom folosi Mockito, ceea ce ne permite să verificăm dacă au fost solicitate anumite metode.
Editați build.gradle fișierul modulului dvs. de aplicație și adăugați următoarele dependențe.
dependente // ... testCompile 'junit: junit: 4.12' // Stabiliți această dependență dacă doriți să folosiți comanda Hamcrest testCompile 'org.hamcrest: hamcrest-library: 1.1' testCompile 'org.robolectric: robolectric: 3.0 "testCompile org .mockito: mockito-core: 1.10.19 '
În interiorul proiectului src pliant, creați următoarea structură de directoare testare / java / [nume-pachet] / [app-name]. Apoi, creați o configurație de depanare pentru a rula suita de testare. Clic Editați configurațiile ... în vârf.
Apasă pe + și selectați JUnit din listă.
A stabilit Director de lucru la $ MODULE_DIR $.
Vrem ca această configurație să ruleze toate testele unității. A stabilit Tip de test la Totul în pachet și introduceți numele pachetului în Pachet camp.
Să începem testele noastre cu clasa Model. Se efectuează testul unității folosind RobolectricGradleTestRunner.class
, care oferă resursele necesare pentru a testa operațiile specifice Android. Este important să adnotați @Cofing
cu următoarele opțiuni:
@RunWith (RobolectricGradleTestRunner.class) // modificați ceea ce este necesar pentru proiectul dvs. @Config (constants = BuildConfig.class, sdk = 21, manifest = "/src/main/AndroidManifest.xml") clasa publică MainModelTest // scrieți teste
Vrem să folosim un DAO real (obiect de acces la date) pentru a testa dacă datele sunt gestionate corect. Pentru a accesa a Context
, noi folosim RuntimeEnvironment.application
clasă.
private DAO mDAO; // Pentru a testa Modelul, puteți doar să // creați obiectul și să parcurgeți // un prezentator mock și o instanță DAO @ Înainte de a stabili void public () // Utilizarea RuntimeEnvironment.application va permite // noi să accesăm un Context și creați un DAO real // introduceți date care vor fi salvate temporar Context context = RuntimeEnvironment.application; mDAO = DAO nou (context); // Utilizarea unui prezentator falsificat va permite verificarea // dacă anumite metode au fost chemați în prezentatorul MainPresenter mockPresenter = Mockito.mock (MainPresenter.class); // Creăm o instanță model utilizând o construcție care include // a DAO. Acest constructor există pentru a facilita testarea mModel = new MainModel (mockPresenter, mDAO); // Abonarea mNotes este necesară pentru metodele de testare // care depind de arrayList mModel.mNotes = new ArrayList <> (); // Respingem prezentatorul nostru martor pentru a garanta că // verificarea metodei rămâne în concordanță între resetarea testelor (mockPresenter);
Acum este momentul să testați metodele modelului.
// Creare obiect Notă pentru a folosi în testele private Notă createNote (Text șir) Notă notă = Notă nouă (); note.setText (text); note.setDate ("o anumită dată"); notă de întoarcere; // Verificați loadData @Test public void loadData () int notesSize = 10; // introducerea datelor direct folosind DAO pentru (int i = 0; i-1); // Verificați deleteNote @Test public void deleteNote () // Trebuie să adăugăm o Notă în DB Notă notă = createNote ("testNote"); Notă insertedNote = mDAO.insertNote (notă); // adăugați aceeași notă în mNotes ArrayList mModel.mNotes = new ArrayList <> (); mModel.mNotes.add (insertedNote); // verificați dacă deleteNote returnează rezultatele corecte assertTrue (mModel.deleteNote (insertedNote, 0)); Notă fakeNote = createNote ("fakeNote"); assertFalse (mModel.deleteNote (fakeNote, 0));
Acum puteți rula testul Model și puteți verifica rezultatele. Simțiți-vă liber să testați alte aspecte ale clasei.
Să ne concentrăm acum pe testarea prezentatorului. De asemenea, avem nevoie de Robolectric pentru acest test pentru a face uz de mai multe clase Android, cum ar fi AsyncTask
. Configurația este foarte asemănătoare testului Model. Utilizăm miscări Vizualizare și Model pentru a verifica apelurile metodice și pentru a defini valorile returnate.
@RunWith (RobolectricGradleTestRunner.class) @Config (constants = BuildConfig.class, sdk = 21, manifest = "/src/main/AndroidManifest.xml") clasa publică MainPresenterTest private MainPresenter mPresenter; privat MainModel mockModel; privat MVP_Main.RequiredViewOps mockView; // Pentru a testa prezentatorul, puteți doar să / / creați obiectul și să treceți modelul și să vizualizați machete. @ Înainte de publicarea void public () // Crearea machetelor mockView = Mockito.mock (MVP_Main.RequiredViewOps.class); mockModel = Mockito.mock (MainModel.class, RETURNS_DEEP_STUBS); // Să treacă machetele într-o instanță a prezentatorului mPresenter = new MainPresenter (mockView); mPresenter.setModel (mockModel); // Definiți valoarea care trebuie returnată de modelul // atunci când încărcați date atunci când (mockModel.loadData ()), apoiReturn (true); reset (mockView);
Pentru a testa metodele prezentatorului, să începem cu clickNewNote ()
care este responsabilă de crearea unei noi note și de înregistrarea ei în baza de date utilizând o AsyncTask
.
@ Test public void testClickNewNote () // Trebuie să frământăm un EditText EditText mockEditText = Mockito.mock (EditText.class, RETURNS_DEEP_STUBS); // macheta ar trebui să returneze un String atunci când (mockEditText.getText (). toString ()), apoiReturn ("Test_true"); // de asemenea, definim o poziție falsă de returnat // prin metoda insertNote în Model int arrayPos = 10, când (mockModel.insertNote (oricare (Note.class))), apoiReturn (arrayPos); mPresenter.clickNewNote (mockEditText); verificați (mockModel) .insertNote (oricare (note.class);) verify (mockView) .notifyItemInserted (eq (arrayPos + 1)); verificați (mockView) .notifyItemRangeChanged (eq (arrayPos), anyInt ());
Am putea testa, de asemenea, un scenariu în care insertNote ()
metoda returnează o eroare.
@ Test public void testClickNewNoteError () EditText mockEditText = Mockito.mock (EditText.class, RETURNS_DEEP_STUBS); când (mockModel.insertNote (orice (Note.class))) thenReturn (-1).; când (mockEditText.getText () toString ().) thenReturn ( "Test_false"). când (mockModel.insertNote (orice (Note.class))) thenReturn (-1).; mPresenter.clickNewNote (mockEditText); verifică (mockView) .showToast (orice (Toast.class));
În cele din urmă, încercăm deleteNote ()
, luând în considerare atât un rezultat de succes, cât și un rezultat nereușit.
@ Test public void testDeleteNote () atunci (mockModel.deleteNote (orice (Note.class), anyInt ())), apoiReturn (true); int adapterPos = 0; int layoutPos = 1; mPresenter.deleteNote (noua notă (), adaptorPos, layoutPos); verifică (mockView) .showProgress (); verificați (mockModel) .deleteNote (orice (note.class), eq (adapterPos)); verifică (mockView) .hideProgress (); verifică (mockView) .notifyItemRemoved (eq (layoutPos)); verifică (mockView) .showToast (orice (Toast.class)); @ Test public void testDeleteNoteError () atunci (mockModel.deleteNote (orice (Note.class), anyInt ())), apoiReturn (false); int adapterPos = 0; int layoutPos = 1; mPresenter.deleteNote (noua notă (), adaptorPos, layoutPos); verifică (mockView) .showProgress (); verificați (mockModel) .deleteNote (orice (note.class), eq (adapterPos)); verifică (mockView) .hideProgress (); verifică (mockView) .showToast (orice (Toast.class));
Dependența de injecție este un instrument excelent pentru dezvoltatori. Dacă nu sunteți familiarizat cu injecția de dependență, atunci vă recomandăm cu insistență să citiți articolul Kerry despre subiect.
Injecția de dependență este un stil de configurare a obiectului în care câmpurile și colaboratorii unui obiect sunt setați de o entitate externă. Cu alte cuvinte, obiectele sunt configurate de o entitate externă. Injecția de dependență este o alternativă la configurarea obiectului. - Jakob Jenkov
În acest exemplu, injectarea dependenței permite ca modelul și prezentatorul să fie create în afara vederii, făcând straturile MVP mai puțin cuplate și sporind separarea preocupărilor.
Folosim Dagger 2, o minunată bibliotecă de la Google, pentru a ne ajuta cu injecția de dependență. În timp ce setarea este simplă, pumnalul 2 are o mulțime de opțiuni reci și este o bibliotecă relativ complexă.
Ne concentrăm numai asupra părților relevante ale bibliotecii pentru a implementa MVP și nu vom acoperi cu multă detaliere biblioteca. Dacă doriți să aflați mai multe despre Dagger, citiți tutorialul lui Kerry sau documentația furnizată de Google.
Începeți prin actualizarea proiectului build.gradle fișier adăugând o dependență.
dependențe // ... classpath 'com.neenbedankt.gradle.plugins: android-apt: 1.8'
Apoi, editați proiectul build.dagger fișier după cum se arată mai jos.
aplicați pluginul: dependențele com.neenbedankt.android-apt // comanda apt vine de la android-apt plugin apt 'com.google.dagger: dagger-compiler: 2.0.2' compile 'com.google.dagger: dagger : 2.0.2 "cu condiția" org.glassfish: javax.annotation: 10.0-b28 '// ...
Sincronizați proiectul și așteptați ca operația să se finalizeze.
Să începem prin crearea unui @Scope
pentru Activitate
clase. Creeaza o @adnotare
cu numele scopului.
@Scope public @interface ActivityScope
Apoi, lucrăm la a @Modul
pentru Activitate principala
. Dacă aveți mai multe activități, trebuie să furnizați o @Modul
pentru fiecare Activitate
.
@ Modul public class MainActivityModule activitate privată MainActivity; public MainActivityModule (Activitatea principalăActivitate) this.activity = activitate; @ Oferă @ActivityScope MainActivity providesMainActivity () activitate returnată; @ Oferă @ActivityScope MVP_Main.ProvidedPresenterOps providedPresenterOps () MainPresenter presenter = new MainPresenter (activitate); Modelul MainModel = noul MainModel (prezentator); prezentator.setModel (model); prezentator de întoarcere;
Avem de asemenea nevoie de a @Subcomponent
pentru a crea un pod cu aplicația noastră @Component
, pe care încă mai trebuie să o creăm.
@ActivityScope @Subcomponent (modules = MainActivityModule.class) interfață publică MainActivityComponent MainActivity inject (Activitate MainActivity);
Trebuie să creăm a @Modul
și a @Component
pentru cerere
.
@ Modul public clasa AppModule aplicație privată de aplicație; public AppModule (aplicație de aplicație) this.application = cerere; @ Oferă aplicația publică @SingletonApplication () returnează cererea;
@Singleton @Component (modules = AppModule.class) interfață publică AppComponent Application application (); MainActivityComponentul getMainComponent (modul MainActivityModule);
În cele din urmă, avem nevoie de un cerere
pentru inițierea injecției de dependență.
clasa publică SampleApp extinde aplicația public static SampleApp get (contextual context) return (SampleApp) context.getApplicationContext (); @Override publice void onCreate () super.onCreate (); initAppComponent (); AppComponent privat appComponent; void privat initAppComponent () appComponent = DaggerAppComponent.builder () .appModule (noul AppModule (this)) .build (); public AppComponent getAppComponent () retur appComponent;
Nu uitați să includeți numele clasei în manifestul proiectului.
În cele din urmă, putem @Injecta
clasele noastre MVP. Modificările pe care trebuie să le facem se fac în Activitate principala
clasă. Modificăm modul în care modelul și prezentatorul sunt inițializate. Primul pas este să schimbați MVP_Main.ProvidedPresenterOps
declarație variabilă. Trebuie să fie public
și trebuie să adăugăm o @Injecta
adnotare.
@ Injectați public MVP_Main.ProvidedPresenterOps mPresenter;
Pentru a configura MainActivityComponent
, adăugați următoarele:
/ ** * Configurați @link com.tinmegali.tutsmvp_sample.di.component.MainActivityComponent * pentru a instanțiza și injecta o @link MainPresenter * / private void setupComponent () Log.d (TAG, "setupComponent") ; SampleApp.get (acest) .getAppComponent () .getMainComponent (noul MainActivityModule (acest)) .inject (acest);
Tot ce trebuie să facem acum este să inițializăm sau să reinitializăm Prezentatorul, în funcție de starea sa StateMaintainer
. Schimba setupMVP ()
și adăugați următoarele:
/ ** * Setare model Vizualizați modelul prezentatorului. * Utilizați un @ StateMaintainer pentru a menține instanțele * Prezentator și Model între modificările configurației. * / private void setupMVP () if (mStateMaintainer.firstTimeIn ()) inițializa (); altceva reinitialize (); / ** * Configurați injecția @link MainPresenter și salvezimStateMaintainer
* / void privat initialize () Log.d (TAG, "initialize"); setupComponent (); mStateMaintainer.put (MainPresenter.class.getSimpleName (), mPresenter); / ** * Recuperați @link MainPresenter de lamStateMaintainer
sau creează * un nou @link MainPresenter dacă instanța a fost pierdutămStateMaintainer
* / void privat reinitialize () Log.d (TAG, "reinitializa"); mPresenter = mStateMaintainer.get (MainPresenter.class.getSimpleName ()); mPresenter.setView (aceasta); dacă (mPresenter == null) setupComponent ();
Elementele MVP sunt acum configurate independent din Vizualizare. Codul este mai organizat datorită utilizării injecției de dependență. Ați putea îmbunătăți codul chiar și mai mult utilizând injecția de dependență pentru a injecta alte clase, cum ar fi DAO.
Am enumerat o serie de probleme comune pe care ar trebui să le evitați atunci când utilizați modelul Model View View Presenter.
onDestroy ()
în Prezentator de fiecare dată când Vederea este distrusă. În unele cazuri, poate fi necesar să informați prezentatorul despre un eveniment onStop
sau an onPause
eveniment.Ați atins sfârșitul seriei în care am explorat modelul Model View Viewer. Acum ar trebui să puteți implementa modelul MVP în propriile proiecte, să-l testați și chiar să adoptați injecția de dependență. Sper că v-ați bucurat de această călătorie la fel de mult ca și mine. Sper sa te vad curand.