背景
URLSessionで作ったApiClientクラスのfetch処理をAlamofireを使った実装にリプレイスすることになり、
今回はテストにも慣れてきたのでTDDで進めようと試みました。
第1段階
元の関数でAPIのレスポンスをパースしてItemを10個拾うテストを書きます。
これは「テストがうまく動作するかどうか」のテストのような段階です。
class ApiClientTests: XCTestCase {
func testFetchItems() throws {
var tenItems: [Item]?
// URLSessionを使った
// fetchItems(url: String, completion: @escaping ([Item]?) -> Void)
ApiClient.fetchItems(url: "http://...10個返ってくるAPI...") { items in
tenItems = items
}
sleep(4)
XCTAssertEqual(tenItems.count, 10)
}
}
// テスト成功♪
第2段階
Apiclient.fetchItems2という関数名でAlmofire版を実装する前にテスト側を書き直します。
class ApiClientTests: XCTestCase {
func testFetchItems() throws {
var tenItems: [Item]?
// Alamofireを使った
// fetchItems2(url: String, completion: @escaping ([Item]?) -> Void)
ApiClient.fetchItems2(url: "http://...10個返ってくるAPI...") { items in
tenItems = items
}
sleep(4)
XCTAssertEqual(tenItems.count, 10)
}
}
// まだfetchItems2は実装していないのでテストは失敗
第3段階ここで問題発生
Apiclient.fetchItems2という関数名でAlmofire版を実装したのにテストがうまくいきません。
class ApiClientTests: XCTestCase {
func testFetchItems() throws {...}
}
// tenItemsがnilでエラーになる。
テストではなく実際にアプリを動かすと問題なく動くのですが、テストだけ失敗してしまう。
調査すると非同期がうまく動作していないようで、testFetchItems()
の処理後にtenItems = items
の処理がされているようです。
非同期処理のテストにはXCTestExpectation
XCTestExpectationとはXCTestにおいて処理を一旦止めて非同期で走っている処理の完了のお知らせfulfill()
を待ってまた走り出すという動きができます。
これでテストもうまく動作するようになりました。
class ApiClientTests: XCTestCase {
func testFetchItems() throws {
var tenItems: [Item]?
// 待ち合わせ用
let expectation = expectation(description: "非同期待ち")
// Alamofireを使った
// fetchItems2(url: String, completion: @escaping ([Item]?) -> Void)
ApiClient.fetchItems2(url: "http://...10個返ってくるAPI...") { items in
tenItems = items
expectation.fulfill() // ここで完了のお知らせ
}
// ここで完了のお知らせを待ちます。
// timeout: 4 とすると4秒待ってお知らせがなければテスト失敗として処理してくれます。
waitForExpectations(timeout: 4)
XCTAssertEqual(tenItems.count, 10)
}
}
// 無事テスト成功♪
リプレイス自体は完了したが、一部の矛盾が解決しませんでした。
無事にリプレイスは完了して、非同期のテストについても問題なく実装できるようになったことにウキウキしていました。
しかし今度は第一段階のテストがなぜ成功していたのかが不明です。
これはまだ解決していないのですが、優先順位が低いので一旦後回しにします。
この記事書いた目的は**「忘れ去ってしまいそうなので備忘録」として残したのもあるのですが、「あわよくば通りすがりの優しいつよつよエンジニアさんが知見をこぼしてくれたりしないだろうか」**なんて期待もしてたりします。
参考にした記事や書籍
- iOSアプリ開発自動テストの教科書
- 【Swift】URLSessionまとめ
- [Swift の HTTP ライブラリで苦しまないための自作 API クライアント設計]
(https://qiita.com/Kuniwak/items/a972ff2ade643799d1fe) - [iOSで非同期処理のテスト: XCTestExpectationの設定値と使い方まとめ]
(https://qiita.com/ktanaka117/items/e452e5ca58545c303dc2)
@takehito-koshimizu
ありがとうございました🙏
今回の解決には至りませんでしたが、URLSessionのキャッシュ機能について知見が広がりました🤗
もし今後解決したら追記予定とします。