XCTest 测试方法中的 KVO addObserver 逻辑崩溃

Posted

技术标签:

【中文标题】XCTest 测试方法中的 KVO addObserver 逻辑崩溃【英文标题】:KVO addObserver logic crashes in XCTest test method 【发布时间】:2013-12-31 00:08:45 【问题描述】:

我在结合 XCTest 使用键值观察逻辑时遇到了一些困难(原始代码正在通过测试覆盖率进行改造)。该逻辑在正常(非测试)上下文中运行良好,但每次在测试上下文中都会出现异常。

逻辑的要点是——我有两个类,分别称为 Service 和 Helper。脚手架实现是:

interface Service : NSObject 
    BOOL svcCallComplete;

@end

@implementation Service

- (id) init 
    if ((self=[super init])==nil) 
        return nil;
    
    return self;

@end

interface Helper : NSObject 

@end

@implementation Helper

- (id) init 
    if ((self=[super init])==nil) 
        return nil;
    
    return self;

@end

Helper 是 Service 中某个属性的观察者。在我的正常运行时逻辑的上下文中,我通过调用服务实例方法addSvcObserver 来做到这一点:

Service.m:

- (void) addSvcObserver:(id)observer 
    [self addObserver:observer
           forKeyPath:@"svcCallComplete"
              options:NSKeyValueObservingOptionNew
              context:nil];

Helper 遵循 KVO 观察模式,因此:

Helper.m:

- (void) observeValueForKeyPath:(NSString*)keyPath
                       ofObject:(id)object
                         change:(NSDictionary*)change
                        context:(void*)context 

非常直截了当,我不会进入监控属性更改的逻辑,因为问题发生在此之前——如果我有如下代码摘录:

Service* service = [[Service alloc] init];
Helper* helper = [[Helper alloc] init]; 

[service addSvcObserver:helper];

在非测试用例中没有问题(即,这个和相关的 KVO 逻辑按预期工作)。但是,在 XCTest 测试方法的上下文中执行 addSvcObserver 调用会产生立即访问被拒绝异常。 我已经包含了一个异常“全部中断”断点——在addObserver:forKeyPath:options:context: 调用期间,问题似乎出现在objc_registerClassPair 中。测试目标明确禁用了 ARC,因为它提供测试覆盖的项目(目标是 ios7)由于遗留原因是非 ARC;这似乎不会对其他测试造成任何问题。

想法?

【问题讨论】:

【参考方案1】:
interface Service : NSObject 

@property (nonatomic) BOOL svcCallComplete;

您应该将 svcCallComplete 声明为属性。

因为对于您希望观察的属性,观察到的类必须是键值观察兼容的

我认为你得到objc_registerClassPair 的原因可能是因为KVO 动态注册了Service 的子类,但找不到svcCallComplete 的setter 方法。动态子类需要覆盖该setter 方法并发送通知。

更多详情read this。

【讨论】:

我确实尝试过使用属性语法,但并没有什么不同。不过,感谢您的链接——Mike 的好文章,它为我指明了正确的方向。正如他在那篇文章中指出的那样,使用手动更改通知时实际上不需要设置器。请参阅我的答案以了解实际原因。【参考方案2】:

结果证明是我的 KVO 逻辑实现不完整。根据指南here,您必须在使用手动更改通知时覆盖automaticallyNotifiesObserversForKey:NSObject 实现——我在最初阅读文本时不知何故错过了这一点。我在Service 类中添加了以下内容:

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey 

    BOOL automatic = NO;
    if ([theKey isEqualToString:@"svcCallComplete"]) 
        automatic = NO;
     else 
        automatic = [super automaticallyNotifiesObserversForKey:theKey];
    
    return automatic;

现在测试用例中的一切都是正确的。有谁愿意冒险猜测为什么这在正常(非测试)情况下没有爆炸?

【讨论】:

您是否在应用程序中设置了异常断点,或者您是否正在查看它记录到控制台的内容?异常可能已被运行循环消耗。 -- 没关系,我看到你说的是访问异常(即 Mach 异常)而不是 ObjC 异常。

以上是关于XCTest 测试方法中的 KVO addObserver 逻辑崩溃的主要内容,如果未能解决你的问题,请参考以下文章

XCTest 中的测试通知(UI 测试)

如何从 XCTest 中的 UI 目标调用主目标中的方法?

在 XCTest 中的每个测试用例之后停止应用程序终止

如何使用 XCTest UI 测试区分 iOS 13 中的标题和静态文本

用于单元测试 Objective-C XCTest 的 .m 文件中的访问变量定义

现有项目的 XCTest