在Xcode中对私有方法进行单元测试

63

我在一个玩具项目中尝试测试驱动开发。我可以让我的类公共接口的测试工作 (尽管我还没有确定这样做是否值得,因为编写测试代码比被测试的方法代码还多)。

我倾向于使用许多私有方法,因为我喜欢保持公共接口的简洁性;然而,我仍然想在这些方法上使用测试。

由于Cocoa是一种动态语言,我仍然可以调用这些私有方法,但测试时会出现警告,说我的类可能无法响应这些方法(虽然它明显可以)。由于我喜欢在没有警告的情况下进行编译,所以我的问题如下:

  1. 我该如何在Xcode中关闭这些警告?
  2. 是否还有其他方法可以关闭这些警告?
  3. 在尝试'白盒'测试中,我是否做错了什么?

到目前为止,我还没有在头文件中公开我的私有方法。为什么要这样做呢?我认为只在头文件中公开公共方法更加优雅。然而,在Objective-C中似乎必须改变这种做法。 - Nils
1
@Nils 在头文件中没有私有方法。一旦在那里,它们就成为公共方法。 - Abizern
是的,@Abizern,到目前为止我遵循了Peter Hosey的建议。 - Nils
7个回答

126

请记住,在Objective-C中实际上不存在“私有方法”,这不仅仅是因为它是一种动态语言。根据设计,Objective-C具有用于实例变量但没有用于方法的可见性修饰符——你可以调用任何你想要的方法并非意外。

@Peter的建议很好。作为他答案的补充,我使用的另一种方法(当我不想/不需要一个专门为私有方法而设的头文件时)是在单元测试文件中声明一个类别。(我用@interface MyClass (Test)作为名称。)这是一种很好的方法,可以添加在发布代码中无关紧要、只用于访问测试对象拥有的实例变量等方法。(当使用属性时,这显然不是问题。)

我发现这种方法使得容易暴露和验证内部状态,并添加针对测试的方法。例如,在此单元测试文件中,我编写了一个用于验证二叉堆正确性的-isValid方法。在生产环境中,这个方法会浪费空间,因为我假设堆是有效的——只有在修改代码后为了进行单元测试回归测试时才会用到它。


4
好的建议!我更喜欢这个想法,它能保持方法的曝光与上下文相一致。 - Bach
1
哇,Peter Hosey应该修复他的答案以参考你的。干得好,现在尝试一下+1。 - Dan Rosenstark
虽然你的回复很有帮助,但我想指出你提供的单元测试文件链接是无效的。+1 - Stunner
一个快速的谷歌搜索会链接到他的git仓库,链接 - Eren Beşel

90
如何在Xcode中关闭这些警告?
不要关闭。
是否有其他方法可以关闭这些警告?
不要关闭。
我在尝试白盒子测试时做错了什么吗?
没有。
解决方法是将您的私有方法移动到其自己的头文件中的类别中。将此头文件导入实际类和测试用例类的实现文件中。

4
+1 — 很好的建议!如果需要更详细的信息,请查看这个问题/答案:https://dev59.com/AHNA5IYBdhLWcg3wS70m。包含私有方法类别的私有头文件可以命名为 MyClass_Private.h,例如。 - Quinn Taylor
2
谢谢你的回答。我仍然不确定,但是我必须承认,编写测试并使其通过确实有一些“有趣”的因素。 - Abizern
我已经在扩展头文件中声明了这些私有方法,但是在XCode5中调用这些在扩展头文件中声明的私有方法时,编译器会出现错误。有什么想法吗? - ArdenDev
@IphoneDeveloper:您应该提出一个更详细的单独问题。 - Peter Hosey
@QuinnTaylor 实际上,这个约定非常有用,因为它提供了在<class_name>.h和<class_name>.m之间切换的快捷方式(CMD+CTRL+UP_ARROW),因为它还包括<class_name>_Private.h :) 至少在XCode5中是这样。 - atxe

4

几天前我开始接触TDD时也遇到了同样的问题。在Test-Driven iOS Development 这本书中,我发现了一个非常有趣的观点:

我经常被问到:“我应该测试我的私有方法吗?”或相关问题“我应该如何测试我的私有方法?”。在第二个问题的提问者已经假定了对第一个问题的答案是“Yes”,并且现在正在寻找一种方法来在他们的测试套件中暴露类的私有接口。 我的答案依赖于一个细微的事实的观察:您已经测试过您的私有方法。通过遵循在测试驱动开发中常见的红-绿-重构方法,您设计了对象的公共API来完成这些对象需要完成的工作。通过测试指定的工作(以及测试的持续执行确保您没有破坏任何内容),您可以自由地组织类的内部管道。 您的私有方法已经得到了测试,因为您所做的所有事情都是重构已经进行了测试的行为。您永远不应该陷入私有方法未经测试或测试不完整的情况,因为只有在看到清理公共方法实现的机会时才创建它们。这确保私有方法仅存在于支持类的情况下,因为它们必须在测试期间调用,因为它们绝对是从公共方法中调用的。

我读了这本书,虽然他在某些方面是正确的,但他没有回答问题。为什么我要将IBAction移动到接口文件中(它应该对其他类保持私有)?这不是一个模型对象,我们在这里谈论的是控制器,特别是视图控制器(这本书没有足够讲解iOS上的UI测试)。 - Abdalrahman Shatou
我的帖子是对原问题的回应。我认为引用包含了有趣的观点,因为当你需要测试私有方法时,这可能意味着你应该将这些方法移动到单独的对象中。而这种“引导”程序员向更小的对象和更短的方法是TDD的主要好处。当然,单元测试不是关于UI测试的。还有其他工具(在仪器中)和其他测试可以做到这一点。 - Alois Holub

4

1
那个回答部分解决了问题,但对于特定情况而言,最好的(或者至少更完整的)解决方案可能与Peter的回答或我的回答有关。 - Quinn Taylor
这也不是放置重复链接的地方。 - Rambatino

3

虽然使用私有头文件或定义自己的类别可能是更正确的解决方案,但还有另一种非常简单的解决方案:在调用方法之前将对象强制转换为(id)。


我认为这是更可取的。不重复,不分割源文件,并利用Objective-C的动态特性。 - Michael
2
@Michael 在 xCode5 中使用这种方法的问题是,使用未知选择器会导致警告。编译器在编译特定的单元测试时必须以某种方式看到选择器。因此,我喜欢Quinn Taylor的解决方案。 - Erik Engheim

3
如果您不想将私有方法实现分布在多个源文件中,对类别解决方案的改进是在头文件中定义一个扩展(本质上是一个匿名的类别 - 参考苹果文档),并将其导入到现有类的实现和相关单元测试源文件中。
使用扩展允许编译器警告您主@implementation块中不存在私有方法的实现。这个链接很好地说明了这一点。

请勿在此处发布链接,而是关键词。苹果的链接已被移动。 - Pétur Ingi Egilsson

1

易如反掌的工作。步骤如下: 1. 在您的目标m文件中,为接口Foo添加-(NSString*)getTestString;方法。

  1. 在您的单元测试文件中添加一个类别:

    @interface DemoHomeViewController() -(NSString*)getTestString; @end

然后,您现在可以做任何想做的事情了。


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