我正在编写我的第一个iOS单元测试(Xcode 5,iOS 6),并发现单元测试的结果取决于我最近在模拟器中所做的事情。例如。我点击模拟器中联系人列表中的用户,现在我在UserDefaults中的“最近联系人”数据还有一个比以前更多的对象,即使我正在运行单元测试。
对于单元测试,拥有随机用户默认数据并不干净(我习惯使用自己的干净数据库进行RoR测试)。此外,我可能想测试特定的状态,比如空的“最近的联系人”数据。
从这里查看相关问题,我似乎有些可能的答案,我不满意。
对于单元测试中应该是标准做法的东西,这些似乎不必要地复杂化。我不想在每个单元测试中重复自己。所以,我的问题是:
答案 0 :(得分:37)
使用命名套件like in this answer对我来说效果很好。删除用于测试的用户默认值也可以在func tearDown()
中完成。
class MyTest : XCTestCase {
var userDefaults: UserDefaults?
let userDefaultsSuiteName = "TestDefaults"
override func setUp() {
super.setUp()
UserDefaults().removePersistentDomain(forName: userDefaultsSuiteName)
userDefaults = UserDefaults(suiteName: userDefaultsSuiteName)
}
}
答案 1 :(得分:21)
可用iOS 7 / 10.9
您可以使用套件名称加载测试
,而不是使用standardUserDefaults[[NSUserDefaults alloc] initWithSuiteName:@"SomeOtherTests"];
这与从setUp
中的相应目录中删除SomeOtherTests.plist文件的一些代码相结合,将归档所需的结果。
您必须设计任何对象以获取默认对象,以便测试不会产生任何副作用。
答案 2 :(得分:15)
正如@Till建议的那样,您的设计可能不正确,可测试性良好。系统的单元可测试部分不是直接读取NSUserDefaults
,而是应该使用其他一些对象(可以与NSUserDefaults
对话)。这大致相当于“mocking NSUserDefaults
”,但实际上是一个额外的抽象层。您的配置对象将抽象NSUserDefaults
和其他配置存储(如钥匙串)。它还可以确保您不会在程序周围散布字符串常量。我为很多项目构建了这种配置对象,并强烈推荐它。
有些人认为单元可测试对象不应该依赖于NSUserDefaults
之类的单例或者我推荐的全局“配置”对象。相反,所有配置都应该在init注入。在实践中,我发现在与Storyboard交互时会产生太多的麻烦,但是在有用的地方值得考虑。
如果您真的想深入研究NSUserDefaults
,它确实提供了一些分层功能。您可以调查setVolatileDomain:forName:
以查看是否可以为单元测试创建额外的图层。在实践中,我对iOS上的这些东西并没有太多的好运(更多的是在Mac上,但仍然没有达到你需要信任它的水平)。
可以调动standardUserDefaults
,但如果可以避免,我不会推荐这种方法。如果您无法调整设计以避免外部性,那么“开始时保存所有内容并在结束时恢复所有内容”可能是解决问题的最佳标准化方法。
答案 3 :(得分:2)
您可以轻松保存&恢复主包标识符的持久域,这是# app/Resources/views/default/index.html.twig
{% extends 'base.html.twig' %}
{% block body %}
{% for review in reviews %}
{{ review.comment ~ '-' ~ review.book.name }}
{% endfor %}
{% endblock %}
写入的内容。例如,
[[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]
(至少对于已经运行到单元测试的任何代码)当然已经开始了。
答案 4 :(得分:2)
我喜欢创建一个新的,所以没有碰撞
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"))
}
}
答案 5 :(得分:0)
虽然我相信Rob Napier's answer是最合理的,但对于那些只需要快速修复的人来说,这是我的解决方法:
class MockUserDefaults: UserDefaults {
private var dict: [String: Any?] = [:]
override func set(_ value: Any?, forKey defaultName: String) {
dict[defaultName] = value
}
override func value(forKey key: String) -> Any? {
return dict[key] ?? nil
}
}
缺点:
String
键,除非您实现所有必需的类型优点: