35

私は最初の iOS 単体テスト (Xcode 5、iOS 6) を書いていますが、最近シミュレーターで行ったことによって単体テストの結果が異なることがわかりました。たとえば、シミュレーターの連絡先リストでユーザーをクリックすると、単体テストを実行している場合でも、UserDefaults の「最近の連絡先」データに以前よりも 1 つ多くのオブジェクトが含まれるようになりました。

単体テストの場合、ランダムなユーザー デフォルト データを使用するのはクリーンではありません (私は独自のクリーン データベースを使用した RoR テストに慣れています)。その上、空の「最近の連絡先」データを持つなど、特定の状態をテストしたい場合があります。

ここで関連する質問を見ると、私が満足していない可能性のある答えがいくつかあるようです。

  • 単体テスト用に UserDefaults をモックしてください。そのモックを注入できるようにするには、多くの既存のクラスを変更する必要があります。
  • setUp メソッドで UserDefaults をクリアまたはカスタマイズしてください。しかし、手動テストで苦労して作成したデータはなくなってしまいます。
  • setUp メソッドで UserDefaults をクリアまたはカスタマイズしてから、tearDown でそれらの値を復元しますああ。

これらは、単体テストの標準的な方法であるはずのものに対して、不必要に複雑に思えます。すべての単体テストで自分自身を繰り返したくありません。だから、私の質問は次のとおりです。

  • アドホック シミュレーター テストから単体テストの実行まで、UserDefaults が永続化される方法について何か望ましいことがありませんか?
  • これを修正する構成可能な方法はありますか? たとえば、単体テストのターゲットを設定して、シミュレーターを使用して手動でテストする場合とは異なる UserDefaults の保存場所を設定する方法はありますか?
  • それができない場合、コードでこれを行うエレガントな方法はありますか?
  • たとえば、MyAppTestCase オブジェクトを XCTestCase から継承させ、setUp メソッドと tearDown メソッドをオーバーライドして、常に脇に置いてから UserDefaults を復元することができます。これは良い考えですか?
4

6 に答える 6

56

この回答のように名前付きスイートを使用すると、うまくいきました。テストに使用されたユーザーのデフォルトを削除することも、func tearDown().

class MyTest : XCTestCase {
    var userDefaults: UserDefaults?
    let userDefaultsSuiteName = "TestDefaults"

    override func setUp() {
        super.setUp()
        UserDefaults().removePersistentDomain(forName: userDefaultsSuiteName)
        userDefaults = UserDefaults(suiteName: userDefaultsSuiteName)
    }
}
于 2016-12-14T17:05:00.093 に答える
23

利用可能な iOS 7 / 10.9

standardUserDefaults を使用する代わりに、スイート名を使用してテストをロードできます

[[NSUserDefaults alloc] initWithSuiteName:@"SomeOtherTests"];

これを適切なディレクトリから SomeOtherTests.plist ファイルを削除するコードと組み合わせるとsetUp、目的の結果がアーカイブされます。

テストによる副作用がないように、デフォルト オブジェクトを使用するようにオブジェクトを設計する必要があります。

于 2014-06-26T11:24:19.110 に答える
19

@Tillが示唆するように、あなたの設計はおそらくテスト容易性のために正しくありません。システムの単体テスト可能な部分をNSUserDefaults直接読み取るのではなく、他のオブジェクト (と通信する可能性がある) と連携する必要がありますNSUserDefaults。これは「モッキングNSUserDefaults」とほぼ同じですが、実際には追加の抽象化レイヤーです。構成オブジェクトはNSUserDefaults、キーチェーンのような構成ストレージと他の構成ストレージの両方を抽象化します。また、プログラムの周りに文字列定数が散在しないようにすることもできます。私は多くのプロジェクトでこの種の構成オブジェクトを作成しており、強くお勧めします。

単体テスト可能なオブジェクトは、シングルトンNSUserDefaultsや私の推奨するグローバルな「構成」オブジェクトにまったく依存すべきではないと主張する人もいます。代わりに、すべての構成を init で注入する必要があります。実際には、ストーリーボードを操作するときにこれがあまりにも頭痛の種になることがわかりましたが、便利な場所では検討する価値があります。

を本当に深く掘り下げたい場合NSUserDefaultsは、レイヤー化機能が提供されます。setVolatileDomain:forName:単体テスト用に追加のレイヤーを作成できるかどうかを調べることができます。実際には、私は iOS でこの種のことをあまりうまく行っていません (Mac ではもっとそうですが、それでも信頼できるレベルではありません)。

スウィズルすることは可能standardUserDefaultsですが、回避できるのであれば、このアプローチはお勧めしません。「最初にすべてを保存し、最後にすべてを復元する」は、外部性を回避するために設計を適応させることができない場合、おそらく問題に取り組むための最も標準化された方法です。

于 2013-09-30T00:04:40.730 に答える
4

[[NSUserDefaults standardUserDefaults] setObject:forKey:]書き込み先であるメイン バンドルの識別子の永続ドメインを簡単に保存および復元できます。例えば、

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *originalValues = [defaults persistentDomainForName:[[NSBundle mainBundle] bundleIdentifier]];

// do stuff, possibly [defaults removePersistentDomainForName:[[NSBundle mainBundle] bundleIdentifier]]
// or using setPersistentDomain: to substitute a dictionary of mock values and test against that

[defaults setPersistentDomain:originalValues forName:[[NSBundle mainBundle] bundleIdentifier]];

[[NSUserDefaults standardUserDefaults] volatileDomainForName:NSRegistrationDomain]すべての呼び出しを使用して登録したものの単一の結合された辞書にアクセスしたい場合にも使用でき-registerDefaults:ます(少なくとも、ユニットテストが開始された場所まで実行されたコードについてはもちろん)。

于 2016-12-29T22:20:50.310 に答える