什么时候需要将应用程序源包含在测试目标中?


72

在一个新项目中,我有这个简单的测试

#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不是测试目标的一部分,但是它可以编译并运行测试而不会出现任何问题。

在此处输入图片说明

我认为这是因为应用程序首先(作为依赖项)构建,然后是测试。链接器然后找出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)2014年

@Sulthan-感谢您的回复。每个目标大约有200个构建设置。您知道哪些可能相关吗?
罗伯特

链接和依赖关系。与问题共享一个示例项目将是最好的解决方案。
Sulthan

@Sulthan-不幸的是我不能分享这个项目。我已经验证了即使在创建新的XCTest目标时,在旧项目上仍然会发生这种情况,因此我认为问题出在项目设置中。目标依赖关系从有效到无效(1项是应用程序)是相同的。链接器的设置是相同的除了“其他连接标志”这是-framework XCTest在一个和ObjC在其他。我纠正了这种差异,但仍然无法编译:(您还能想到其他吗?
罗伯特

直接检查链接器输出(项目导航器中最右边的选项卡),此link步骤。检查传递给链接器的参数之间的差异。
苏珊(Sulthan)2014年

Answers:


50

我花了一些时间解决这个问题。

如果阅读此文档,您会发现Xcode具有两种运行测试的模式。逻辑测试和应用测试。不同之处在于,逻辑测试将使用您的类和符号内置它们来构建自己的目标。生成的可执行文件可以在模拟器中运行,并将测试输出报告回Xcode。另一方面,应用程序测试会建立一个动态库,该库链接到您的代码,该代码将在运行时注入到应用程序中。这使您可以在iPhone环境中运行测试以及测试Xib加载和其他功能。

由于取消链接源文件时测试目标中缺少这些符号,因此您的较旧项目似乎为逻辑测试(而不是应用程序(单元)测试)配置了测试目标。

如今,由于Xcode似乎一直试图不区分两者,因此默认情况下不创建应用程序测试目标,而是让您逐步了解将逻辑测试目标转变为单元测试所必须进行的所有更改。

我还将假设您有一个Application Target而不是一个静态库目标,因为方向会有所不同。

  1. 在测试目标的构建设置中,删除“捆绑程序加载器”和“测试主机”的构建设置。我们将让Xcode在以后添加这些
  2. 您需要从测试目标中删除应用程序中的所有.m文件。您可以通过选择所有.m文件并在Xcode文件检查器中删除测试目标来执行此操作,也可以使用测试目标的编译源构建阶段。
  3. 更改测试目标的“框架搜索路径”。对于Xcode 5,它们应该按 $(SDKROOT)/Developer/Library/Frameworks $(inherited) $(DEVELOPER_FRAMEWORKS_DIR) 该顺序排列,并且没有多余的引号或反斜杠。
  4. 转到测试目标的构建设置的“常规”窗格,然后从下拉菜单中选择目标。如果菜单已指定您的应用程序目标,则应将其关闭然后再打开。这将使Xcode用正确的值重新配置Bundle加载器和Test Host设置。
  5. 最后,仔细检查您的应用程序的方案。在方案下拉列表中,选择编辑方案。然后单击测试操作。确保测试目标位于信息窗格上的列表中,并确保所有测试都已选中。

此信息或多或少来自上述链接的文档,但是我更新了Xcode 5的步骤。

编辑:

嗯,100%注意到eph515在说调试符号可见,但是您可能还想检查是否有人没有将方案的测试动作设置为在Release或其他配置中构建。单击方案选择器,然后选择编辑方案。单击测试操作,然后确保构建配置为Debug

构建方案中的测试操作的配置屏幕

如果您有静态库目标

因此,如果您有一个静态库目标,则有两个选择:1.逻辑测试2.主机应用程序中的应用程序测试

对于1.,您必须确保Bundle Loader并且Test Host对于静态库目标为空。然后,您的源代码必须编译到测试目标中,因为它们将无法运行。

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


谢谢您的回答,看起来不错。明天要调查。
罗伯特

我做了所有步骤,但失败了。我什至在现有工作区中创建了一个新的应用程序目标(它会自动生成一个新的应用程序测试目标),但仍然失败。我验证了框架的搜索路径,并按照说明重新生成了Bundle Loader和Test Host。我现在的猜测是,有一个继承的工作空间设置将其弄乱了。
罗伯特

“我还将假设您有一个应用程序目标,而不是一个静态库目标,因为方向会有所不同。” 您能告诉我在哪里可以找到有关静态库目标的说明吗?
罗伯特

我将添加到我的答案中
jackslash 2014年

1
Iv接受了这个答案,但是得到了eph515的赏金。这是正确的答案,但是eph515帮助我找到了特定问题。
罗伯特

22

在Xcode 6上,我能够通过在测试目标>常规>测试中选中“允许测试主机应用程序API”来解决此问题。

Xcode屏幕截图


有趣-谢谢!这是用于旧的现有项目吗?默认情况下是否为新项目选择此选项?
罗伯特

2
这适用于在Xcode 5中创建的项目,稍后在Xcode 6中添加了测试目标。我不知道默认情况下是否为新项目选择了它。
2014年

谢谢,这已修复了用Xcode 5创建的我的应用程序项目。但是“主机应用程序”选择不适用于仍具有“体系结构的未定义符号”错误的静态库(在遵循@jackslash等的指示后)。还有其他线索可以修复Xcode 6静态库的测试吗?
kalana 2014年

@ clance_911还应确保关闭单个对象
预链接

18

我也遇到了这个问题,并遵循jackslash的建议,但又添加了一个建议:选择您的主要目标,并查找默认情况下隐藏的Symbols(在Apple LVM 5.0-Code Generation下),如果值为Yes,请将其更改为No。以“隐藏”单元测试目标正在寻找的已编译源的所有符号。为我工作。请确保还包括粗斜线概述的所有步骤。


1
请注意,iOS默认的“默认隐藏的符号”似乎NO适用于Debugbuilds和YESfor Release。您的项目可能应该符合默认设置
jackslash 2014年

如果有件事做的符号也选中“删除调试符号的复制过程中”,这也应该是NODebugYESRelease
jackslash

是的,我相信“较新的” XCode项目的“调试时默认隐藏的符号”设置为“否”,因为我认为XCode 5现在默认包含单元测试,或者是否在Xcode 4中选择了包含单元测试的选项。由XCode 4及其之前版本创建的项目(不包括单元测试)为YES
eph515 2014年

答案是更改此标志以及Deployment PostprocessingtoNO进行调试。我还不得不摆弄喜欢的库,以避免重复。iv给了这个答案赏金,因为我本来不会通过所有构建设置。
罗伯特

在我看来,这是正确的答案,乍一看与问题中的描述相同。我转到了主要目标的“构建设置”,然后在“ Apple LLVM 6.1-代码生成”下将“默认隐藏的符号”设置为“否”。@ eph515谢谢!
e2l3n

9

答案是jackslash和eph515的答案的组合。

如在eph515的答案中,symbols hidden by default对于调试应为“否”。

在此处输入图片说明

deployment postprocessing对于调试也应该为“否”。

在此处输入图片说明

同样,测试目标中包含的所有库也应从单元测试中删除。屏幕截图中只剩下3个,以及单元测试特有的所有内容。

在此处输入图片说明

同样,如果列表末尾有运行构建脚本构建阶段,则应将其删除(因为这是单元测试的产物)。

然后按照jackslash的答案做所有事情。


1
总之,我认为这将是一个非常有
启发性

感谢您进行总结和集中整理,此方法有效。
Dan Rosenstark

0

就我而言,在Xcode 6.2中是项目目标和测试目标中不同体系结构中的错误。

项目目标仅具有armv7和armv7s体系结构(由于某些较旧的库)

Project Tests目标具有armv7,armv7s和arm64体系结构。

删除arm64体系结构可以解决我的情况。

Project Editor -> Project Tests target -> Build Settings -> Valid Architectures = armv7 armv7s

(也许还需要将“ Architectures”(而不是将$(ARCHS_STANDARD)设置为$(ARCHS_STANDARD_32_BIT)))


1
删除ARM64会禁用您对某些苹果设备(如iPhone5和iPad)的支持:jamesdempsey.net/ios-device-summary
OhadM 2015年

0

对我而言,这只是为计划添加任何测试目标的情况。

对于应用程序目标,请转到“编辑方案”,然后单击右侧的“测试”,然后使用底部的+按钮添加一个测试目标: 在此处输入图片说明


0

创建Unit Testing Bundle用于测试应用程序的(单元测试目标)时,有两个选择

  1. 启用 Allow testing Host Application APIs
General -> Host  Application <app_name> -> >check< Allow testing Host Application APIs 
  • 建造缓慢
  1. 将每个应用的测试文件添加到Target Membership[关于]
  • 您应该注意用于可测试类的类依赖关系(也应该添加它们)

当您编写测试且未启用任何选项时,您可以获得

Undefined symbol: nominal type descriptor for <class_name>
Undefined symbol: type metadata accessor for <class_name>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.