tl;博士
在 Pivotal,我们编写 Cedar 是因为我们在 Ruby 项目中使用并喜欢 Rspec。Cedar 并不是要取代 OCUnit 或与 OCUnit 竞争。它旨在为 Objective C 带来 BDD 样式测试的可能性,就像 Rspec 在 Ruby 中开创了 BDD 样式测试一样,但并未消除 Test::Unit。选择其中一个很大程度上取决于风格偏好。
在某些情况下,我们设计 Cedar 是为了克服 OCUnit 为我们工作的方式中的一些缺点。具体来说,我们希望能够在测试中使用调试器,从命令行和 CI 构建中运行测试,并获得有用的测试结果文本输出。这些东西可能或多或少对你有用。
长答案
在 Cedar 和 OCUnit 等两个测试框架之间做出决定(例如)归结为两件事:首选风格和易用性。我将从风格开始,因为这只是意见和偏好的问题;易用性往往是一系列权衡。
风格考虑超越了您使用的技术或语言。xUnit 风格的单元测试比 BDD 风格的测试存在的时间要长得多,但后者迅速流行起来,主要是由于 Rspec。
xUnit 风格测试的主要优点是它的简单性和广泛采用(在编写单元测试的开发人员中);几乎任何您可以考虑使用其编写代码的语言都有可用的 xUnit 风格的框架。
与 xUnit 风格相比,BDD 风格的框架往往有两个主要区别:如何构建测试(或规范),以及编写断言的语法。对我来说,结构上的差异是主要的区别。xUnit 测试是一维的,给定测试类中的所有测试都有一个 setUp 方法。然而,我们测试的类不是一维的。我们经常需要在几个不同的、可能相互冲突的上下文中测试操作。例如,考虑一个带有 addItem: 方法的简单 ShoppingCart 类(出于本答案的目的,我将使用 Objective C 语法)。当购物车为空时,与购物车包含其他物品时相比,此方法的行为可能有所不同;如果用户输入了折扣代码,它可能会有所不同;如果指定的项目可以,它可能会有所不同' t 通过所选的运输方式运输;等等。当这些可能的条件相互交叉时,你最终会得到几何上增加的可能上下文;在 xUnit 风格的测试中,这通常会导致很多方法名称为 testAddItemWhenCartIsEmptyAndNoDiscountCodeAndShippingMethodApplies。BDD 风格框架的结构允许您单独组织这些条件,我发现这样可以更轻松地确保涵盖所有情况,也更容易找到、更改或添加单个条件。例如,使用 Cedar 语法,上面的方法如下所示:在 xUnit 风格的测试中,这通常会导致很多方法名称为 testAddItemWhenCartIsEmptyAndNoDiscountCodeAndShippingMethodApplies。BDD 风格框架的结构允许您单独组织这些条件,我发现这样可以更轻松地确保涵盖所有情况,也更容易找到、更改或添加单个条件。例如,使用 Cedar 语法,上面的方法如下所示:在 xUnit 风格的测试中,这通常会导致很多方法名称为 testAddItemWhenCartIsEmptyAndNoDiscountCodeAndShippingMethodApplies。BDD 风格框架的结构允许您单独组织这些条件,我发现这样可以更轻松地确保涵盖所有情况,也更容易找到、更改或添加单个条件。例如,使用 Cedar 语法,上面的方法如下所示:
describe(@"ShoppingCart", ^{
describe(@"addItem:", ^{
describe(@"when the cart is empty", ^{
describe(@"with no discount code", ^{
describe(@"when the shipping method applies to the item", ^{
it(@"should add the item to the cart", ^{
...
});
it(@"should add the full price of the item to the overall price", ^{
...
});
});
describe(@"when the shipping method does not apply to the item", ^{
...
});
});
describe(@"with a discount code", ^{
...
});
});
describe(@"when the cart contains other items, ^{
...
});
});
});
在某些情况下,您会发现其中包含相同的断言集的上下文,您可以使用共享的示例上下文将其干燥。
BDD 风格的框架和 xUnit 风格的框架之间的第二个主要区别,断言(或“匹配器”)语法,只是使规范的风格更好一些。有些人真的很喜欢,有些人不喜欢。
这就引出了易用性的问题。在这种情况下,每个框架都有其优点和缺点:
OCUnit 的存在时间比 Cedar 长得多,并且直接集成到 Xcode 中。这意味着创建一个新的测试目标很简单,并且在大多数情况下,启动和运行测试“正常工作”。另一方面,我们发现在某些情况下,例如在 iOS 设备上运行,让 OCUnit 测试工作几乎是不可能的。设置 Cedar 规范比 OCUnit 测试需要更多的工作,因为你已经获得了库并自己链接它(在 Xcode 中绝不是一项微不足道的任务)。我们正在努力简化设置,欢迎提出任何建议。
OCUnit 作为构建的一部分运行测试。这意味着您无需运行可执行文件即可运行测试;如果任何测试失败,您的构建就会失败。这使运行测试的过程更简单了一步,测试输出直接进入您的构建输出窗口,便于查看。我们选择将 Cedar 规范构建到您单独运行的可执行文件中,原因如下:
- 我们希望能够使用调试器。您可以像运行任何其他可执行文件一样运行 Cedar 规范,因此您可以以同样的方式使用调试器。
- 我们想要简单的控制台登录测试。您可以在 OCUnit 测试中使用 NSLog(),但输出会进入构建窗口,您必须展开构建步骤才能阅读它。
- 我们希望在命令行和 Xcode 中都易于阅读测试报告。OCUnit 结果很好地显示在 Xcode 的构建窗口中,但是从命令行构建(或作为 CI 过程的一部分)会导致测试输出与大量其他构建输出混合在一起。通过单独的构建和运行阶段,Cedar 将输出分开,因此测试输出很容易找到。默认的 Cedar 测试运行器复制标准样式的打印“.”。对于每个通过的规范,“F”代表失败的规范等。Cedar 还具有使用自定义报告对象的能力,因此您可以让它以您喜欢的任何方式输出结果,只需一点点努力。
OCUnit 是 Objective C 的官方单元测试框架,得到 Apple 的支持。苹果基本上拥有无限的资源,所以如果他们想要做某事,它就会完成。而且,毕竟,这是我们正在玩的 Apple 的沙盒。然而,硬币的另一面是,Apple 每天都会收到数以亿计的支持请求和错误报告。他们非常擅长处理所有问题,但他们可能无法立即或根本无法处理您报告的问题。Cedar 比 OCUnit 更新且不那么成熟,但是如果您有任何疑问或问题或建议,请向 Cedar 邮件列表 (cedar-discuss@googlegroups.com) 发送消息,我们将尽我们所能为您提供帮助。此外,请随时从 Github (github.com/pivotal/cedar) 分叉代码并添加您认为缺少的任何内容。
在 iOS 设备上运行 OCUnit 测试可能很困难。老实说,我已经有一段时间没有尝试过这个了,所以它可能变得更容易了,但我最后一次尝试时,我根本无法让任何 UIKit 功能工作的 OCUnit 测试。当我们编写 Cedar 时,我们确保我们可以在模拟器和设备上测试依赖于 UIKit 的代码。
最后,我们为单元测试编写了 Cedar,这意味着它无法与 UISpec 之类的项目真正相提并论。自从我尝试使用 UISpec 以来已经有一段时间了,但我理解它主要专注于以编程方式驱动 iOS 设备上的 UI。我们特别决定不尝试让 Cedar 支持这些类型的规范,因为 Apple(当时)即将宣布 UIAutomation。