2012-02-18 55 views
0

我一直在使用Objective C和Cocoa/iOS并像测试者一样进行测试。 (Definition在ObjectiveC中做BDD时嘲笑合作者的最好方法是什么?

  • 我想嘲笑使用OCMock的对象的协作者。
  • 有目标C做这两种方式我所知道的:

    1. 依赖注入
    2. 设置内部状态 - 无论是通过访问器或setValue方法:forKey:

我应该使用哪一种?

我不喜欢这些。但我必须使用一个...除非我还没有其他选项。

1.依赖注入

这杂波我的代码,尤其是当SUT有2/3的合作者。如果SUT需要通过1/2参数,事情看起来确实非常混乱。

我明白,远远超过3,有太多的依赖关系,对象应该分成其他部分...但即使有2个依赖关系和1个参数,它仍然是丑陋的罪过。

2.设置内部状态

这打乱了类的内部 - 我觉得这是一个很大的禁忌测试。

访问者绝对不在 - 他们暴露的数据是没有人应该知道的。 我可以使用setValue:forKey:...但这感觉像一个可怕的黑客。

这也意味着我必须初始化SUT,然后换出真正的模拟合作伙伴,然后运行测试中的方法,这感觉很混乱。

我的问题

什么是做BDD时,模拟出的目标C合作者的最好方法是什么?

代码

惩戒使用的setValue:forKey:

@interface JGCompositeCommand : JGCommand <JGCompositeCommandProtocol> { 
    NSMutableArray *commands; 
    JGCommandFinderFactory *commandFinderFactory; 
} 

-(id <JGCommandProtocol>)initWithName:(NSString *)name_ recoverer:(id <JGCommandRecoveryProtocol>)recoverer_ executor:(id <JGCommandExecutorProtocol>)executor_; 

@end 

@implementation JGCompositeCommand 

-(id)initWithName:(NSString *)name_ recoverer:(id)recoverer_ { 
    self = [super initWithName:name_ recoverer:recoverer_]; 
    if (self) { 
     commands = [NSMutableArray array]; 
     commandFinderFactory = [[JGCommandFinderFactory alloc] init]; 
    } 
    return self; 
} 

-(id <JGCommandProtocol>)commandWithName:(NSString *)name_ { 
    return [[commandFinderFactory commandFinderWithCommandName:name_ andCommands:commands] findCommandWithName]; 
} 

@end 

@interface JGCommandTestCase : SenTestCase { 
    JGCompositeCommand *compositeCommand; 
    OCMockObject *commandFinderFactoryMock; 
} 

@end 

@implementation JGCommandTestCase 

-(void)setUp { 
    [super setUp]; 
    compositeCommand = [[JGCompositeCommand alloc] initWithName:@"" recoverer:nil]; 
    commandFinderFactoryMock = [OCMockObject mockForClass:[JGCommandFinderFactory class]]; 
    // Hack alert! Ugh. 
    [compositeCommand setValue:commandFinderFactoryMock forKey:@"commandFinderFactory"]; 
} 

-(void)testGivenCommandNotFoundShouldThrow { 
    // ** Setup ** 
    [[[commandFinderFactoryMock expect] andReturn:...] commandFinderWithCommandName:... andCommands:...]; 

    // ** Execute ** 
    [compositeCommand commandWithName:@"Blah"]; 

    // ** Asserts ** 
    [commandFinderFactoryMock verify]; 
} 

@end 

回答

0

我们的就是让合作者单身,并提供了一种方法,在注入一个模拟实例降落的方法测试时间:

static JGCommandFinder *sharedFinder = nil; 

+(JGCommandFinder *)sharedFinder { 
    if (sharedFinder == nil) sharedFinder = [[JGCommandFinder alloc] init]; 
    return sharedFinder; 
} 

+(void)setSharedFinder:(JGCommandFinder *)instance { 
    sharedFinder = instance; 
} 

这是非常灵活的,因为你可以使用实际的目标t,即使已经初始化了一个真正的对象,也要注入一个模拟对象,并通过将实例设置为nil来重置它以使用真实对象。

+0

我很困惑 - 当然,这意味着每一位合作者都是单身?这肯定意味着大多数班级都变成了单身人士。我认为一个单身人士是一个反模式。 – 2012-02-19 10:17:08

+0

我想这取决于你的意思是“每一位合作者”。当然还有其他的获取和配置对象的模式。您选择的取决于您的需求。例如,笔尖是依赖注入的一种形式,所以在笔尖中配置的任何东西都可以在测试时注入。至于单身人士作为反模式,我认为主要的批评是他们很难作为协作者测试,这种方法试图解决这个问题。看看http://blog.securemacprogramming.com/2011/02/on-singletons/。 – 2012-02-20 18:05:33