【问题标题】:isKindOfClass returning NO unexpectedlyisKindOfClass 意外返回 NO
【发布时间】:2012-08-28 13:08:33
【问题描述】:

我的一个单元测试失败了,原因是我没有预料到的。似乎对 isKindOfClass 的调用返回 NO,但是当我调试并逐步执行时,似乎没有理由这样做。

代码是:

if ([self.detailItem isKindOfClass:[MovieInfo class]]) {
    [self configureViewForMovie];
}

我单步执行了代码并做到了:

po self.detailItem

显示:

(id) $1 = 0x0ea8f390 <MovieInfo: 0xea8f390>

那么,我错过了什么,为什么在这种情况下 if 语句会返回 false?

编辑:

这里是 DetailItem 的设置器:

- (void)setDetailItem:(id)newDetailItem
{
    if (_detailItem != newDetailItem) {
        NSLog(@"%@", [newDetailItem class]);
        _detailItem = newDetailItem;

        // Update the view.
        [self configureView];
    }

    if (self.masterPopoverController != nil) {
        [self.masterPopoverController dismissPopoverAnimated:YES];
    } 
}

它是来自主从模板的模板代码。

单元测试在setUp中创建一个MovieInfo:

movie = [[MovieInfo alloc] initWithMovieName:@"Movie" movieID:1];

并在测试中设置它

controller.detailItem = movie;

另外,我在setDetailItem中添加了参数断言:

NSParameterAssert([newDetailItem isKindOfClass:[MovieInfo class]] || [newDetailItem isKindOfClass:[PersonInfo class]] || newDetailItem == nil);

这个断言也失败了。

我在断言调用上方放置了两条日志语句:

NSLog(@"%@", [newDetailItem class]);
NSLog(@"%@", newDetailItem);

哪个显示:

2012-08-28 08:31:37.574 Popcorn[8006:c07] MovieInfo
2012-08-28 08:31:38.253 Popcorn[8006:c07] <MovieInfo: 0x6daac50>

更多编辑:

我在单元测试中设置它之前添加了isKindOfClass 检查,该检查通过了。

if ([movie isKindOfClass:[MovieInfo class]]) {
    NSLog(@"Yep"); //This passes and prints out
}
controller.detailItem = movie; //calls into the setter and fails.

【问题讨论】:

  • 您是否以这样的方式覆盖了- detailItem,以至于当您再次调用它时它可能会返回不同的结果?
  • 你能显示你分配self.detailItem的代码吗?重要的是它是一个实例,而不是类对象。
  • “MovieInfo”的超类是什么?
  • MovieInfo 是一个 NSObject
  • 你怎么知道isKindOfClass 正在返回NO?似乎您没有在任何地方检查返回值...您只是对class 进行了两次和三次检查,并且您多次记录它们,但您没有在任何地方检查isKindOfClass 的返回值...您能记录返回值,也许?

标签: objective-c cocoa


【解决方案1】:

这是因为被测类“DetailViewController”不在测试目标中。我本来希望这会以不同的方式表现出来(链接器错误或其他东西),但显然,它只会导致奇怪的行为。将 DetailViewController 添加到测试目标可以解决问题。

【讨论】:

  • 谢谢!真是奇怪的行为!
  • 谢谢!花了 2 个小时调试这个。
  • 谢谢!我不小心将这个类包含到我的测试目标中(即与你的情况相反),并且在删除它后,所有测试都通过了!真是奇怪的行为!
  • @Taz 的回答帮助我在这里找到了正确的解决方案:.m 文件永远不需要包含在您的测试目标中;只需包含用于比较的类的 .h(在本例中为 MovieInfo)
【解决方案2】:

我怀疑存在竞争条件,或者 Debug 和 Release 配置之间存在差异。这些将导致调试器和常规运行时之间的差异。

首先,确保self.detailItem 不是nil。这是此类问题的最常见原因。然后尝试使用日志记录而不是调试器来调试它。如果您确实有竞争条件,那么您也可以考虑使用printf() 而不是NSLog() 进行日志记录。 printf() 是一种对性能影响较小的多线程日志记录方式。

【讨论】:

  • 我在 isKindOfClass 调用之前有一个 NSLog(@"%@", self.detailItem) 并且它也显示 MovieInfo。
  • 我没有在单元测试中明确使用线程,所以不确定任何竞争条件来自哪里。
  • 切换到 printf: printf("%s", [[newDetailItem description] UTF8String]);这也打印出 MovieInfo。
【解决方案3】:

我在正在编写的库中遇到了同样的问题。有趣的是,它在我的 iOS 测试目标上运行良好,但在 Mac 测试目标上却失败了(有趣!)。

我认为该问题与类声明中的不匹配有关。该库正在使用 .h 声明,但单元测试正在查看内部声明。

举个例子更容易解释:

/*
 * ClassA.h
 */
@interface ClassA () : NSObject

@property (nonatomic, strong, readonly) NSString *someValue1;
@property (nonatomic, strong, readonly) NSString *someValue2;
@property (nonatomic, strong, readonly) NSString *someValue3;

@end

添加到.m中的属性列表

/*
 * ClassA.m
 */
@interface ClassA () : NSObject

@property (nonatomic, strong, readwrite) NSString *someValue2;
@property (nonatomic, strong, readwrite) NSDate *date;
@property (nonatomic, strong, readwrite) NSDateFormatter *dateFormatter;

@end

@implementation 
// implementation
@end

现在,由于单元测试目标的编译源设置为包含 ClassA.m,isKindOfClass: 将返回 no,但在调试器中运行时 po 命令和 NSStringFromClass([ClassA class]) 将返回您期望的值。

我知道这是一篇旧帖子,但我希望这很有用并可以节省很多时间。我花了将近一个小时试图解决这个问题!

【讨论】:

  • 最后,一个可靠的答案。这个答案应该得到更高的评价,因为它解释了此页面上的所有差异:Q1:为什么[self.detailItem class] == [ETTWallpaper class] 有效而[self.detailItem] isKindOfClass:[ETTWallpaper class]] 无效? A:因为类的比较依赖于 .m 或 .h 签名,这与类名的比较不同 Q2: 为什么要删除 .m 文件正在测试的类工作,以及添加 .m 文件? A:可能是因为 .m 文件包含对所需 .h 文件的引用,或者某些链接提供了它。
【解决方案4】:

如果您想始终将所有源文件添加到所有测试目标中,MarkPowell 的回答绝对有帮助。

但是,如果您将应用程序作为测试目标的目标依赖项(如果您的测试目标中只有测试源文件,而不是项目源文件),那么您会遇到与我相同的问题:你的类应该在 App 目标中而不是测试目标中(在我的例子中,它是一个测试助手类!)

【讨论】:

    【解决方案5】:

    正如@stefreak 所说,在标准单元测试配置中,被测对象不应包含在单元测试目标中。在这种情况下,如果您在测试目标中包含被测对象,则在构建时会向日志中写入以下类型的消息:

    Class foo is implemented in both <.app path> and <.xctest path>. One of the two will be used. Which one is undefined.
    

    here 所述,您不应在两个目标中都包含被测对象。将对象仅放在一个目标中将停止 isKindOfClass: 的意外行为。

    另外,我的一位同事发现您需要确保为“运行”构建关闭单元测试。在应用程序的方案下,选择“Build”,然后查看单元测试目标,“Run”应该被取消选择。

    如果您使用 Swift 进行开发,那么您应该在单元测试文件的顶部有一个 @testable import MyModule

    【讨论】:

      【解决方案6】:

      self.detailItem 可以成为nil 吗?在这种情况下,-isKindOfClass: 的结果将是 NO

      【讨论】:

      • 我检查了 nil(调试器中的 NSLog 调用和 po),它不是 nil 并返回预期的结果。
      【解决方案7】:

      同样的情况也发生在我身上。 我最终做了如下比较:

      [self.detailItem class]  == [ETTWallpaper class]
      

      自从

      [[self.detailItem class] isKindOfClass:[ETTWallpaper class]]
      

      没有工作,并且总是返回 NO,尽管确定它必须返回 YES

      【讨论】:

      • isKindOfClass: 将对象与Class 进行比较,而不是将ClassClass 进行比较。这应该适合你:[self.detailItem isKindOfClass:[ETTWallpaper class]]
      【解决方案8】:

      我认为 Xcode 7.0 目前存在一个错误。我有同样的问题,并检查了测试目标中包含的所有 CUT,但我有一个在代码中返回 NO-[isKindOfClass:],但在调试器中返回 YES。这正在模拟器和物理设备中发生。

      我最终检查了-[respondsToSelector:] 以检查我的班级的签名。不完美,但可以让我通过。

      【讨论】:

        【解决方案9】:

        我在使用 CocoaPods 1.0 运行单元测试时遇到了同样的问题 (SomeClass 在 Pod 库中)

        我收到以下警告消息:

        Class SomeClass is implemented in both </path/to/myapp> and </path/to/myapptest>. One of the two will be used. Which one is undefined.
        

        我的解决方案是将 Podfile 更新为:

        target "MyApp" do
          pod 'xxx'
          pod 'yyy'
        
          target "MyApp-Tests" do
              inherit! :search_paths
          end
        end
        

        参考资料: https://github.com/CocoaPods/CocoaPods/issues/4626

        【讨论】:

          【解决方案10】:

          link 中找到了解决方案:

          我的 SomeEntity 类包含在测试目标中。构建测试目标还包括作为依赖项的主应用程序,其中还包括 SomeEntity。这显然让 Xcode 相信有两种不同的类型。

          从测试目标中删除 SomeEntity 并且一切都通过了!

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2015-08-18
            • 1970-01-01
            • 2020-04-21
            • 2020-12-08
            • 2018-03-24
            • 1970-01-01
            • 2020-06-24
            • 2020-12-07
            相关资源
            最近更新 更多