مشكلة
عند الانتقال إلى عالم Swift من ObjC / C ++ ، واجهت مشكلة عند كتابة اختبارات الوحدة: نقص الأدوات لإنشاء كائنات وهمية.
عند كتابة شفرة متحللة ، غالبًا ما نخفي تفاصيل التنفيذ خلف الواجهات (البروتوكولات). كما أنه من الملائم جدًا التحقق من وظيفة كائن بشكل منفصل عن الآخرين ، واستبدال الأجزاء المكونة به بموكامي.
Googling ، وجدت العديد من إطارات عمل Swift Mocking على github. ولكن لم يظهر لي أي منها واضحًا وواضحًا في الاستخدام (لسبب واحد أو عدة أسباب):
- سحابة من التبعيات
- لا بناء جملة بديهي
- مطلوب إنشاء رمز / الكتابة اليدوية لكمية كبيرة من التعليمات البرمجية المساعدة
- غير متكامل مع XCTest (يسمح بالبلل ، ولكن لم يتم اختباره)
- لإلقاء العديد من الميزات في مجموعة من إطار واحد (الخلط بين ماذا ولماذا)
- يجب استخدامها مع عدد محدود من الأنواع المحددة مسبقًا / المدمجة
كان هذا الوضع مزعجًا بالنسبة لي ، ولمدة عام تقريبًا ، استخدمت الحلول البديلة والموك ذاتي الصنع.
كائنات وهمية مكتوبة ذاتيا بسيطة ، لكنها
- مختلفة في كل مرة (إعادة اختراع العجلة)
- أو نفس كل مرة (نسخ ولصق)
- غير واضح للاستخدام
القرار
C ++ gTest / gMock ( Google).
Mock- . , «» ( ) .
gMock, sMock (Swift Mock) gMock.
https://github.com/Alkenso/sMock
sMock:
- mock /
- expectations
- - mock-
- XCTest.framework
- zero-dependency
- pure Swift
()
sMock
- Mock , ( )
- Mock methodCall. "" .
Mocking synchronous method
:
import XCTest
import sMock
protocol HTTPClient {
func sendRequestSync(_ request: String) -> String
}
class MockHTTPClient: HTTPClient {
let sendRequestSyncCall = MockMethod<String, String>()
func sendRequestSync(_ request: String) -> String {
sendRequestSyncCall.call(request) ?? ""
}
}
struct Client {
let httpClient: HTTPClient
func retrieveRecordsSync() -> [String] {
let response = httpClient.sendRequestSync("{ action: 'retrieve_records' }")
return response.split(separator: ";").map(String.init)
}
}
class ExampleTests: XCTestCase {
func test_Example() {
let mock = MockHTTPClient()
let client = Client(httpClient: mock)
mock.sendRequestSyncCall
.expect("Request sent.")
.match("{ action: 'retrieve_records' }")
.willOnce(
.return("r1;r2;r3"))
let records = client.retrieveRecordsSync()
XCTAssertEqual(records, ["r1", "r2", "r3"])
}
}
Mocking synchronous method + mocking asynchonous callback
Mock callback'a, .
protocol HTTPClient {
func sendRequestSync(_ request: String) -> String
}
class MockHTTPClient: HTTPClient {
let sendRequestSyncCall = MockMethod<String, String>()
func sendRequestSync(_ request: String) -> String {
sendRequestSyncCall.call(request) ?? ""
}
}
struct Client {
let httpClient: HTTPClient
func retrieveRecordsAsync(completion: @escaping ([String]) -> Void) {
let response = httpClient.sendRequestSync("{ action: 'retrieve_records' }")
completion(response.split(separator: ";").map(String.init))
}
}
class ExampleTests: XCTestCase {
func test_Example() {
let mock = MockHTTPClient()
let client = Client(httpClient: mock)
mock.sendRequestSyncCall
.expect("Request sent.")
.match("{ action: 'retrieve_records' }")
.willOnce(
.return("r1;r2;r3"))
let completionCall = MockClosure<[String], Void>()
completionCall
.expect("Records retrieved.")
.match(["r1", "r2", "r3"])
.willOnce()
client.retrieveRecordsAsync(completion: completionCall.asClosure())
sMock.waitForExpectations()
}
}
Matchers, Actions, Argument capture
sMock (. ):
- Matchers — , .match: .any ( )
- Actions: expectation match'e .return, .
- : expectation .
- : , unexpectedCall
.
.
: , . .
, Swift .
, sMock . .
"testing with Swift"!
Try sMock on github (with SwiftPackageManager)