私はTDDに精通しています。非同期のURLSession呼び出しをテストするにはどうすればよいですか?どのXCAを使用するのが良いか、どこで、どの段階ですか?Swift TDD&async URLSession - テスト方法は?
最初に、URLSessionを内部に持つ関数を作成し、この関数内でboolフラグをtrueに設定してXCAssertTrueでテストしました。あるいは、USLSessionコードを含む関数を呼び出した後、偽のデータを同期して返すことも考えました。
私はTDDに精通しています。非同期のURLSession呼び出しをテストするにはどうすればよいですか?どのXCAを使用するのが良いか、どこで、どの段階ですか?Swift TDD&async URLSession - テスト方法は?
最初に、URLSessionを内部に持つ関数を作成し、この関数内でboolフラグをtrueに設定してXCAssertTrueでテストしました。あるいは、USLSessionコードを含む関数を呼び出した後、偽のデータを同期して返すことも考えました。
GUIのテストと同じように、クライアントのアプリケーションコード内から直接バックエンドをテストすることはお勧めしません。問題は、主にネットワークからの一貫性のない状態です。たとえば、データベースが変更された場合など、安定したテストをどのように維持していますか?あなたのコードが変更されないが、あなたのテスト結果がそうでないなら、それは良くありません。
代わりに、偽のデータをテストするのが正しいでしょう。ネットワークサービスを何らかのインターフェースの背後に置くことをお勧めします。テスト戦略に応じて、何らかの形でテスト中にそのインターフェイスを実装するテストダブルを提供します。
アプリケーションのネットワークインタラクションを直接テストするのではなく、あらかじめ定義されたドメインモデルオブジェクト(ネットワークJSON/XMLから既に変換されているかのように)でドメインレイヤーをテストすると、良い。
私がこれを提案するもう一つの理由は、あなたの時間と正気を守ることです。予測可能で正確なテストを維持することは、特にアプリケーションコードをリファクタリングする場合には、非常に困難です。それに潜在的にシフトする外部性を追加することはリスクです。
@drhrからポイントを追加するには、ネットワークの詳細を抽象化するFacadeパターンを考慮してください。コンストラクタでは、ネットワーク機能をラップする特定の実装をパスします。つまり、ネットワーキングをスタブしているテストのコードで、Networkingを使用していたライブのコードです。
このユニットテストの目的は、コードがネットワークコードからのさまざまな応答にどのように反応するかをテストすることです。実際のネットワーク要素をここでテストすることを目的としていません(if必須)。
良い質問。私はそれが2つの部分に分解することができると思う、最初はXCTest
を使用して非同期コードをテストする方法について、2番目はあなたが提案する戦略についてです。
非同期コードの問題は、テストとは別のスレッドで実行されるため、非同期呼び出し後にテストが実行され、完了する前に終了することです。
XCTestフレームワークは、このユースケースのためだけに構築されたwaitForExpectationsWithTimeout:handler
関数を提供します。非同期呼び出しが完了するのを待ってから、その結果だけを確認することができます。
これは、あなたがそれを使用することができる方法である:
import XCTest
@testable import MyApp
class CallbackTest: XCTestCase {
func testAsyncCalback() {
let service = SomeService()
// 1. Define an expectation
let expectation = expectationWithDescription("SomeService does stuff and runs the callback closure")
// 2. Exercise the asynchronous code
service.doSomethingAsync { success in
XCTAssertTrue(success)
// Don't forget to fulfill the expectation in the async callback
expectation.fulfill()
}
// 3. Wait for the expectation to be fulfilled
waitForExpectationsWithTimeout(1) { error in
if let error = error {
XCTFail("waitForExpectationsWithTimeout errored: \(error)")
}
}
}
}
あなたはthis blog postにアプローチについての詳細を読むことができます。 全開示、私はそれを書いた。ご提案に関する今
、:
私が最初に考えたのは、その内部URLSessionを持つ関数を作成し、この機能セットブールフラグ内部の真にし、その後
XCAssertTrue
でそれをテストすることでした。
良いアイデア。これは、非同期コールバックが実際に呼び出されたかどうかをテストする優れた方法です。心に留めておくべき唯一の点は、テストが非同期呼び出しが実行されるのを待つ必要があることです。そうでないと、常に失敗します。あなたが書くことができる上記の技術を使用して
:
let service = SomeService()
var called = false
let expectation = expectationWithDescription(
"The callback passed to SomeService is actually called"
)
service.doSomethingAsync { _ in
called = true
expectation.fulfill()
}
waitForExpectationsWithTimeout(1) { error in
if let error = error {
XCTFail("waitForExpectationsWithTimeout errored: \(error)")
}
XCTAssertTrue(called)
}
それとも別の考えが
URLSession
コードが含まれている関数を呼び出した後、同期偽のデータを返すようにしました。
これはもう1つの素晴らしいアイデアです。 1つのことだけを行うコンポーネントでソフトウェアを分解し、それらをすべて孤立して完全にテストし、最後にすべてがうまくいって、最も一般的な障害パスを強調するいくつかの統合テストで動作することをテストします。
あなたはURLSession
と言及しているので、ネットワークに接触する単体テストコードに関する注意書きを残したいと思います。あなたの単体テストで実際のネットワークコールをするのは、一般的には悪い考えです。実際には、ネットワークが利用可能になり、期待される結果を返すという事実にテストを結びつけ、またそれらを遅くする。
ユニットレベルでは、私たちのテストを分離し、決定論的で高速にしたいと思っています(これはもっとhereです)。ネットワークにヒットすることは、これらの目標と一致しません。
ネットワークにぶつかるのを避けるための良い方法は、URLSession
をプロトコルにラップしてから、テストに準拠した偽物を使用するか、OHHTTPStubsのようなライブラリを使用してスタブすることです。 This postはさらに詳細になります。再び完全な開示をしました。私もこの手紙を書いています。
これが役に立ちます。ユニットテストの非同期コードは自明ではありません、あなたはそれに忍耐強くて何かが正しくないかどうか質問を続けてください:)
私はURLSession、実際のユニットテストとスピードアップの開発を偽装するためにモックを使用しますhttp://ocmock.org/は素晴らしいツールですそれをする。依然としてURLSessionをテストしたい場合は、XCTの期待値とスレッドを使用するコードを使用できます。他の回答とは異なり、このテストでは、テスト中の関数がURLSessionを内部で実行し、ブール値(myFlagと呼ばれる)を設定する完了ハンドラを使用すると想定しています。関数自体は非同期ではなく、URLSessionによって呼び出された関数の一部です。少し異なる期待を使用しなければなりません:
func testAFunc(){
let expect = expectation(description: "...")
DispatchQueue.global(qos: .userInitiated).async { // 1
let myObject = MyObject()
myObject.myFuncUnderTestWhichCallURLSession() // the function which calls URLSession
while(true){
if(myObject.myFlag == nil){
XCTAssertEqual(true, myObject.myFlag)
expect.fulfill()
break;
}
}
}
waitForExpectations(timeout: 5000) { error in
// ...
}
なぜスレッドですか?期待は、達成が呼び出されることを期待しています。この例では、非同期コードをdirecly呼び出すのではなく、内部でfulfillを呼び出すことはできません。 waitForExpectationsに到達できるように、スレッド内でフラグが変更されたとき(URLSession応答後)にwhileループを使用して呼び出します。