The Missing Manual
for Swift Development
The Guide I Wish I Had When I Started Out
Join 20.Join Swift: How to Unit Test Private Methods in Swift
The Missing Manual
for Swift Development
The Guide I Wish I Had I Started Out
Join 20,Swift 開発について学ぶ 000 人以上の開発者たち
無料コピーをダウンロード
Swift プロジェクトのユニットテストは、Objective-C で書かれたプロジェクトのユニットテストとはかなり異なります。 Objective-C ランタイムの柔軟性に慣れている人にとっては、後ろ手に縛られているように感じるかもしれません。
アクセス制御
アクセス制御は多くの利点を持つ非常に歓迎すべき追加機能ですが、特にユニットテストに慣れていない場合は、ユニットテストを複雑にする場合があります。 内部として宣言されているエンティティにアクセスするために、テスト ターゲットで import ステートメントに testable
属性を適用できることはご存知でしょう。
import XCTest@testable import Notesclass NotesTests: XCTestCase { ...}
これは便利な追加機能ですが、テスト ターゲットのプライベートなエンティティにアクセスできるわけではありません。 ここで、今日の疑問が生まれました。 プライベート エンティティをどのようにユニット テストするのか。
間違った質問
この質問に対する短い答えは単純です。 他のモジュールからプライベートなエンティティにアクセスすることはできませんし、これはテスト ターゲットにも当てはまります。 これはテスト ターゲットにも当てはまります。 それはアクセス制御のためにあるものです。
しかし、それは質問に対する答えではありません。 プライベートなエンティティをどのようにユニットテストするかという質問であれば、それは間違った質問をしていることになります。 しかし、それはなぜでしょうか。
None of Your Business
なぜエンティティをプライベートと宣言するのでしょうか。 そうする動機は何でしょうか。 次の例を見てみましょう。
import Foundationstruct AccountViewViewModel { // MARK: - Properties let account: Account // MARK: - Public Interface var subscriptionAsString: String { switch account.subscription { case .monthly: return "Monthly Subscription" case .yearly: return "Yearly Subscription" case .trial: return "Trial Subscription" } } var expiresAtAsString: String { // Parse Date let date = parse(date: account.expiresAt) // Initialize Date Formatter let dateFormatter = DateFormatter() // Configure Date Formatter dateFormatter.dateFormat = "YYYY, MMMM" // Convert Date to String return dateFormatter.string(from: date) } // MARK: - Private Interface private func parse(date dateAsString: String) -> Date { let dateFormat: String if dateAsString.contains("/") { dateFormat = "YYYY'/'MM'/'dd" } else { dateFormat = "YYYYMMdd" } // Initialize Date Formatter let dateFormatter = DateFormatter() // Configure Date Formatter dateFormatter.dateFormat = dateFormat if let date = dateFormatter.date(from: dateAsString) { print(date) return date } else { fatalError("Incompatible Date Format") } }}
私は AccountViewViewModel
構造体をユニットテストしたいと思います。 ご覧のように、AccountViewViewModel
構造体は 2 つの内部計算プロパティを公開し、また、プライベート メソッドを定義しています。 expiresAtAsString
計算されるプロパティは、その作業の一部をプライベート parse(date:)
メソッドにオフロードします。
// MARK: - Tests for Subscription as Stringfunc testSubscriptionAsString_Monthly() { let account = Account(expiresAt: "20161225", subscription: .monthly) let accountViewViewModel = AccountViewViewModel(account: account) XCTAssertEqual(accountViewViewModel.subscriptionAsString, "Monthly Subscription")}func testSubscriptionAsString_Yearly() { let account = Account(expiresAt: "20161225", subscription: .yearly) let accountViewViewModel = AccountViewViewModel(account: account) XCTAssertEqual(accountViewViewModel.subscriptionAsString, "Yearly Subscription")}func testSubscriptionAsString_Trial() { let account = Account(expiresAt: "20161225", subscription: .trial) let accountViewViewModel = AccountViewViewModel(account: account) XCTAssertEqual(accountViewViewModel.subscriptionAsString, "Trial Subscription")}// MARK: - Tests for Expires at as Stringfunc testExpiresAtAsString_20161225() { let account = Account(expiresAt: "20161225", subscription: .trial) let accountViewViewModel = AccountViewViewModel(account: account) XCTAssertEqual(accountViewViewModel.expiresAtAsString, "2016, December")}
しかし、プライベート メソッドをどのようにテストすればよいのでしょうか。 テスト ターゲットからプライベート メソッドにアクセスすることはできません。 しかし、なぜプライベート メソッドをユニット テストする必要があるのでしょうか。 私たちはそれを理由があってプライベートとしてマークしました。 そうでしょう? そして、それが私たちが始めた疑問への答えになります。
Unit Testing the Public Interface
AccountViewViewModel
構造体のパブリック インターフェイスをユニット テストすることにより、構造体のプライベート インターフェイスを自動的に、または暗黙のうちにユニット テストすることになるのです。 パブリック インターフェイスが徹底的にテストされていることを確認するタスクがあります。 つまり、AccountViewViewModel
構造体のすべてのコードパスがユニットテストでカバーされていることを確認する必要があります。 言い換えれば、ユニットテストのスイートは、完全なコードカバレッジをもたらすべきです。
Xcode でコード カバレッジを有効にして、AccountViewViewModel
構造体のユニット テストを実行すると、いくつかのコード パスが実行されないことがわかります。 致命的なエラーのコード パスは無視できます。 私は致命的なエラーになるコード パスをユニット テストすることはありませんが、これはプロジェクトで致命的なエラーをどのように使用するかに大きく依存します。
1 つのユニット テストを追加することにより、AccountViewViewModel
構造体のコード カバー率を向上させることができます。
func testExpiresAtAsString_20161225WithForwardSlashes() { let account = Account(expiresAt: "2016/12/25", subscription: .trial) let accountViewViewModel = AccountViewViewModel(account: account) XCTAssertEqual(accountViewViewModel.expiresAtAsString, "2016, December")}
Implementation and Specification
AccountViewViewModel
struct の仕様をテストしていることを理解することは重要です。 その実装をテストしているわけではありません。 これは似ているように聞こえるかもしれませんが、実際には非常に異なっています。 私たちは AccountViewViewModel
構造体の機能をテストしているのです。
この記事のキーポイントは、プライベートなエンティティはユニット テストする必要がないことです。 ユニット テストはブラックボックス テスティングの一種です。 これは、AccountViewViewModel
構造体の実装をテストせず、その仕様をテストすることを意味します。
ただし、実装に興味がないということではありません。 ユニット テストのスイートが、テストしているエンティティのすべてのコード パスをカバーしていることを確認する必要があります。 コード カバレッジ レポートは、これを達成するために非常に重要です。
The Guide I Wish I Had When I Started Out
Join 20,000+ Developers Learning About Swift Development
Download Your Free Copy