如何在Xcode 4.5的“命令行工具”项目中设置工作逻辑单元测试目标?

7

我在为一个特定场景设置单元测试时遇到了问题。这是我的尝试:

在Xcode 4.5中,我创建了一个简单的OSX“命令行工具”应用程序项目(Foundation)。 请注意,Xcode不会自动为“命令行工具”项目提供添加单元测试的选项 - 因此请不要建议勾选复选框; 它不存在 :-/ 在我的项目中,我创建了一个微不足道的示例类,我想测试它; 例如“形状”。 我按照Apple的“Xcode Unit Testing Guide”中的说明进行操作,设置单元测试:(链接1): 我向项目添加了一个单元测试目标, 并编辑了“Test”方案以在新目标中运行测试。 在测试项目的实现(.m)文件中,我添加了一个导入Shape.h的引用,并在setUp()方法中编写代码来实例化一个形状并将其赋值给一个实例变量。

那时,我决定看看是否能够构建并且默认测试是否仍然可以运行。然而,当我从菜单中选择Product...Test时,构建失败,并出现以下错误:

Undefined symbols for architecture x86_64:
  "_OBJC_CLASS_$_Shape", referenced from:
      objc-class-ref in ExampleTests.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

解释这个错误不是问题。我明白单元测试目标没有链接到包含Shape实现的二进制文件。然而,我还不理解Xcode单元测试和目标配置。那么:
为了让测试目标与命令行工具的输出结果进行链接,我需要做什么?我可以从单元测试目标中链接到命令行可执行文件吗?苹果的文档似乎只适用于常规的OSX应用程序(*.app)和iOS应用程序,而这不属于此类。
我有业务逻辑类,我想在命令行工具环境中进行开发(起步阶段),所以我想了解如何在“命令行工具”类型的项目中运行单元测试目标。谢谢!
(附注:请注意,我不感兴趣从命令行运行我的单元测试——Stack Overflow已经提供了关于如何做到这一点的“类似”问题——而是在“命令行工具”类型的项目中运行单元测试,并且仍然在Xcode中运行。)
3个回答

13

我已经确定了一个适合我的解决方法,除了增加一个目标以外,该方法似乎没有显著的缺点。

简而言之:该解决方案涉及添加静态库目标,以利用Xcode在此类目标周围创建和运行单元测试代码的能力。然后,命令行工具目标委托给静态库,在其中定义并由实际的main()入口点调用另一个类似于main()的函数。命令行工具不包含非平凡代码,因此单元测试目标可以访问所有值得测试的内容。

以下是步骤:

  • 从空的Xcode中,从菜单中选择文件...新建项目

  • 在弹出的对话框中,选择OS X...应用程序...命令行工具。为了本示例,假设它被命名为SampleCmd

在基本的命令行工具项目创建完毕后:

  • 从菜单中选择文件...新建...目标

  • 在弹出的对话框中,选择OS X...框架和库...Cocoa库。为了本示例,假设它被命名为SampleCmdLogic

  • 选择静态类型,这样命令行工具将保持独立的可执行文件。

  • 确保勾选了包含单元测试选项框。

在创建静态库项目之后:

  • main.m中复制main()函数到SampleCmdLogic.m,替换@implementation块。(此文件仅保存主入口点。可以添加其他文件以用于Objective-C类等。)将该函数重命名为libMain()

  • SampleCmdLogic.h 中,添加一个新的 libMain() 声明,替换掉@interface块:
    int libMain(int argc, const char * argv[]);

  • 在命令行工具的 main.m 中,在顶部添加 #import "SampleCmdLogic.h"

  • 在命令行工具的 main.m 中,将真正的 main() 函数的整个内容改为:
    return libMain(argc, argv);

  • 现在代码已准备就绪,但还需要进行必要的链接步骤:

    • SampleCmd 项目设置中,在 Build Phases 下,展开 Target Dependencies 并添加 (+) SampleCmdLogic 作为依赖项。

    • SampleCmd 项目设置中,在 Build Phases 下,展开 Link Binary With Libraries 并添加 (+) libSampleCmdLogic.a

    现在一切准备就绪。当您切换到 SampleCmd 目标并从菜单中选择 Product..Run 时,构建应该成功,并按预期生成输出。当您切换到 SampleCmdLogic 目标并从菜单中选择 Product...Test 时,构建应该成功,并运行单元测试。在 SampleCmdLogicTests.m 中插入的初始默认失败的单元测试断言是 Xcode 插入的,这是正常现象。

    从此开始,继续添加所有逻辑和相应的测试到 SampleCmdLogic 目标。SampleCmd 目标仅应保持简单,仅提供命令行工具入口点。


    太有趣了:我刚好发现自己也需要解决这个问题!感谢你提出了一个简单而优雅的解决方案。 - Jon Reid

    3
    通常情况下,将测试目标添加到应用程序项目中需要进行一些额外的步骤,特别是要设置Bundle Loader和Test Host,就像我在https://dev59.com/FWjWa4cB1Zd3GeqPtsBg#12624873中所描述的那样。
    但是,当我使用命令行工具执行这些步骤并尝试运行测试时,它只会运行该工具。对于应用程序,则需要启动应用程序、将测试捆绑包注入正在运行的应用程序,然后执行测试。但是,这些阶段不适用于命令行工具。
    因此,您需要的不是注入的测试捆绑包,而是第二个运行测试的命令行工具。然后将您的类设置为同时针对测试工具和实际工具。 gh-unitgoogle-toolbox-for-mac 都遵循这种模型,因此建议您尝试它们。

    1
    谢谢,这个帮助很大,至少让我放弃了尝试让命令行工具直接参与单元测试的想法。相反,我使用了一个静态库目标来将Xcode单元测试支持重新引入我的项目中。请参考我的答案;有助于支持著名的格言“计算机科学中的所有问题都可以通过另一层间接性来解决”(虽然这更多是一个工具问题,而不是一个科学问题)。 - Chris W. Rea
    1
    我将接受自己的答案,但你会因为提供思考解决方法而得到赏金。 - Chris W. Rea

    1

    看起来最紧迫的问题就是新测试目标中没有包含Shape。尝试将Shape.m添加到测试目标中:

    1. 在项目导航器中单击Shape.m
    2. 打开实用程序视图(查看 -> 实用程序 -> 显示实用程序)
    3. 在实用程序视图的目标成员资格部分中,确保您的应用程序和测试目标都已选中。

    我不知道这是否是您设置中问题的全部,但它似乎是您最紧迫的问题的可能原因。


    1
    谢谢。我明白这样做只是将我的源文件包含在单元测试二进制可执行文件中重新编译,而不是链接到命令行工具目标并使用已编译的代码。我承认这可以在最简单的情况下产生一个工作的单元测试项目,但这不是一个理想的解决方案,因为将源文件包含和重新编译到单元测试项目中可能会引入微妙的差异;例如,被测试的结果代码不一定相同。在实际项目中,编译器选项、定义的符号等可能会有所不同,不是吗? - Chris W. Rea

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