يعد نمط MVP في تطوير تطبيقات الهاتف المحمول طريقة بسيطة إلى حد ما لتفريغ ViewController وإخراج بعض المنطق في مقدم العرض. يبدأ المقدم في اكتساب منطق يسهل اختباره.
يجب ألا تكون هناك شاشة MelodyListViewController
تعرض قائمة من الألحان. لديه مقدم عرض MelodyListPresenter
الذي يخبر ViewController بما يجب إظهاره. سيأخذ المقدم بيانات من خدمة MelodyService
. MelodyService
هو مجمّع فوق قاعدة البيانات وعميل api يقوم بتنزيل الألحان. إذا كانت الشبكة متاحة ، فإن الخدمة تأخذ البيانات من واجهة برمجة التطبيقات ، وإلا من قاعدة البيانات. يتم تقديم أنواع أخطاء التحميل في تعداد ServiceRequestError
.
protocol MelodyListViewController: class { func showMelodies(melodies: [Melody]) func showLoadError(error: ServiceRequestError) } protocol MelodyListPresenter { var view: MelodyListViewController? { get } var melodyService: MelodyService { get } func fetchMelodies() -> Promise<Void> } extension MelodyListPresenter { func fetchMelodies() -> Promise<Void> { return melodyService.getMelodies().done { melodies in self.view?.showMelodies(melodies: melodies) }.catch { error in self.view?.showLoadError(error: error) } } } protocol MelodyService { func getMelodies() -> Promise<[Melody]> } public enum ServiceRequestError: Error { case unknownError case noNetwork case noData }
بعد بناء هيكل الشاشة هذا ، يمكنك إجراء الاختبار. أي اختبار استلام البيانات من قبل مقدم العرض. لدى مقدم MelodyService
تبعية MelodyService
، لذلك تحتاج إلى محاكاة هذا البروتوكول. دعنا نتفق على أن Melody
لديه طريقة mocks
ثابتة ترجع قائمة من الألحان العشوائية.
class MelodyServiceMock: MelodyService, ServiceRequestMock { var emulatedResult: ServiceRequestResult = .error(.unknownError) func getMelodies() -> Promise<[Melody]> { let melodies = Melody.mocks() return mock(result: emulatedResult, model: melodies) } } enum ServiceRequestResult { case success case error(ServiceRequestError) }
نحن أيضا الرطب ViewController.
class MelodyListViewControllerMock: MelodyListViewController { var shownMelodies: [Melody]? var shownError: ServiceRequestError? func showMelodies(melodies: [Melody]) { shownMelodies = melodies } func showLoadError(error: ServiceRequestError) { shownError = error } }
ServiceRequestMock
هو بروتوكول يحتوي على أسلوب func mock<T>(result: ServiceRequestResult, model: T) -> Promise<T>
أحادي الأسلوب func mock<T>(result: ServiceRequestResult, model: T) -> Promise<T>
، والذي يُرجع Promise. في هذا الوعد ، تتم حماية النغمات أو خطأ التمهيد - ما يتم إرساله كنتيجة محاكاة.
protocol ServiceRequestMock { func mock<T>(result: ServiceRequestResult, model: T) -> Promise<T> } extension ServiceRequestMock { func mock<T>(result: ServiceRequestResult, model: T) -> Promise<T> { return Promise { seal in switch result { case .success: return seal.fulfill(model) case .error(let requestError): return seal.reject(requestError) } } } }
وهكذا ، قدمنا كل ما هو ضروري لاختبار المقدم.
import XCTest import PromiseKit class MelodyListPresenterTests: XCTestCase { let view = MelodyListViewControllerMock() let melodyService = MelodyServiceMock() var presenter: MelodyListPresenterImp! override func setUp() { super.setUp() presenter = MelodyListPresenterImp( melodyService: melodyService, view: view) view.presenter = presenter } func test_getMelodies_success() { // given let melodiesMock = Melody.mocks() melodyService.emulatedResult = .success // when let fetchMelodies = presenter.fetchMelodies() // then fetchMelodies.done { melodies in XCTAssertNotNil(self.view.shownMelodies) XCTAssert(self.view.shownMelodies == melodiesMock) }.catch { _ in XCTFail("Failed melodies upload") } } func test_getMelodies_fail() { // given melodyService.emulatedResult = .error(.noNetwork) // when let fetchMelodies = presenter.fetchMelodies() // then fetchMelodies.done { melodies in XCTFail("Mistakenly uploaded melodies") }.catch { _ in XCTAssertNotNil(self.view.shownError) XCTAssert(self.view.shownError is ServiceRequestError) XCTAssert(self.view.shownError as! ServiceRequestError == .noNetwork) } } }
ونتيجة لذلك ، حصلنا على أداة مناسبة لكتابة الاختبارات.