UserDefaultsをテストする際にuserSuiteを利用する件

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でクリアするほうが良さそう

もしグローバルなUserDefaultsを保持しているなら

```swift
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ができ、もともとの<BundleID>.plistと2つになる
    - `standard`なデータ（.plist）にないが別のデータ（.plist）にある場合、それは`standard`で別のデータの値で **取り出せる**
    - 別のデータ(.plist）にないが`standard`なデータにある場合、それは`init(suiteName:)`で`standard`を **取り出せる**
- `init(suiteName:)`は複数作れる
    - 複数の.plistが作られる
        - A.plistになくB.plistにある場合、`init(suiteName:A)`でBのデータが **取り出せてしまう**

### 動作確認コード

```swift
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`で分かれてようが参照できても異常でない、みたいなことなんだろう。