【问题标题】:Is there an idiomatic Objective-C technique to make an init method return a stub implementation for testing?是否有一种惯用的 Objective-C 技术可以使 init 方法返回一个存根实现以进行测试?
【发布时间】:2013-05-16 09:51:29
【问题描述】:

我正在为一个类Foo 编写单元测试,该类有一个协作者Bar。我想在Foo 的测试中使用Bar 的手动构建存根实现。

如果我在 Java 中执行此操作,我会给Foo 一个BarFactory 协作者,并在Foo 的测试中注入一个MockBarFactory,该测试总是返回我的StubBar

我知道这种技术在 Objective-C 中可以很好地工作,但它并没有让我觉得它是在动态语言中做的特别惯用的事情。我想知道我是否可以做任何棘手的事情,导致[[Bar alloc] init] 在我运行单元测试时返回StubBar,但在“现实生活”中给我Bar 的正常实现。

或者在这种情况下最适合使用明显的工厂模式?

【问题讨论】:

  • 我不会将单元测试代码与“现实生活”代码混为一谈,如果它们的行为不同,你怎么能确定你的测试是好的?
  • @JonathanCichon 我认为这在单元测试中很常见,不是吗?当你想测试一些特定的对象或功能时(比如本例中的Foo),由于不同的原因,你不能总是设置一个完整的测试环境。也许Foo 必须有一个与测试实际上无关的Bar 属性。
  • 正如我在下面评论的那样,如果它仅用于单元测试,我想避免添加编译到我的主要目标中的东西。
  • @DrummerB 在这种情况下,我需要 Bar 的行为可预测,这样我就可以测试通过 Foo 的所有代码路径,这些路径取决于 Bar 的行为方式。

标签: objective-c unit-testing factory-pattern idioms ocmockito


【解决方案1】:

您可以在init 中返回不同的对象。这就是为什么你应该将[super init] 的返回值分配给self。试试这样的:

@implementation Bar 

- (id)init {
    if (UNIT_TEST) {
        self = [[StubBar alloc] init];
        if (self) {
            // do unit test init here
        }
    } else {
        self = [super init];
        if (self) {
            // do regular init here
        }
    }
    return self;
}

...

@end

注意:这应该在 ARC 下工作。如果您不使用 ARC,请确保在分配新的 StubBar 实例之前释放 self


如果您想避免将与单元测试相关的代码编译到您的主要目标中:
@implementation Bar 

- (id)init {
#if UNIT_TEST
    self = [[StubBar alloc] init];
    if (self) {
        // do unit test init here
    }
#else
    self = [super init];
    if (self) {
        // do regular init here
    }
#endif
    return self;
}

...

@end

如果您想完全分离单元测试和真实代码,您可以只拥有两个不同版本的 Bar 类。一个用真实代码编译,另一个用单元测试目标编译。


您可以轻松地分配实例,而无需在编译时知道确切的类类型,如下所示:

id someBar = [[someClass alloc] init]; // assuming someClass is of type Class

或:

id someBar = [[NSClassFromString(@"Bar") alloc] init];

第一个更可取。您可以使用它来拥有一个可以在单元测试时更改的默认类类型。可以作为Foo 本身的属性,也可以作为您在单元测试时重新定义的预处理器宏。

【讨论】:

  • 虽然我可以看到这是如何工作的,但我不喜欢将单元测试的东西混合到我的真实代码库中。
  • @RobertAtkins 添加了更多建议。确实有很多方法可以做到这一点,我认为没有办法,而且正如你提到的那样,工厂也可以正常工作。
  • 我不明白在Bar 由被测类inside 的实现构造的情况下,我将如何实现您的最后一个建议。工厂在 Java 中为您解决的问题是 new 是一个静态调用,不能在运行时模拟出来。我的问题的要点是,“我可以在 Objective-C 中做一些聪明的事情来解决 [Bar alloc] 是静态调用的事实吗?”
  • 另外,我们设置单元测试的方式,单元测试目标取决于主目标,所以我认为两次定义类是行不通的。
  • 如何在 Java 中将工厂分配给 Foo?是静态变量吗?伊瓦尔?
【解决方案2】:

您正在寻找类似于OCMock 的内容。 它允许您从类、协议构建模拟对象,并且专门用于单元测试。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-16
    相关资源
    最近更新 更多