感谢 Shaggy Frog 的回答,我们知道 Xcode 发行说明中提到的神秘“迁移器”是通过选择“编辑 > 重构 > 转换为 XCTest”启动的向导。我将分两部分写下我对这个向导的体验。第一部分是对主要问题的不完整回答,第二部分回答次要问题。
第 1 部分:如何从 OCUnit 迁移到 XCTest
您需要意识到的第一件事是,要让向导工作,您需要选择一个单元测试目标。如果您选择了主要目标,向导不会列出任何要转换的目标。
一旦我发现了这一点,我就能够逐步完成向导,但就我而言,最终结果仍然是一个惊人的失败!该向导声称不需要更改源代码,只需更新构建设置即可迁移到 XCTest。最后,向导甚至没有正确地做到这一点:它确实删除了对 SenTestingKit 框架的引用,但它确实没有添加了对 XCTest 的引用框架。
无论如何,以下是我必须手动进行的更改列表,因为向导无法为我进行更改。如果向导更适合您,您可能不需要执行所有这些操作。
- 从单元测试目标中删除“运行脚本”构建阶段
- 将所有测试用例类的基类从
SenTestCase更改为XCTestCase
- 将导入的标头从
<SenTestingKit/SenTestingKit.h>更改为<XCTest/XCTest.h>
- 在测试目标的 Build Settings 中,将 Wrapper Extension 从
octest 更改为 xctest。
- 将所有断言宏从
ST* 重命名为 XCT*(例如,STAssertTrue 变为 XCTAssertTrue)
- 上述例外:
STAssertEquals 需要重命名为XCTAssertEqual(注意末尾缺少的“s”)。如果您收到此编译器警告,您将知道您已经忘记了这一点:warning: implicit declaration of function 'XCTAssertEquals' is invalid in C99
- 新的 XCTest 断言宏不允许将
nil 作为故障描述传递。例如,XCTAssertNotNil(anObject, nil) 是不可能的,必须更改为 XCTAssertNotNil(anObject)。当你得到这个编译器错误时,你就会知道你有这个问题:error: called object type 'NSString *' is not a function or function pointer。
- 如果您确实需要传递失败描述,新的 XCTest 断言宏需要格式说明符的常量表达式,就像
NSString 类方法 stringWithFormat: 所做的那样。当你得到这个编译器错误时,你就会知道你有这个问题:error: expected ')'。一些例子:
NSString* formatSpecifier = @"%@";
NSString* failureDescription = @"foo";
// These are OK
XCTAssertNotNil(anObject, @"foo")
XCTAssertNotNil(anObject, @"%@", failureDescription)
// These are not OK
XCTAssertNotNil(anObject, failureDescription);
XCTAssertNotNil(anObject, formatSpecifier, failureDescription);
最后但同样重要的是,正如前面已经提到的,对 XCTest 框架的引用需要添加到单元测试目标中。如果您收到诸如Undefined symbols for architecture i386: "_OBJC_CLASS_$_XCTestCase", referenced from: foo 之类的链接器错误,您就会知道您已经忘记了这一点。
Xcode 6 更新:在 Xcode 6 中不再需要链接到 XCTest(事实上 XCTest 甚至不再被列为可用框架)。而是将构建设置 CLANG_ENABLE_MODULES 设置为 YES(在 UI 中显示为“启用模块(C 和 Objective-C)”)。这将导致clang 在看到#import <XCTest/XCTest.h> 语句时自动链接到XCTest。详情请见"Modules" section of the clang documentation。
第 2 部分:如何在 Xcode 5 中运行 OCUnit 测试
此时我遇到了一个链接器错误,这让我意识到我迁移到 XCTest 的任务失败了。原因:XCTest 不是 SDK 6.1 的一部分,但我仍在使用基本 SDK iOS 6.1 构建我的项目(this SO answer 解释了如何将 SDK 6.1 集成到 Xcode 5 中)。
由于我无法继续迁移,因此我目前的解决方案是保留基于 SenTestingKit/OCUnit 的单元测试,直到我有时间将我的应用程序升级到 iOS 7。这就是我必须要做的为了让单元测试运行:
- 从单元测试目标中删除“运行脚本”构建阶段。这就是让 Xcode 通过“测试”操作 (⌘ + U) 在选择单元测试目标时执行单元测试所需的全部内容.
- 但这并不理想,因为我不想仅仅为了执行单元测试而切换目标。相反,我想在选择主要目标时执行单元测试。因此,第二步是修改主目标的 Xcode 方案,以便在我运行“测试”操作时,执行单元测试目标的测试。
最终的解决方案不如 Xcode 4.x 中的好,在 Xcode 4.x 中,每次我运行主要目标的“运行”或“构建”操作时都会自动执行单元测试。不幸的是,如果没有“运行脚本”构建阶段,我似乎无法让它工作。