iOS Unit Testing and UI Testing Tutorial

Update note: Michael Katz はこのチュートリアルを Xcode 10.1, Swift 4.2, および iOS 12 用に更新しました。 Audrey Tam がオリジナルを書きました。

テストを書くことは華やかではありませんが、テストはあなたのきらめくアプリがバグだらけのガラクタにならないようにするので、必要なことです。 このチュートリアルを読んでいる場合、コードと UI のテストを書くべきであることをすでに知っていますが、どのように書けばよいかわからないかもしれません。 おそらく、すでにテストが書かれていますが、それが正しいテストであるかどうか確信が持てません。 または、新しいアプリに取り組み始めていて、進めながらテストを行いたい場合。

このチュートリアルでは、次のことを説明します。

  • Xcode のテスト ナビゲーターを使ってアプリのモデルと非同期メソッドをテストする方法
  • スタブやモックを使ってライブラリやシステム オブジェクトとのやり取りを偽装する方法
  • UI とパフォーマンスのテスト方法
  • コード カバー ツールの使用方法

途中で、次のことがわかるようになります。 テスト忍者が使う単語をいくつかピックアップします。

Figuring Out What to Test

テストを書く前に、基本を知ることが重要です。 何をテストする必要があるのでしょうか。

目標が既存のアプリを拡張することである場合、まず、変更する予定のすべてのコンポーネントのテストを書くべきです。

一般に、テストは次の項目をカバーする必要があります。 モデル クラスとメソッド、およびコントローラとそれらの相互作用

  • 最も一般的な UI ワークフロー
  • 境界条件
  • バグ修正
  • Best Practices for Testing

    FIRST という略語は有効なユニット テストに関する一連の基準を簡潔に説明したものである。 これらの基準は次のとおりです:

    • 速い。
    • 独立/分離:テストはすばやく実行されなければなりません。
    • 反復可能であること。 テストを実行するたびに同じ結果を得る必要があります。 外部のデータ プロバイダーや同時実行の問題により、断続的に失敗することがあります。
    • 自己検証。 テストは完全に自動化されるべきです。 ログファイルのプログラマの解釈に頼るのではなく、出力は「合格」か「不合格」のどちらかでなければなりません。 理想的には、テストはテストする本番コードを書く前に書くべきです (テスト駆動開発)。

    FIRSTの原則に従うことにより、テストがアプリの障害になるのではなく、明確で有用なものになります。 2 つの別々のスターター プロジェクトがあります。 BullsEye と HalfTunes です。

    • BullsEye は、iOS Apprentice のサンプル アプリをベースにしています。 ゲーム ロジックは BullsEyeGame クラスにあり、このチュートリアルでテストします。
    • HalfTunes は、URLSession チュートリアルからのサンプル アプリの更新版です。 ユーザーは iTunes API に楽曲をクエリし、楽曲の断片をダウンロードして再生できます。

    Unit Testing in Xcode

    The Test navigator provides the easiest way to work with tests; you will use it to create test targets and run tests against your app.

    Creating a Unit Test Target

    BullsEyeプロジェクトを開き、command-6 を押して Test navigatorを開きます。

    左下にある + ボタンをクリックし、メニューから New Unit Test Target…

    デフォルト名、BlsEyeTestsを受け付けます。 テストバンドルがテストナビゲーターに表示されたら、クリックしてエディターでバンドルを開きます。 バンドルが自動的に表示されない場合は、他のナビゲータのいずれかをクリックしてトラブルシューティングを行い、テスト ナビゲータに戻ります。

    デフォルトのテンプレートは、テスト フレームワーク XCTest をインポートし、setUp()tearDown()、およびサンプル テスト メソッドを含む XCTestCase のサブクラスを定義しています。 これらはどちらもすべてのテスト クラスを実行します。

  • テスト ナビゲーターで矢印ボタンをクリックします。
  • ガッターで菱形のボタンをクリックします。
  • また、テスト ナビゲーターまたはガッターで、そのダイヤモンドをクリックして、個々のテスト メソッドを実行することもできます。 サンプル テストはまだ何もしていないので、非常に速く実行されます!

    すべてのテストが成功すると、ダイヤモンドが緑色に変わり、チェックマークが表示されます。 testPerformanceExample() の最後にある灰色の菱形をクリックすると、パフォーマンス結果を開くことができます:

    このチュートリアルでは testPerformanceExample()testExample() は必要ないので、削除します。

    Using XCTAssert to Test Models

    最初に、XCTAssert関数を使って BullsEye モデルのコア機能をテストしてみましょう。

    BullsEyeTests.swiftのimportステートメントのすぐ下に、次の行を追加します:

    @testable import BullsEye

    これにより、ユニットテストはBullsEyeの内部型と関数にアクセスできます。

    BullsEyeTestsクラスの先頭で、次のプロパティを追加します。

    var sut: BullsEyeGame!

    これはBullsEyeGameのプレースホルダーを作成し、テスト対象のシステム(SUT)、またはこのテストケースクラスがテスト対象としているオブジェクトを示します。

    次に、setup() の内容を次のように置き換えます。

    super.setUp()sut = BullsEyeGame()sut.startNewGame()

    これはクラス レベルで BullsEyeGame オブジェクトを作成するので、このテスト クラスのすべてのテストは SUT オブジェクトのプロパティとメソッドにアクセスできます。

    忘れないうちに、tearDown()でSUTオブジェクトを解放しておきましょう。 その内容を次のように置き換えます:

    sut = nilsuper.tearDown()
    注: すべてのテストが白紙の状態から始まるように、setUp() で SUT を作成し tearDown() でそれを解放することは良い習慣です。 詳細については、この件に関する Jon Reid の投稿を確認してください。

    最初のテストを書く

    さて、最初のテストを書く準備ができました!

    BullsEyeTests の最後に次のコードを追加します。

    func testScoreIsComputed() { // 1. given let guess = sut.targetValue + 5 // 2. when sut.check(guess: guess) // 3. then XCTAssertEqual(sut.scoreRound, 95, "Score computed from guess is wrong")}

    テスト メソッドの名前は常に test で始まり、何をテストするかの説明が続きます。 ここでは、必要なすべての値を設定します。 この例では、guess値を作成し、targetValueとの違いを指定します。

  • When。 このセクションでは、テストされるコードを実行します。 Call check(guess:).
  • Then: このセクションでは、テストが失敗した場合に表示されるメッセージとともに、期待する結果をアサートします。 この場合、sut.scoreRoundは95 (100 – 5)になるはずです。
  • ガターまたはテストナビゲータでダイヤモンドのアイコンをクリックして、テストを実行します。 これにより、アプリがビルドされて実行され、ダイヤモンドのアイコンが緑のチェックマークに変わります!

    注: XCTestAssertions の全リストを見るには、Apple の Assertions Listed by Category に行ってください。

    Debugging a Test

    意図的に BullsEyeGame に組み込まれたバグがあり、今それを見つける練習をしています。 そのバグを実際に見るために、与えられたセクションの targetValue から 5 を引き、他は同じにするテストを作成します。

    以下のテストを追加してください。

    func testScoreIsComputedWhenGuessLTTarget() { // 1. given let guess = sut.targetValue - 5 // 2. when sut.check(guess: guess) // 3. then XCTAssertEqual(sut.scoreRound, 95, "Score computed from guess is wrong")}

    guesstargetValueの差はまだ5なので、スコアはまだ95のはずです。

    ブレークポイントナビで、[テスト失敗]ブレークポイントを追加します。

    テストを実行し、テスト失敗の XCTAssertEqual 行で停止するはずです。

    デバッグコンソールでsutguessを調べます。

    guesstargetValue - 5 ですが scoreRound は95ではなく105です!

    さらに調べるには、通常のデバッグプロセスを使用します。 また、BullsEyeGame.swift の check(guess:) 内、difference を生成している箇所にもブレークポイントを設定します。 その後、再度テストを実行し、let differenceステートメントをステップオーバーして、アプリ内のdifferenceの値を検査します。

    問題は、differenceが負なので、100 – (-5) になることです。 これを解決するには、differenceの絶対値を使用する必要があります。 check(guess:) において、正しい行のコメントを解除し、間違った行を削除します。

    2つのブレークポイントを削除し、テストを再度実行して、成功したことを確認します。

    Using XCTestExpectation to Test Asynchronous Operations

    モデルのテスト方法とテストの失敗のデバッグ方法を学んだので、次は非同期のコードのテストに移ります。 これは URLSession を使用して、iTunes API に問い合わせ、曲のサンプルをダウンロードします。 ネットワーク操作のために AlamoFire を使用するように変更したいとします。 何かが壊れているかどうかを確認するために、ネットワーク操作のテストを書き、コードを変更する前と後にそれらを実行する必要があります。

    URLSession メソッドは非同期です:それらはすぐに返されますが、後で実行を終了しません。 非同期メソッドをテストするには、XCTestExpectation を使用して、テストが非同期操作が完了するのを待ちます。

    非同期テストは通常遅いので、より速いユニットテストからそれらを分離しておく必要があります。 HalfTunesSlowTests クラスを開き、既存の import ステートメントのすぐ下にある HalfTunes app モジュールをインポートします。

    @testable import HalfTunes

    このクラスのすべてのテストは、Apple のサーバーにリクエストを送信するのにデフォルトの URLSession を使用するので、sut オブジェクトを宣言し、setUp() でそれを作成し、tearDown() でそれをリリースしてください。

    var sut: URLSession!override func setUp() { super.setUp() sut = URLSession(configuration: .default)}override func tearDown() { sut = nil super.tearDown()}

    次に、この非同期テストを追加します。

    // Asynchronous test: success fast, failure slowfunc testValidCallToiTunesGetsHTTPStatusCode200() { // given let url = URL(string: "https://itunes.apple.com/search?media=music&entity=song&term=abba") // 1 let promise = expectation(description: "Status code: 200") // when let dataTask = sut.dataTask(with: url!) { data, response, error in // then if let error = error { XCTFail("Error: \(error.localizedDescription)") return } else if let statusCode = (response as? HTTPURLResponse)?.statusCode { if statusCode == 200 { // 2 promise.fulfill() } else { XCTFail("Status code: \(statusCode)") } } } dataTask.resume() // 3 wait(for: , timeout: 5)}

    このテストは、iTunesに有効なクエリを送信すると200のステータスコードが返ってくることを確認します。 コードのほとんどは、アプリで記述するものと同じですが、以下の行が追加されています:

    1. expectation(description:): promise に格納されている XCTestExpectation オブジェクトを返します。 description パラメータには、何が起こるかを期待する内容を記述します。
    2. promise.fulfill(): 非同期メソッドの完了ハンドラの成功条件クロージャでこれを呼び出すと、期待値が満たされたことを示すフラグが立てられます。
    3. wait(for:timeout:): すべての期待値が満たされるか、timeout間隔が終了するか、どちらか先に起こるまでテストを実行し続けます。

    テストを実行します。 インターネットに接続している場合、シミュレーターでアプリをロードしてから、テストが成功するまで約 1 秒かかるはずです。

    Failing Fast

    失敗は痛いですが、永遠にかかる必要はありません。

    失敗を体験するには、URL の “itunes” から ‘s’ を削除するだけです。 失敗しましたが、タイムアウトの間隔をフルに使っています! これは、リクエストが必ず成功すると想定し、そこでpromise.fulfill()を呼び出したからです。

    これを改善し、仮定を変更することによって、テストをより速く失敗させることができます。 要求が成功するのを待つのではなく、非同期メソッドの完了ハンドラが呼び出されるまでだけ待ちます。 これは、アプリがサーバーからレスポンス (OKかエラー) を受け取るとすぐに起こります。

    これがどのように機能するかを見るために、新しいテストを作成します。

    しかし、最初に、urlに加えた変更を元に戻して、前のテストを修正します。
    次に、次のテストをクラスに追加します。 リクエストが失敗した場合、then アサーションは失敗します。

    テストを実行します。 今度は失敗するまでに約 1 秒かかるはずです。

    url を修正し、テストを再度実行して、成功したことを確認します。

    Faking Objects and Interactions

    Asynchronous tests は、あなたのコードが非同期 API に正しい入力を生成することに確信を与えてくれます。 また、コードが URLSession から入力を受け取ったときに正しく動作すること、または、ユーザーのデフォルト データベースまたは iCloud コンテナを正しく更新することをテストしたいと思うかもしれません。

    ほとんどのアプリはシステムまたはライブラリ オブジェクト (ユーザーが制御できないオブジェクト) とやり取りし、これらのオブジェクトとやり取りするテストは遅く、再現性がないことがあり、FIRST 原則の 2 つに違反します。 代わりに、スタブから入力を取得するか、モック オブジェクトを更新することにより、相互作用を偽造できます。

    コードがシステムまたはライブラリ オブジェクトに依存している場合は、偽造を使用します。 これは、その部分を演じる偽のオブジェクトを作成し、この偽物をコードに注入することによって行うことができます。 Jon Reid による Dependency Injection では、これを行うためのいくつかの方法を説明しています。

    Fake Input From Stub

    このテストでは、searchResults.count が正しいことをチェックすることにより、アプリの updateSearchResults(_:) がセッションによってダウンロードされたデータを正しくパースすることを確認します。 SUT はビュー コントローラーで、スタブといくつかの事前にダウンロードされたデータでセッションをフェイクします。 それをHalfTunesFakeTestsと名付けます。 HalfTunesFakeTestsを開いてください。

    @testable import HalfTunes

    ここで、HalfTunesFakeTestsクラスの内容を次のように置き換えます:

    var sut: SearchViewController!override func setUp() { super.setUp() sut = UIStoryboard(name: "Main", bundle: nil) .instantiateInitialViewController() as? SearchViewController}override func tearDown() { sut = nil super.tearDown()}

    これは、SearchViewControllerであるSUTを宣言し、setUp()でそれを作成し、tearDown()でそれをリリースします。

    注:SUTはビューコントローラです。HalfTunesは大規模なビューコントローラの問題を抱えているため、すべての作業はSearchViewControllerで行われます。swift ネットワーク コードを別のモジュールに移動すると、この問題が軽減され、また、テストが容易になります。

    次に、偽のセッションがテストに提供するいくつかのサンプル JSON データが必要になります。 いくつかの項目で十分なので、iTunes でのダウンロード結果を制限するために、URL 文字列に &limit=3 を追加してください:

    https://itunes.apple.com/search?media=music&entity=song&term=abba&limit=3

    この URL をコピーして、ブラウザに貼り付けてください。 1.txtや1.txt.jsといった名前のファイルがダウンロードされます。 プレビューしてJSONファイルであることを確認し、abbaData.jsonにリネームします。

    ここで、Xcodeに戻って、プロジェクトナビゲータに移動します。 HalfTunesFakeTestsグループにファイルを追加します。

    HalfTunesプロジェクトは、サポートファイルであるDHURLSessionMock.swiftを含んでいます。 これは、URLまたはURLRequestを持つデータタスクを作成するメソッド(スタブ)と、DHURLSessionというシンプルなプロトコルを定義しています。 また、データ、レスポンス、エラーを選択したモック URLSession オブジェクトを作成できる初期化子を持つ、このプロトコルに準拠した URLSessionMock も定義されています。

    フェイクを設定するには、HalfTunesFakeTests.swift に移動して、setUp() 内、SUT を作成する文の後に以下を追加してください。

    let testBundle = Bundle(for: type(of: self))let path = testBundle.path(forResource: "abbaData", ofType: "json")let data = try? Data(contentsOf: URL(fileURLWithPath: path!), options: .alwaysMapped)let url = URL(string: "https://itunes.apple.com/search?media=music&entity=song&term=abba")let urlResponse = HTTPURLResponse( url: url!, statusCode: 200, httpVersion: nil, headerFields: nil)let sessionMock = URLSessionMock(data: data, response: urlResponse, error: nil)sut.defaultSession = sessionMock

    これにより、偽のデータとレスポンスが設定され、偽のセッションオブジェクトが作成されます。

    さて、updateSearchResults(_:) を呼び出すことで偽のデータが解析されるかどうかをチェックするテストを書く準備が整いました。

    func test_UpdateSearchResults_ParsesData() { // given let promise = expectation(description: "Status code: 200") // when XCTAssertEqual( sut.searchResults.count, 0, "searchResults should be empty before the data task runs") let url = URL(string: "https://itunes.apple.com/search?media=music&entity=song&term=abba") let dataTask = sut.defaultSession.dataTask(with: url!) { data, response, error in // if HTTP request is successful, call updateSearchResults(_:) // which parses the response data into Tracks if let error = error { print(error.localizedDescription) } else if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 { self.sut.updateSearchResults(data) } promise.fulfill() } dataTask.resume() wait(for: , timeout: 5) // then XCTAssertEqual(sut.searchResults.count, 3, "Didn't parse 3 items from fake response")}

    スタブが非同期メソッドのふりをしているので、やはり非同期テストとして書かなければなりません。

    whenアサーションは、データタスクが実行される前にsearchResultsが空であるというものです。

    偽のデータには 3 つの Track オブジェクトの JSON が含まれているので、then アサーションはビューコントローラーの searchResults 配列に 3 つのアイテムが含まれているということです。 実際のネットワーク接続がないので、かなり早く成功するはずです!

    モック オブジェクトへの偽の更新

    前のテストでは、偽のオブジェクトからの入力を提供するスタブを使用しました。 次に、モック オブジェクトを使用して、コードが UserDefaults を正しく更新することをテストします。

    BullsEyeプロジェクトを再度開きます。 このアプリには2つのゲームスタイルがあります。 ユーザーはスライダーを動かして目標値に一致させるか、スライダーの位置から目標値を推測します。

    次のテストでは、アプリが gameStyle プロパティを正しく保存することを確認します。

    テスト ナビゲーターで、新しいユニット テスト クラスをクリックし、それを BullsEyeMockTests と名付けます。 import ステートメントの下に以下を追加します。

    @testable import BullsEyeclass MockUserDefaults: UserDefaults { var gameStyleChanged = 0 override func set(_ value: Int, forKey defaultName: String) { if defaultName == "gameStyle" { gameStyleChanged += 1 } }}

    MockUserDefaults overrides set(_:forKey:) to increment the gameStyleChanged flag. 多くの場合、Bool 変数を設定する同様のテストを見かけますが、Int をインクリメントすることで、より柔軟なテストができます – たとえば、メソッドが 1 回だけ呼び出されることをチェックできます。

    BullsEyeMockTests で SUT とモック オブジェクトを宣言します。

    var sut: ViewController!var mockUserDefaults: MockUserDefaults!

    次に、デフォルトの setUp()tearDown() を次のように置き換えます。

    override func setUp() { super.setUp() sut = UIStoryboard(name: "Main", bundle: nil) .instantiateInitialViewController() as? ViewController mockUserDefaults = MockUserDefaults(suiteName: "testing") sut.defaults = mockUserDefaults}override func tearDown() { sut = nil mockUserDefaults = nil super.tearDown()}

    これは SUT とモック オブジェクトを作成し、SUT のプロパティとしてモック オブジェクトをインジェクトするものです。

    さて、テンプレート内の 2 つのデフォルト テスト メソッドを次のように置き換えます:

    func testGameStyleCanBeChanged() { // given let segmentedControl = UISegmentedControl() // when XCTAssertEqual( mockUserDefaults.gameStyleChanged, 0, "gameStyleChanged should be 0 before sendActions") segmentedControl.addTarget(sut, action: #selector(ViewController.chooseGameStyle(_:)), for: .valueChanged) segmentedControl.sendActions(for: .valueChanged) // then XCTAssertEqual( mockUserDefaults.gameStyleChanged, 1, "gameStyle user default wasn't changed")}

    アサーションは、テスト メソッドが分割された制御を変更する前に gameStyleChanged フラグが 0 であるというものです。

    Xcode における UI テスト

    UI テストでは、ユーザー インターフェイスとのインタラクションをテストできます。 UI テストは、クエリでアプリケーションの UI オブジェクトを見つけ、イベントを合成し、次にそれらのオブジェクトにイベントを送信することによって動作します。

    BullsEye プロジェクトの Test ナビゲーターで、新しい UI テスト ターゲットを追加します。

    BullsEyeUITests.swiftを開き、BullsEyeUITestsクラスの先頭にこのプロパティを追加します:

    var app: XCUIApplication!

    setUp()で、XCUIApplication().launch()を次の記述と入れ替えます:

    app = XCUIApplication()app.launch()

    testExample()の名前をtestGameStyleSwitch()と変更します。

    testGameStyleSwitch()で新しい行を開き、エディター ウィンドウの下部にある赤い[記録]ボタンをクリックします:

    これにより、シミュレーターでアプリが開き、テスト コマンドとして操作を記録するモードが適用されます。 アプリをロードしたら、ゲーム スタイル スイッチのスライド セグメントと上部ラベルをタップします。

    You now have the following three lines in testGameStyleSwitch():

    let app = XCUIApplication()app.buttons.tap()app.staticTexts.tap()

    The Recorder has created code to test the same actions you tested in the app.Xcode Record button to stop the recording.Xcode Record buttonをクリックして、記録を停止してください。 スライダーとラベルにタップを送信します。 これらをベースにして、独自の UI テストを作成します。
    その他の記述があれば、削除してください。

    1 行目は、setUp() で作成したプロパティと重複しているので、その行を削除してください。 まだ何もタップする必要はありませんので、2行目と3行目の末尾の.tap()も削除してください。 の横にある小さなメニューを開き、segmentedControls.buttons.

    を選択すると、次のようになります。

    app.segmentedControls.buttonsapp.staticTexts

    その他のオブジェクトをタップして、テストでアクセスできるコードをレコーダーに表示させます。

    // givenlet slideButton = app.segmentedControls.buttonslet typeButton = app.segmentedControls.buttonslet slideLabel = app.staticTextslet typeLabel = app.staticTexts

    セグメント化されたコントロールの 2 つのボタンの名前と、考えられる 2 つのトップ ラベルがわかったので、次のコードを下に追加します:

    // thenif slideButton.isSelected { XCTAssertTrue(slideLabel.exists) XCTAssertFalse(typeLabel.exists) typeButton.tap() XCTAssertTrue(typeLabel.exists) XCTAssertFalse(slideLabel.exists)} else if typeButton.isSelected { XCTAssertTrue(typeLabel.exists) XCTAssertFalse(slideLabel.exists) slideButton.tap() XCTAssertTrue(slideLabel.exists) XCTAssertFalse(typeLabel.exists)}

    これは、セグメント化コントロールのそれぞれのボタンに tap() したときに正しいラベルがあるかどうかを確認するものです。 テストを実行します – すべてのアサーションが成功するはずです。

    パフォーマンス テスト

    Apple のドキュメントから。 パフォーマンス テストでは、評価したいコードのブロックを取り、それを 10 回実行し、平均実行時間と実行の標準偏差を収集します。 これらの個々の測定値の平均は、テスト実行の値を形成し、成功または失敗を評価するためにベースラインと比較することができます。

    これを実際に見るには、HalfTunes プロジェクトを再び開き、HalfTunesFakeTests.Tests.Test にある、HalfTunesFakeTests.Test にある、measure()のクロージャに測定したいコードを配置するだけでよい。

    func test_StartDownload_Performance() { let track = Track( name: "Waterloo", artist: "ABBA", previewUrl: "http://a821.phobos.apple.com/us/r30/Music/d7/ba/ce/mzm.vsyjlsff.aac.p.m4a") measure { self.sut.startDownload(track) }}

    テストを実行し、統計情報を見るために、measure()末尾の閉鎖の始まりの隣に表示されているアイコンをクリックします。 その後、パフォーマンス テストを再度実行し、結果を表示します – ベースラインよりも良くなっているかもしれないし、悪くなっているかもしれません。 ベースラインはデバイス構成ごとに保存されるので、複数の異なるデバイスで同じテストを実行し、特定の構成のプロセッサ速度、メモリなどに依存する異なるベースラインをそれぞれが維持することが可能です。

    コード カバレッジ

    コード カバレッジ ツールは、テストによって実際に実行されるアプリ コードを教えてくれるので、アプリ コードのどの部分が(まだ)テストされていないかがわかります。

    コード カバレッジを有効にするには、スキームの Test アクションを編集し、[オプション] タブの [カバレッジを収集する] チェック ボックスをオンにします。

    SearchViewController.swift 内の関数とクロージャーのリストを表示するには、三角形をクリックします:

    updateSearchResults(_:) までスクロールすると、カバー率が 87.9% であることがわかります。 右側のサイドバーのカバレッジ注釈にマウスを合わせると、コードのセクションが緑または赤でハイライト表示されます。

    カバレッジ注釈は、テストが各コード セクションに何回当たったかを示し、呼ばれなかったセクションは赤でハイライト表示されます。

    この関数のカバレッジを上げるには、abbaData.json を複製し、異なるエラーを引き起こすように編集することができます。 たとえば、print("Results key not found in dictionary") をヒットするテストでは "results""result" に変更します。

    100% Coverage?

    どの程度コードカバレッジ 100% を目指すべきですか? 100% unit test coverage」でググると、「100% coverage」の定義そのものをめぐる議論とともに、さまざまな賛成論や反対論が見つかります。 反対論者は、最後の10-15%は努力に値しないと言います。 賛成派は、最後の10-15%が最も重要で、テストするのがとても難しいからだと言います。 テストできないコードはより深い設計上の問題の兆候であるという説得力のある議論を見つけるには、「hard to unit test bad design」でググってください。 この iOS ユニット テストと UI テストのチュートリアルが、あらゆるものをテストする自信を与えてくれたことを願っています!

    このチュートリアルの上部または下部にある「Download Materials」ボタンを使用して、プロジェクトの完成版をダウンロードすることができます。 あなた自身のテストを追加して、スキルを伸ばし続けてください!

    さらなる学習のためのリソースがいくつかあります:

    • テストのトピックに関するいくつかの WWDC ビデオがあります。 WWDC17 のもので、良いものが 2 つあります。 Engineering for Testability」と「Testing Tips & Tricks」です。
    • 次のステップは、自動化です。 継続的インテグレーションと継続的デリバリーです。 Apple の Automating the Test Process with Xcode Server と xcodebuild 、および ThoughtWorks の専門知識を活用した Wikipedia の継続的デリバリの記事から始めてください。
    • すでにアプリを持っているがまだそのためのテストを書いていない場合は、マイケル フェザーズによる Working Effectively with Legacy Code を参照するとよいでしょう、テストのないコードはレガシ コードですから!
    • ジョン リードの Quality Coding サンプル アプリ アーカイブはテスト駆動開発についてもっと学ぶには素晴らしいものです。

    raywenderlich.com Weekly

    the raywenderlich.com newsletter は、モバイル開発者として知っておくべきすべてのことについて最新情報を入手する最も簡単な方法です。

    当社のチュートリアルやコースのダイジェストを毎週受け取り、無料の詳細メール コースを特典として受け取ってください!

    Raywenderlich.com Weekly は、モバイル開発者であるあなたが知りたいことについての最新情報を入手できる最も簡単な方法を提供します。

    Average Rating

    4.7/5

    Add a rating for this content

    Sign in to add a rating

    90 rates

    コメントを残す

    メールアドレスが公開されることはありません。