15

我正在尝试使用 XCTest 类和方法在 Xcode 5 中对我的模型进行单元测试。

因为我的模型类继承了managedObject,所以我不能只实例化(alloc/init)它们并调用 getter 和 setter 或我需要测试的方法。我需要通过使用NSEntityDescription和使用来创建它们managedObjectContext

正是在这一点上,我遇到了麻烦。我不知道在哪里以及如何创建managedObjectContext用于单元测试的目的。

如果有人有一些建议或代码示例,那将非常有帮助。谢谢。

4

3 回答 3

12

我使用内存存储来进行单元测试并在其中创建所有实体。

这个类方法可以放在TestsHelper.m

+ (NSManagedObjectContext *)managedObjectContextForTests {
    static NSManagedObjectModel *model = nil;
    if (!model) {
        model = [NSManagedObjectModel mergedModelFromBundles:[NSBundle allBundles]];
    }

    NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
    NSPersistentStore *store = [psc addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:nil];
    NSAssert(store, @"Should have a store by now");

    NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    moc.persistentStoreCoordinator = psc;

    return moc;
}

这对我有用,因为我使用依赖注入来传递我的 moc 而不是使用单例。

于 2014-01-30T15:25:59.400 回答
12

我同意@Abizern:NSManagedObjectContext在你的代码中传递一个实例,而不是依赖你的应用程序委托、全局变量或自定义助手单例。

依赖注入

如果您知道某些控制器需要访问,NSManagedObjectContext请在其 init 方法中添加一个参数并保持对其的强引用:

@interface SomeController : NSObject
@property (nonatomic, strong, readwrite) NSManagedObjectContext *context;
- (instancetype)initWithContext:(NSManagedObjectContext *)context;
@end

这是进行“依赖注入”的最低要求。您不需要花哨的框架来为您进行注入。相反,在您的应用程序中,您分配NSManagedObjectContext可能使用 SQLite 存储的常用实例。在您的测试中,您创建一个单独NSManagedObjectContext的内存存储并将其传递给SomeController. 这甚至适用于使用 OCMock 的(部分)模拟。

objc.io #4 有一些很好的例子,尤其是 Chris Eidhof 的示例应用程序: http ://www.objc.io/issue-4/full-core-data-application.html

如何访问 MOC

查看 GitHub 上的objc.io 示例代码,您会看到一个PersistentStack帮助程序类,它负责为您的应用程序初始化托管对象上下文。Chris 使用抽象测试用例子类来提供测试上下文

总体指导方针是这样的:

  1. 不要在整个代码中依赖单例助手类,因为很难用测试上下文替换它的上下文。这是我发现的一个特性,这是由于 XCTest 被“注入”到正在运行的应用程序代码中。网上有一些工作示例,但它们并没有达到我对 Xcode 5.1 和 XCTest 的预期。
  2. 相反,NSManagedObjectContext在适当的时候准备一次。传递托管对象并使用托管对象来访问上下文。

弗洛里安·库格勒这样说:

托管对象应该在应用程序中传递,至少跨越模型控制器屏障,甚至可能跨越控制器视图屏障。后者虽然更具争议性,并且可以通过例如定义对象必须符合的协议以被特定视图消费,或者通过在视图类别中实现弥合差距的配置方法来以更好的方式进行抽象从模型对象到视图的细节。

无论如何,我们不应该将托管对象限制在模型层,并在我们想要传递它们时立即将它们的数据提取到不同的结构中。托管对象是 Core Data 应用程序中的一等公民,我们应该相应地使用它们。例如,托管对象应该在视图控制器之间传递,以便为它们提供所需的数据。

为了访问托管对象上下文,我们经常在视图控制器中看到这样的代码:

NSManagedObjectContext *context = 
  [(MyApplicationDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];

如果您已经将模型对象传递给视图控制器,最好通过此对象直接访问上下文:

NSManagedObjectContext *context = self.myObject.managedObjectContext;

这消除了对应用程序委托的隐藏依赖,使其更具可读性并且更易于测试。

这是我得到的最好的整体建议。现在我可以在需要的地方测试 Core Data 的使用。以前,通过全局单例类(自定义类或应用程序委托)访问上下文在生产中使用起来很方便,但在测试中证明很难验证。

于 2014-05-10T20:25:05.950 回答
2

迅速

NSManagedObjectContext为测试创建内存CoreData堆栈的扩展

extension NSManagedObjectContext {
    
    class func contextForTests() -> NSManagedObjectContext {
        // Get the model
        let model = NSManagedObjectModel.mergedModel(from: Bundle.allBundles)!
        
        // Create and configure the coordinator
        let coordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
        try! coordinator.addPersistentStore(ofType: NSInMemoryStoreType, configurationName: nil, at: nil, options: nil)
        
        // Setup the context
        let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
        context.persistentStoreCoordinator = coordinator
        return context
    }
    
}

然后在你的测试中使用它:

class YourTests: XCTestCase {
    
    private var context: NSManagedObjectContext?

    override func setUp() {
        self.context = NSManagedObjectContext.contextForTests()
    }

    // And use it in your tests
}
于 2018-01-31T14:31:57.513 回答