何时需要在测试目标中包含应用程序源代码?

73
在一个新项目中,我有这个简单的测试。
#import <XCTest/XCTest.h>
#import "ViewController.h"

@interface ViewControllerTests : XCTestCase
@end

@implementation ViewControllerTests

- (void)testExample
{ 
    // Using a class that is not in the test target.
    ViewController * viewController = [[ViewController alloc] init];
    XCTAssertNotNil(viewController, @"");
}

@end

ViewController.h不是测试目标的一部分,但编译和运行测试没有任何问题。

enter image description here

我认为这是因为应用程序首先构建(作为依赖项),然后是测试。 链接器随后确定ViewController类是什么。

然而,在旧项目中,与完全相同的测试和ViewController文件相比,构建在链接器阶段失败:

Undefined symbols for architecture i386:
"_OBJC_CLASS_$_ViewController", referenced from:
  objc-class-ref in ViewControllerTests.o
即使创建全新的XCTest单元测试目标,也会出现此链接器错误。
为了绕过这个问题,可以将源文件包括在应用程序和测试目标中(在上面的图像中选中两个框)。这会导致重复符号的构建警告,在模拟器系统日志中显示(打开模拟器并按cmd-/键查看):
Class ViewController is implemented in both 
[...]/iPhone Simulator/ [...] /MyApp.app/MyApp and 
[...]/Debug-iphonesimulator/LogicTests.octest/LogicTests. 
One of the two will be used. Which one is undefined.
偶尔这些警告会引起问题,下面是一个例子:
 [viewController isKindOfClass:[ViewController class]]; // = NO
 // Memory address of the `Class` objects are different.

 NSString * instanceClassString = NSStringFromClass([viewController class]);
 NSString * classString         = NSStringFromClass([ViewController class]);

 [instanceClassString isEqualToString:classString]; // = YES
 // The actual class names are identical

那么问题是,在旧项目中有哪些设置要求将应用程序源文件包含在测试目标中?


评论摘要

在可工作和不可工作的项目之间:

  1. 链接器输出没有任何区别(以 Ld 开头的命令)。
  2. 目标依赖项没有任何区别(测试目标有 1 个依赖项,即应用程序)。
  3. 链接器设置没有任何区别。

测试目标设置可能存在问题。您能展示一下测试目标的设置吗? - Sulthan
@Sulthan - 感谢您的回复。每个目标有大约200个构建设置。您知道哪些可能是相关的吗? - Robert
链接和依赖项。与问题共享一个示例项目将是最好的解决方案。 - Sulthan
@Sulthan - 很抱歉我不能分享这个项目。我已经验证了即使我创建一个新的XCTest目标,这个问题仍然存在于旧项目中,所以我猜测问题在于项目设置上。从工作到不工作,目标依赖项是相同的(只有一个应用程序)。链接器设置是相同的,除了“其他链接器标志”,其中一个是-framework XCTest,另一个是ObjC。我纠正了这个差异,但它仍然无法编译 :( 你还能想到其他什么吗? - Robert
直接检查链接器输出(项目导航器中最右侧的选项卡),即“link”步骤。检查传递给链接器的参数之间的差异。 - Sulthan
显示剩余9条评论
7个回答

51
我花了一些时间来解决这个问题。
如果你阅读这份文档,你会发现Xcode有两种运行测试的模式。逻辑测试和应用程序测试。它们的区别在于逻辑测试会构建自己的目标,其中包含您的类和符号。生成的可执行文件可以在模拟器中运行,并将测试输出报告给Xcode。另一方面,应用程序测试会构建动态库,链接到您的代码,并在运行时注入到应用程序中。这使您可以在iPhone环境中运行测试并测试Xib加载等其他功能。
因为在取消链接源文件后,测试目标缺少符号,所以似乎您的旧项目似乎配置了逻辑测试而不是应用程序(单元)测试的测试目标。
由于现在Xcode似乎试图不区分这两种模式,并默认创建一个应用程序测试目标,因此让我们逐步介绍您可能需要更改的所有内容,以将您的逻辑测试目标转换为单元测试目标。

我假设您有一个应用程序目标,而不是静态库目标,因为操作步骤会有所不同。

  1. 在测试目标的构建设置中删除“Bundle Loader”和“Test Host”构建设置。稍后我们将让Xcode重新添加它们。
  2. 您需要从测试目标中删除应用程序的所有.m文件。您可以通过在Xcode文件检查器中选择所有.m文件并删除测试目标,或者使用测试目标的编译源代码构建阶段来完成此操作。
  3. 更改测试目标的“Framework search paths”。对于Xcode 5,它们应该按照以下顺序排列:$(SDKROOT)/Developer/Library/Frameworks $(inherited) $(DEVELOPER_FRAMEWORKS_DIR) ,且不要添加额外的引号或反斜杠
  4. 转到测试目标的构建设置的一般面板,并从下拉菜单中选择您的目标。如果菜单已经指定了您的应用程序目标,则应将其切换开关打开并关闭。这将使Xcode重新配置Bundle loader和Test Host设置以正确的值。
  5. 最后,请仔细检查您的应用程序方案。在方案下拉菜单中选择编辑方案。然后点击测试操作。确保您的测试目标在信息面板上的列表中,并确保选择了所有测试。
这些信息基本上来自于上面链接的文档,但我已经更新了适用于Xcode 5的步骤。
编辑:
嗯,完全注意到eph515所说的关于调试符号可见性的问题,但您还应该检查是否有人将方案的测试操作设置为在“Release”或其他配置中构建。点击方案选择器并选择编辑方案。单击测试操作,然后确保构建配置为“Debug”。

build configuration screen for test action in a scheme

如果您有一个静态库目标

如果您有一个静态库目标,则有两个选项: 1. 逻辑测试 2. 在主机应用程序中进行应用程序测试

对于1,您必须确保您的静态库目标的Bundle LoaderTest Host为空。然后,您的源代码必须编译到测试目标中,因为它们没有其他运行方式。

对于2,您需要在Xcode中创建一个新的应用程序项目,并将您的静态库项目添加为子项目。然后,您需要手动将您的新应用程序的测试目标的Bundle LoaderTest Host构建设置复制到您的静态库测试目标中。接下来,您打开新的测试应用程序的方案,并将您的测试目标添加到新应用程序的测试操作中。 要在您的库上运行测试,请运行主机应用程序的测试操作。


谢谢你的回答,看起来不错。我明天会调查一下。 - Robert
我按照所有步骤进行了操作,但它还是失败了。我甚至在现有的工作区中创建了一个新的应用程序目标(这自动生成了一个新的应用程序测试目标),但它仍然失败了。我验证了框架搜索路径,并按照描述重新生成了捆绑加载器和测试主机。 我现在猜测可能存在一些继承的工作区设置导致它出现问题。 - Robert
我还假设您有一个应用程序目标,而不是静态库目标,因为指令会有所不同。 您能告诉我在哪里找到静态库目标的说明吗? - Robert
我会在我的回答中补充。 - jackslash
1
我接受了这个答案,但是把赏金给了eph515的答案。这是正确的答案,但eph515的回答帮助我找到了我的具体问题。 - Robert
希望我能给你的答案点赞100次! :) 救了我的一天 - 非常感谢! - Karoly Nyisztor

22
在Xcode 6上,我能够通过在测试目标的常规选项卡里勾选“允许测试主机应用程序API”来解决这个问题。

Xcode Screenshot


有趣 - 谢谢!这是为一个旧项目吗?这是新项目的默认选择吗? - Robert
2
这适用于在Xcode 5中创建的项目,在Xcode 6中稍后添加了测试目标。我不知道它是否默认选中新项目。 - yood
谢谢,这解决了我使用Xcode 5创建的应用程序项目。但是,“主机应用程序”选择对我的静态库不适用,该库仍然存在“未定义的架构符号”错误(在遵循@jackslash等指示后)。有没有更多线索来修复Xcode 6中静态库的测试? - kalana
@clance_911 也请确保单个对象预链接已关闭。 - PatchyFog

18

我也遇到了这个问题,并按照jackslash的建议进行了操作,但还有一个额外的步骤:选择您的主要目标,并查找“默认情况下隐藏的符号”(在Apple LVM 5.0 - Code Generation下),如果值为Yes,则将其更改为No。 这似乎 '取消隐藏' 编译源代码的所有符号,单元测试目标正在寻找这些符号。 对我有效。 请确保您同时包括jackslash概述的所有步骤。


1
请注意,iOS中“默认隐藏符号”的默认值似乎是Debug构建的NO,而是Release构建的YES。您的项目应该与这些默认值相匹配。 - jackslash
如果涉及符号方面的问题,请检查“复制期间剥离调试符号”,对于“Debug”应该是“NO”,对于“Release”应该是“YES”。 - jackslash
是的,我相信“较新”的XCode项目默认情况下将符号隐藏设置为NO以进行调试,因为我认为XCode 5现在默认包括单元测试,或者如果在Xcode 4中选择包括单元测试选项。对于由XCode 4及之前版本创建且未包含单元测试的项目,该值为YES。 - eph515
答案是将此标志以及“部署后处理”更改为调试状态的“NO”。我还不得不调整链接库以避免重复。因为如果不这样做,我就不得不通过所有构建设置了解问题。我已经给了这个答案赏金。 - Robert
这在我的情况下是正确的答案,乍一看与问题描述中的相同。 我转到主目标的“Build Settings”下,在“Apple LLVM 6.1 - Code Generation”下将“Symbols Hidden By Default”设置为“否”。@eph515 谢谢! - e2l3n

9
答案是jackslash和eph515的答案的结合。
就像eph515的答案中所说,默认情况下隐藏符号应该为No以进行调试。

enter image description here

此外,对于调试来说,部署后处理应该是“否”。

enter image description here

此外,所有包含在测试目标中的库都应该从单元测试中删除。只留下屏幕截图中的3个以及与单元测试特定的任何内容。

enter image description here

此外,如果在列表末尾有一个运行构建脚本的构建阶段,则应将其删除(因为它是单元测试的产物)。
然后按照jackslash's answer中的所有步骤操作。

1
总的来说,我认为这将是一个非常有建设性和信息丰富的stackoverflow问题,对于未来的参考非常有价值 :) - jackslash
感谢您的总结和集中,这很有效。 - Dan Rosenstark

0
在我的情况下,Xcode 6.2 中的错误是项目目标和测试目标中不同的架构。
项目目标仅具有 armv7 和 armv7s 架构(由于某些较旧的库)。
项目测试目标具有 armv7、armv7s 和 arm64 架构。
删除 arm64 架构可解决我的问题。
Project Editor -> Project Tests target -> Build Settings -> Valid Architectures = armv7 armv7s

(也许需要将"Architectures"设置为$(ARCHS_STANDARD_32_BIT),而不是$(ARCHS_STANDARD))


1
移除 ARM64 将禁止您支持某些苹果设备,例如 iPhone5 和 iPad: http://jamesdempsey.net/ios-device-summary/ - OhadM

0

对我来说,这仅仅是因为没有为Scheme添加测试目标。

对于应用程序目标,请转到“编辑Scheme”,然后单击右侧的“测试”,然后使用底部的+按钮添加一个测试目标: 输入图像描述


0

当您创建用于测试应用程序的单元测试包(单元测试目标)时,有两个选项:

  1. 启用允许测试主机应用程序API
General -> Host  Application <app_name> -> >check< Allow testing Host Application APIs 
  • 编译速度慢
  1. 将每个应用程序的测试文件添加到目标成员资格[关于]
  • 您应该注意测试类中使用的类依赖关系(它们也应该被添加)

当您编写测试时,如果没有启用任何选项,则可能会出现

Undefined symbol: nominal type descriptor for <class_name>
Undefined symbol: type metadata accessor for <class_name>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接