UserDefaultsをテストする際にinit(suiteName:)
でUserDefaultsをアプリとは別に作成し、それに対してデータを保存したり読み出したりしていたが、自分の意図している挙動と違うことがあったので動作確認コードを書いた。
そうやってからようやくリファレンスが理解できるようになった。
だけどそのリファレンスを解説してみても間違いがあるかもしれないので、自分の至った結論と、その結論に至った理由と動作確認のコードを示す。
最初に結論
- テストごと(Specごと)に別々の
init(suiteName:)
でUserDefaultsを作ったが、テストごとに複数のinit(suiteName:)
を分ける意味はないかもしれない- 理由
- 別々の
suiteName
で作っても相互にその値を 見ることができる -
standard
なUserDefaultsでもsuiteName
で保存した値を 見ることができる - Quickで非同期にテストが実行される場合、DIしてないUserDefaultsをbeforeSuiteで決めるのは難しい
- 別々の
- 理由
- テスト用のsuiteNameとして一つを作る意味は大きい
- 理由
-
suiteName
で作られたUserDefaultsはstandard
なものとは別なのでテスト終わったら削除すれば参照できなくなる
- 書き込みできているかのテストなら書き込む前にクリアしておけばいい
- QuickならbeforeSuiteでクリアすると非同期に
it
が動作しているので他のテストに影響を与えてしまう- 書き込む
it
があるならbeforeEachでクリアするほうが良さそう
- 書き込む
- QuickならbeforeSuiteでクリアすると非同期に
もしグローバルなUserDefaultsを保持しているなら
let myUserDefaults = MyUserDefaults.shared()
class ASpec: QuickSpec {
override func spec() {
beforeEach {
// 都度suiteNameで指定
myUserDefaults.ud = UserDefaults(suiteName: "A")
// removePersistentDomain(forName:)で都度クリアし他のテストの影響を受けないようにする
UserDefaults.removePersistentDomain(forName: "A")
}
afterEach {
// removePersistentDomain(forName:)でクリアしアプリに影響を与えないようにする
UserDefaults.removePersistentDomain(forName: "A")
}
it("...") {
}
it("...") {
}
}
}
理由
suiteNameで作った場合のテストとアプリという視点で見たデータ共有
suiteNameでテストごとにUserDefaultsを作る視点で挙動をまとめる
- テストターゲットで
init(suiteName:)
でUserDefaultsを作っても、それはアプリのstandard
なUserDefaultsから読み込める- ただし、アプリ側で同様のkeyを使いすでに保存してたらそれが優先される(nilならテストコードで入れた値が使われる)
よくわからないので視点を変える
suiteNameで分ける場合の細かな挙動
suiteNameで分ける場合の細かな挙動をまとめる
-
init(suiteName:)
で作成して書き込んだデータはstandard
なデータ(.plist)とは別のデータ(.plist)が作成される- 例
-
UserDefaults(suiteName: "A")
のsetメソッドでA.plistができ、もともとの.plistと2つになる
-
-
standard
なデータ(.plist)にないが別のデータ(.plist)にある場合、それはstandard
で別のデータの値で 取り出せる - 別のデータ(.plist)にないが
standard
なデータにある場合、それはinit(suiteName:)
でstandard
を 取り出せる
- 例
-
init(suiteName:)
は複数作れる- 複数の.plistが作られる
- A.plistになくB.plistにある場合、
init(suiteName:A)
でBのデータが 取り出せてしまう
- A.plistになくB.plistにある場合、
- 複数の.plistが作られる
動作確認コード
UserDefaults().removePersistentDomain(forName: "A")
UserDefaults().removePersistentDomain(forName: "B")
// udA
let udA = UserDefaults(suiteName: "A")!
udA.set(1, forKey: "foo")
udA.set(2, forKey: "bar")
udA.set(3, forKey: "baz")
print("udA.foo:", udA.integer(forKey: "foo")) // -> 1
print("udA.bar:", udA.integer(forKey: "bar")) // -> 2
print("udA.baz:", udA.integer(forKey: "baz")) // -> 3
print("udA.ud :", udA.integer(forKey: "ud")) // -> 0
// udB
let udB = UserDefaults(suiteName: "B")!
udB.set(10, forKey: "foo")
print("udB.foo:", udB.integer(forKey: "foo")) // -> 10
print("udB.bar:", udB.integer(forKey: "bar")) // -> 2
print("udB.baz:", udA.integer(forKey: "baz")) // -> 3
// standard
UserDefaults.standard.set(100, forKey: "foo")
UserDefaults.standard.set(200, forKey: "bar")
UserDefaults.standard.set(999, forKey: "ud")
print("stn.foo:", UserDefaults.standard.integer(forKey: "foo")) // -> 100
print("stn.bar:", UserDefaults.standard.integer(forKey: "bar")) // -> 200
print("stn.baz:", UserDefaults.standard.integer(forKey: "baz")) // -> 3
print("stn.ud:", UserDefaults.standard.integer(forKey: "ud")) // -> 999
// foo見たい
print("udA.foo:", udA.integer(forKey: "foo")) // -> 1
print("udB.foo:", udB.integer(forKey: "foo")) // -> 10
print("stn.foo:", UserDefaults.standard.integer(forKey: "foo")) // -> 100
// bar見たい
print("udA.bar:", udA.integer(forKey: "bar")) // -> 2
print("udB.bar:", udB.integer(forKey: "bar")) // -> 2
print("stn.bar:", UserDefaults.standard.integer(forKey: "bar")) // -> 200
// 削除したあとどうなるか
UserDefaults().removePersistentDomain(forName: "A")
print("udA.foo:", udA.integer(forKey: "foo")) // -> 0
print("udB.foo:", udB.integer(forKey: "foo")) // -> 10
print("stn.foo:", UserDefaults.standard.integer(forKey: "foo")) // -> 100
print("udA.bar:", udA.integer(forKey: "bar")) // -> 0
print("udB.bar:", udB.integer(forKey: "bar")) // -> 0
print("stn.bar:", UserDefaults.standard.integer(forKey: "bar")) // -> 200
plistの確認法
~/Library/Developer/CoreSimulator/Devices/<シミュレータのID>/data/Containers/Data/Application/<アプリを区別するID>/Library/Preferences/
おわりに
そもそもsuiteName
で.plistが分かれてというのは自分が作成した別アプリやウィジェットとのグループ間での共有のためだろうと思う。だから自分のアプリ単体であればsuiteName
で分かれてようが参照できても異常でない、みたいなことなんだろう。