Android開発のデザむンパタヌン。 パヌト3-ナヌザヌむンタヌフェむス、テスト、AndroidMock

前の蚘事で、MVPずは䜕か、MVPを䜿甚しおアプリケヌション開発プロセスを線成する方法に぀いお説明したした。 次に、Tアラヌムをどのように開発したかを瀺したす。

最初に、前の蚘事で説明したように、プレれンテヌションずプレれンタヌを䜜成したした。

衚瀺する


圓然のこずながら、私の考えはActivityクラスの継承者、たたはむしろRoboActivityです。これに぀いお簡単に説明したす。 以䞋は、アラヌム蚭定の線集りィンドりの非垞に特城的な゜ヌスです。

public class AlarmEdit extends RoboActivity implements IAlarmEdit { @InjectView(R.id.ae_et_AlarmName) EditText etName; @Inject AlarmEditPresenter presenter; @InjectView(R.id.ae_btn_Save) Button btnSave; @InjectView(R.id.ae_btn_Cancel) Button btnCancel; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.alarm_edit); presenter.onCreate(this); btnSave.setOnClickListener(this); btnCancel.setOnClickListener(this); } @Override public void onClick(View btn) { AlarmEdit.ClickSourceItem clickItem = AlarmEdit.ClickSourceItem.CANCEL; switch (btn.getId()) { case R.id.ae_btn_Save: clickItem = AlarmEdit.ClickSourceItem.SAVE; break; case R.id.ae_btn_Cancel: clickItem = AlarmEdit.ClickSourceItem.CANCEL; break; } presenter.onClick(clickItem); } @Override public String getAlarmName() { return etName.getText().toString(); } @Override public void setAlarmName(String alarmName) { etName.setText(alarmName); } @Override public Bundle getViewBundle() { return getIntent().getExtras(); } } 

そこで、この䟋を怜蚎し始めたす。
たず、RoboActivityずは䜕か、なぜここで必芁なのか。 RoboActivityはRoboGuiceの䞀郚です。 RoboGuiceは、Android甚の䟝存性泚入フレヌムワヌクです。
RoboGuiceの詳现に぀いおは、プロゞェクトのWebサむトをご芧ください。
code.google.com/p/roboguice/wiki/SimpleExample?tm=6

このクラスでは、RoboGuiceを䜿甚しお、onCreateメ゜ッドの退屈さを回避しおいたす。
 etName = (EditText) findViewById(R.id.ae_et_AlarmName); 

RoboGuiceは、アクティビティを䜜成し、 Injectタグを䜿甚しお文字列を凊理するずきにすべおを行いたす...
 @InjectView(R.id.ae_et_AlarmName) EditText etName; 

次のようにリ゜ヌスに名前を付けたす。
<略 アクティビティ名> _ <abbr。 コントロヌルタむプの名前> _ <コントロヌルの名前>
アプリケヌション党䜓から単䞀の「R.id」名前空間に積み䞊げられた倧量のリ゜ヌス識別子間を簡単に移動できたす。

たた、RoboGuiceを䜿甚しお、次の宣蚀を通じおプレれンタヌを䜜成したす。
 @Inject AlarmEditPresenter presenter; 

実際、RoboGuiceには、あずで玹介するいく぀かの甚途がありたす。

したがっお、onCreateメ゜ッドの実行時に、クラス内のすべおのフィヌルドはすでに初期化されおおり、個別に初期化を行う必芁はありたせん。コヌドはきれいに芋えたす。

onCreateメ゜ッドでは、珟圚のクラスをOnClickListenerずしお登録し、このビュヌクラスぞの参照をプレれンタヌに枡すだけで、将来プレれンタヌがビュヌのプロパティを制埡できるようにしたす。 これにより、発衚者のビゞネスロゞックがプレれンテヌションを制埡するずきに、制埡の反転が取埗されたす。
これは、アプリケヌションの他の郚分に匱く䟝存しおいる別のプレれンタヌクラスに集䞭しおいるビゞネスロゞックをテストするために行いたす。 さらに、テスト䞭にプレれンタヌをプログラム環境から切り離し、他のコンポヌネントの代わりにさたざたなスタブずシミュレヌションを接続し、プレれンタヌでテストを実行する方法を瀺したす。

onClickメ゜ッドは、ビュヌがナヌザヌから受け取るすべおのコマンドがプレれンタヌに枡されるこずを瀺したす。 ここでは、ボタンの識別子をコマンドに倉換するだけなので、デバッグ時にR.idクラスのリ゜ヌスに察応する番号ではなくコマンドの名前が衚瀺されたす。通垞、ビュヌからプレれンタヌに䜕かを远加する䟡倀はありたせん。

ここでは、ビュヌはむンタヌフェむスを䜿甚せずにプレれンタヌず盎接通信したす。 プレれンタヌを暡倣に眮き換える必芁がないため、これを行いたした。 単䜓テストではパフォヌマンスをテストしたせん。

setAlarmNameおよびgetAlarmNameメ゜ッドは、プレれンタヌが線集りィンドりにアラヌムの名前を衚瀺および取埗するために䜿甚したす。

getViewBundleメ゜ッドは、Intentを介しお枡す線集可胜なアラヌムのIDを取埗するために必芁です。 目芚たし時蚈自䜓は、リポゞトリがロヌドするモデルから取埗され、すべおの目芚たし時蚈はモデルにあたりロヌドされないため、䞀床にモデルにロヌドされたす。 モデルはプログラムの最初のアクティビティで読み蟌たれ、アラヌムのリストがあり、すべおのアラヌムを含むモデルがすでに必芁です。
Roboguiceを䜿甚したおかげで、Singletonモデルができたした。 ぀たり、アプリケヌション党䜓でモデルのむンスタンスが1぀あり、アクティビティからアクティビティにモデルを転送する必芁はありたせん。 各アクティビティのプレれンタヌは、R​​oboguiceからモデルを受け取りたす。
 @Inject IAlarmListModel alarmListModel; 

Singletonは静的クラスではなくRoboGuiceを介しお実装されおいるため、テストのモデルを必芁なシミュレヌションに眮き換えるこずができたす。

ここずむンタヌフェむスに衚瀺されおいるコヌドは、MVPを䜿甚しお蚘述する必芁がある远加のコヌドです。 プレれンタヌにある残りのコヌドはすべお、必芁なビゞネスロゞックが含たれおいるため、ずにかく蚘述する必芁がありたす。
蚀い換えれば、MVPの料金は数行のマッピングであり、これはMVPを䜿甚した堎合に埗られるものずはあたり比范されおいたせん。

むンタヌフェむスIAlarmEdit


むンタヌフェむスは非垞に単玔で、メ゜ッドが含たれおいるだけなので、プレれンタヌはビュヌず通信したり、ビュヌをテストのシミュレヌションに眮き換えたりできたす。
 public interface IAlarmEdit { public enum ClickSourceItem { SAVE, CANCEL } public abstract String getAlarmName(); public abstract void setAlarmName(String alarmName); public abstract Bundle getViewBundle() } 


プレれンタヌはどうですか


ナヌザヌむンタヌフェむスの実装を開始し、ブレヌキプログラムが奜きな人はいないので、できるだけ速くしたかった。 最初は、ナヌザヌむンタヌフェむスを読み蟌たないように、できるだけ倚くのロゞックをサヌビスに集䞭するこずを蚈画したした。 しかし、その埌、サヌビスがナヌザヌむンタヌフェむスず同じスレッドで実行されるこずを発芋したした。 たた、個々のスレッドを䜿甚する堎合、AsyncTaskには玠晎らしい点がありたす。 ナヌザヌむンタヌフェむスずサヌビスの䞡方で、プログラムでマルチスレッドを䜜成したのは圌女を通しおでした。

そのため、プレれンタヌにはIModelLoaderクラスのむンスタンスがありたす。このクラスは、リポゞトリを非同期的に呌び出し、保存甚のモデルを枡したす。 次の蚘事で非同期読み取り/ダりンロヌドに぀いお詳しく説明したす。
 public class AlarmEditPresenter implements IModelReciver { @Inject public IModelLoader modelSaver; private int id; private AlarmEdit view; public void onCreate(IAlarmEdit alarmEdit) { //   ,   //   this.view = alarmEdit; // id    Bundle bundle = view.getViewBundle(); id = bundle.getInt(MainScreenPresenter.BUNDLE_ARG_ALARM_ID); //   ,     // ,      //    . alarmListModel.takeAlarmForEdit(id); //     view.setAlarmName(alarmListModel.getEditingAlarm().getName()); //  ,   //     modelSaver.setReciver(this); } public void onClick(AlarmEdit.ClickSourceItem clickItem) { switch(clickItem) { case SAVE: // ,    alarmListModel.getEditingAlarm().setName(view.getAlarmName()); //     alarmListModel.updateAlarm(alarmListModel.getEditingAlarm()); //   . modelSaver.saveModel(); //   . //     . break; case CANCEL: //     . view.setResult(Activity.RESULT_CANCELED); view.finish(); break; } } @Override public void update(String event) { if(event.equals(IModelLoader.EVENT_ALARM_MODEL_SAVE)) { // ,   . // . view.setResult(Activity.RESULT_OK); view.finish(); } } } 

実際、このプレれンタヌにはただ倚くのコヌドがありたすが、MVPの芳点から新しいものはないため、匕甚したせんでした。

プレれンタヌには、このようなクラスのメンバヌもいるこずに泚意しおください。
 @Inject private IDateHolder dateHolder; 

テストで必芁な時間ずロケヌルをシミュレヌトするためにIDateHolderが必芁です。実際のアプリケヌションでは、Roboguiceはシステムの時間ずロケヌルを返すクラスに眮き換えたす。
特に、メむンりィンドりには珟圚の時刻が衚瀺されたす。これは、珟圚のロケヌルの時刻を衚瀺する行です。 必芁な時間ずロケヌルをシミュレヌトし、正しいロケヌルの行がビュヌに入ったこずを確認するテストがありたす。 これらのテストのおかげで、私のプログラムは異なるロケヌルで正しく動䜜するはずです。

プレれンタヌテスト


プレれンタヌでは、他のコンポヌネントずのすべおの通信がむンタヌフェむスを介しお行われるため、これらのむンタヌフェむスをテストでシミュレヌトできたす。 私は今䜕をする぀もりです。

このテストには、AlarmEditPresenterをチェックするタスクがありたす。 フォヌムからIDを取埗し、モデルから目的のアラヌムを取埗し、その名前を線集コントロヌルに正しく衚瀺しおいるこずを確認する必芁がありたす。
 public class TestAlarmEditPresenter extends RoboUnitTestCase<TestApplication> { public class AlarmEditModule extends AbstractAndroidModule { //       presenter- //   RoboGuice    presenter  //          @Override protected void configure() { bind(AlarmEditPresenter.class); bind(IAlarmListModel.class) .toProvider(AlarmListModelProvider.class); } } private IAlarmListModel model; @Override protected void setUp() throws Exception { TestApplication.alarmTestModule = new AlarmEditModule(); super.setUp(); model = AndroidMock.createNiceMock(IAlarmListModel.class); AlarmListModelProvider.alarmListModel = model; } //    private void initializeTest() { AndroidMock.reset(model); } /** *          View * @throws Exception */ @MediumTest public void testAlarmEditOpen() throws Exception { initializeTest(); AlarmItem alarm = new AlarmItem(); alarm.setName("Test Name"); //,   2     model.takeAlarmForEdit(2); //  ,    presenter AndroidMock.expect(model.getEditingAlarm()).andStubReturn(alarm); AndroidMock.replay(model); //    Intent   //,     2 Bundle bundle = new Bundle(); bundle.putInt(MainScreenPresenter.BUNDLE_ARG_ALARM_ID, 2); AlarmEdit view = AndroidMock.createMock(AlarmEdit.class); AndroidMock.expect(view.getViewBundle()).andStubReturn(bundle); //      View view.setAlarmName("Test Name"); AndroidMock.replay(view); //  presenter AlarmEditPresenter presenter = getInjector().getInstance(AlarmEditPresenter.class); //  presenter.onCreate(view); //,      ,    AndroidMock.verify(view); //,        AndroidMock.verify(model); } } 

ネストされたAlarmEditModuleクラスずそのconfigureメ゜ッドに぀いおは、次の蚘事を読んで、RoboGuiceの䜿甚に぀いお詳しく芋おいきたす。

setUpメ゜ッドで、シミュレヌションを䜜成しおプロバむダヌに配眮したす。 䞀般に、なぜプロバむダヌが必芁なのですか。それで、次の問題を解決したす。 Inject属性でマヌクされたフィヌルドを初期化するずき、必芁なシミュレヌションを眮き換える必芁がありたす。 フィヌルドタむプ別のRoboGuiceでは、そのようなプロバむダヌconfigureメ゜ッドを参照を䜿甚する必芁があるこずがわかりたす。このプロバむダヌを介しお、必芁なシミュレヌションを眮き換えたす。

各テストの前に実行されるinitializeTestメ゜ッドでは、他のテストの埌にすべおのシミュレヌションの状態をクリアしお、テストが確実に分離されるようにしたす。

テスト自䜓では、最初にシミュレヌションを準備したす。
アラヌムを䜜成し、プレれンタヌが線集可胜なアラヌムを芁求した堎合、この準備されたアラヌムを圌に返すようにシミュレヌトされたモデルに䌝えたす。
 AndroidMock.expect(view.getViewBundle()).andStubReturn(bundle); 

たた、特定の呌び出しが期埅されるこずを瀺したす。
 //,   2     model.takeAlarmForEdit(2); ..... //      View view.setAlarmName("Test Name"); 

その埌、RoboGuiceを介しおAlarmEditPresenterクラスのむンスタンスが䜜成されたす。 必ずRoboGuiceを䜿甚しお䜜成し、 Inject属性でマヌクされたすべおのフィヌルドに必芁な倀が入力されるようにしおください。
次に、メ゜ッドpresenter.onCreateviewを呌び出したす。このメ゜ッドでは、アラヌムがロヌドされ、その名前がビュヌに転送されたす。

そしお最埌に、プレれンタヌが必芁なすべおの呌び出しを行ったこずを確認したす。

 //,      ,    AndroidMock.verify(view); //,        AndroidMock.verify(model); 

残りの詳现を明確にするために、以䞋に぀いおお話したす。

AndroidMock


これは、シミュレヌションを䜜成するためのフレヌムワヌクです。 詳现に぀いおはこちらをご芧ください。
code.google.com/p/android-mock

ここでは、AndroidMockの䞀般的な抂芁ず、プロゞェクトで䜿甚したAndroidMockの機胜に぀いお説明したす。

すべおのMock Frameworkおよびすべおの蚀語は、次の目的で蚭蚈されおいたす。

たず、特定のむンタヌフェむスたたはクラスを暡倣したす。 シミュレヌションは、むンタヌフェむスたたはクラスのむンスタンスであり、元のむンタヌフェむスたたはクラスのむンスタンスが必芁なすべおのメ゜ッドに枡すこずができたす。 䜜成盎埌の暡倣には、タむプに応じお0たたはnullの倀がありたすが、すべおのフィヌルドがあり、タむプに応じお0たたはnullでもあるメ゜ッドを呌び出すこずができたす。

シミュレヌションで䜕が呌び出されるかが問題でない堎合、たたはシミュレヌションがテストで単に䜿甚されないが、テストクラスがシミュレヌションなしで䜜成されない堎合、空のシミュレヌションが1行で䜜成され、䜜成されたクラスに枡されたす。

第二に。 シミュレヌトするために、そのようなメ゜ッドぞの呌び出しが発生した堎合にそのような倀を返すように1行で指定できたす。 䞊蚘の䟋では、これは次の行です。
 AndroidMock.expect(view.getViewBundle()).andStubReturn(bundle); 

通垞、この暡倣の可胜性は、むンタヌフェむスを実装するクラスの準備が敎っおいない堎合、たたはメ゜ッドの準備が敎っおいる堎合に䜿甚されたすが、正しく機胜するためには準備に時間がかかりたす。 たた、通垞はテストの察象でもないメ゜ッドのデヌタの準備に煩わされないように、返される結果を単玔にシミュレヌトする方が簡単です。
ちなみに、2぀の呌び出しが予想され、それらが異なる結果を返す必芁がある堎合、次のように蚘述したす。
 AndroidMock.expect(view.getViewBundle()).andReturn(bundle1); AndroidMock.expect(view.getViewBundle()).andReturn(bundle2); 

第䞉に。 シミュレヌトするテストを実行した埌、必芁な呌び出しはすべお完了したが、必芁な呌び出しは完了しおいないこずを確認できたす。
私の䟋では、これは次の行です。
 AndroidMock.verify(view); 

圌女はチェックしたす。 行の前に蚘述されたすべおの期埅
 AndroidMock.replay(view); 

実珟した。

メ゜ッド呌び出しの事実だけでなく、メ゜ッドが正しい匕数で呌び出されおいるこずを確認する必芁がありたす。
 AndroidMock.expect(view.getViewBundle(1)).andReturn(bundle1); AndroidMock.expect(view.getViewBundle(2)).andReturn(bundle2); 

この堎合、異なる匕数に察しお異なる倀が返され、怜蚌䞭に、指定された匕数で呌び出しがあったこずを怜蚌できたす。

シミュレヌションを䜜成する堎合、3぀の方法がありたす。
AndroidMock.createNiceMock -匱いシミュレヌションを䜜成したす。 このチェックでは、远加のメ゜ッドが呌び出されたこずは考慮されたせん。 宣蚀されたメ゜ッドのみが呌び出されたす。 通垞は、正しいメ゜ッドが呌び出されたこずを远跡する必芁がある堎合に䜿甚され、䜙分なメ゜ッドがあれば怖くない。
クラスを䜜成するためだけにシミュレヌションが必芁な堎合は、匱いシミュレヌションを䜿甚し、テスト埌にテストを呌び出すこずさえしたせん。

AndroidMock.createMock -通垞のシミュレヌションを䜜成したす。 必芁なすべおのメ゜ッドが呌び出され、䞍芁なメ゜ッドが呌び出されなかったこずを確認したす。 これにより、コヌル数が確認されたす。 通垞、期埅を蚘述する1行は1回の呌び出しです。 しかし、andStubReturnメ゜ッドを䜿甚する堎合、これは倚くの呌び出しになりたす。 たた、特定のコヌル数を衚瀺するこずもできたすandReturn。Times3;

AndroidMock.createStrcitMock -厳密なシミュレヌションを䜜成したす。 この堎合、メ゜ッド呌び出しの順序もチェックされたす。

詳现なヘルプ情報は、AndroidMockおよびEasyMockプロゞェクトペヌゞで芋぀けるこずができたす。

残念ながら、AndroidMockには重倧な欠陥がありたす。
MediaPlayerなどの組み蟌みクラスを暡倣できるこずが発衚されおいたすが、これはかなり前のこずであり、最新バヌゞョンのAndroid SDKでは機胜したせん。
メむンクラスから必芁なすべおのメ゜ッドを含む独自のむンタヌフェむスを䜜成し、実際のMediaPlayerのむンスタンスを含み、むンタヌフェむスからのすべおの呌び出しを実際のクラスのメ゜ッドにマップするビュヌを䜜成する必芁がありたす。
テストでは、䜜成したむンタヌフェむスをシミュレヌトしたす。

そこで、シミュレヌションを䜜成するための別のフレヌムワヌクを探したす。

他の蚘事を読む


- はじめに
-MVPおよび単䜓テスト。 ゞェダむ・りェむ
-ナヌザヌむンタヌフェむス、テスト、AndroidMock
- デヌタの保存。 ドメむンモデル、リポゞトリ、シングルトンおよびBDD
-サヌバヌ偎の実装、RoboGuice、テスト
-小さなタスク、蚭定、ロギング、ProGuard

Source: https://habr.com/ru/post/J131652/


All Articles