tl;dr
在 Pivotal,我们编写了 Cedar,因为我们在 Ruby 项目中使用并喜爱 Rspec。 Cedar 并不是要取代 OCUnit 或与 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 语法)。与购物车包含其他商品时相比,当购物车为空时,此方法的行为可能有所不同;如果用户输入了折扣代码,则可能会有所不同;如果指定的商品无法通过所选的运送方式运送,则可能会有所不同;当这些可能的条件相互交叉时,您最终会得到几何数量的可能上下文;在 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 支持。苹果基本上拥有无限的资源,所以如果他们想要完成某件事,它就会完成。毕竟,这是我们正在玩的苹果沙盒。然而,硬币的另一面是,苹果每天都会收到数以百万计的支持请求和错误报告。他们非常擅长处理所有问题,但他们可能无法立即或根本无法处理您报告的问题。 Cedar 比 OCUnit 更新且不够成熟,但如果您有疑问、问题或建议,请发送消息到 Cedar 邮件列表 ([电子邮件受保护] /cdn-cgi/l/email-protection),我们将尽力帮助您。另外,请随意从 Github (github.com/pivotal/cedar) 分叉代码并添加您认为缺少的任何内容。我们将测试框架开源是有原因的。
在 iOS 设备上运行 OCUnit 测试可能很困难。老实说,我已经有一段时间没有尝试过这个了,所以它可能会变得更容易,但上次我尝试时,我根本无法让 OCUnit 测试让任何 UIKit 功能正常工作。当我们编写 Cedar 时,我们确保可以在模拟器和设备上测试依赖于 UIKit 的代码。
最后,我们编写了 Cedar 用于单元测试,这意味着它与 UISpec 这样的项目没有真正的可比性。自从我尝试使用 UISpec 以来已经有一段时间了,但我理解它主要专注于以编程方式驱动 iOS 设备上的 UI。我们特别决定不尝试让 Cedar 支持这些类型的规范,因为 Apple(当时)即将发布 UIAutomation。