Help us understand the problem. What is going on with this article?

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を保持しているなら

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のデータが 取り出せてしまう

動作確認コード

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした