NSUserDefault不应该是单元测试的清单吗?

Lis*_*saD 30 xcode unit-testing

我正在编写我的第一个iOS单元测试(Xcode 5,iOS 6),并发现单元测试的结果取决于我最近在模拟器中所做的事情.例如,我点击模拟器中联系人列表中的用户,现在我在UserDefaults中的"最近联系人"数据还有一个比以前更多的对象,即使我正在运行单元测试.

对于单元测试,使用随机用户默认数据并不干净(我习惯使用自己的干净数据库进行RoR测试).此外,我可能想测试特定的状态,比如拥有空的"最近的联系人"数据.

从这里查看相关问题,我似乎有些可能的答案,我不满意.

  • 模拟UserDefaults进行单元测试!我将不得不修改许多现有的类,以便我可以注入该模拟.
  • 在setUp方法中清除或自定义UserDefaults!但是,我在手动测试中费力地创建的数据将会消失.
  • 在setUp方法中清除或自定义UserDefaults 然后在tearDown中恢复这些值!哎哟.

对于在单元测试中应该是标准做法的东西,这些似乎不必要地复杂化.我不想在每个单元测试中重复自己.所以,我的问题是:

  • 我是否遗漏了从Ad-hoc模拟器测试到单元测试运行的UserDefaults持久化方式?
  • 是否有一种可配置的方法来解决这个问题,比如设置单元测试目标以便为UserDefaults设置不同的存储位置比使用模拟器手动测试时的某种方式?
  • 如果失败了,在代码中有一种优雅的方法吗?
  • 例如,我可以从XCTestCase继承MyAppTestCase对象并覆盖setUp和tearDown方法以始终保留然后恢复UserDefaults.这是一个好主意吗?

ork*_*den 44

使用像这个答案中的命名套件对我来说效果很好.删除用于测试的用户默认值也可以在中进行func tearDown().

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

    override func setUp() {
        super.setUp()
        UserDefaults().removePersistentDomain(forName: userDefaultsSuiteName)
        userDefaults = UserDefaults(suiteName: userDefaultsSuiteName)
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这是我见过的最接近干净的解决方案.太糟糕了我不再为iOS编程了! (2认同)

big*_*gkm 21

适用于iOS 7/10.9

您可以使用套件名称来加载测试,而不是使用standardUserDefaults

[[NSUserDefaults alloc] initWithSuiteName:@"SomeOtherTests"];
Run Code Online (Sandbox Code Playgroud)

这与一些代码相结合,从相应的目录中删除SomeOtherTests.plist文件setUp将归档所需的结果.

您必须设计任何对象来获取默认对象,以便测试不会产生任何副作用.

  • 这是我走过的方法,它的效果非常好.我有一个使用用户默认值的服务,它有一个`init`方法,允许用户默认实例被"注入".这使得对服务进行单元测试变得容易.**要从用户默认值**中清除设置,请使用方法`removePersistentDomainForName:`.这比使用plist文件在磁盘上进行编辑更容易. (2认同)

Rob*_*ier 15

正如@Till建议的那样,您的设计可能不正确,可测试性良好.NSUserDefaults它们不应该直接读取系统的单元可测试部分,而应该使用其他一些对象(可以与之交谈NSUserDefaults).这大致相当于"模拟NSUserDefaults",但实际上是一个额外的抽象层.您的配置对象将抽象两者NSUserDefaults和其他配置存储(如钥匙串).它还可以确保您不会在程序周围散布字符串常量.我为很多项目构建了这种配置对象,并强烈推荐它.

有些人认为单元可测试的对象不应该依赖于像NSUserDefaults我这样推荐的全局"配置"对象的单身人士.相反,所有配置都应该在init注入.在实践中,我发现在与Storyboard交互时会产生太多的麻烦,但是在有用的地方值得考虑.

如果你真的想深入挖掘NSUserDefaults,它确实提供了一些分层功能.您可以调查setVolatileDomain:forName:一下,看看是否可以为单元测试创​​建额外的图层.在实践中,我对iOS上的这些事情并没有太多的好运(更多的是在Mac上,但仍然没有达到你需要信任它的水平).

它可能是混合standardUserDefaults,但如果你能避免它,我不会推荐这种方法.如果您无法调整设计以避免外部性,那么"开始时保存所有内容并最终恢复所有内容"可能是解决问题的最佳标准化方法.


hfo*_*sli 5

我喜欢创建一个新的,所以没有冲突

import XCTest

extension UserDefaults {
    private static var index = 0
    static func createCleanForTest(label: StaticString = #file) -> UserDefaults {
        index += 1
        let suiteName = "UnitTest-UserDefaults-\(label)-\(index)"
        UserDefaults().removePersistentDomain(forName: suiteName)
        return UserDefaults(suiteName: suiteName)!
    }
}

class MyTest: XCTestCase {

    func testOne() {
        let userDefaults = UserDefaults.createCleanForTest()
        XCTAssertFalse(userDefaults.bool(forKey: "foo"))
        userDefaults.set(true, forKey: "foo")
        XCTAssertTrue(userDefaults.bool(forKey: "foo"))
    }

    func testTwo() {
        let userDefaults = UserDefaults.createCleanForTest()
        XCTAssertFalse(userDefaults.bool(forKey: "foo"))
        userDefaults.set(true, forKey: "foo")
        XCTAssertTrue(userDefaults.bool(forKey: "foo"))
    }
}
Run Code Online (Sandbox Code Playgroud)