在Objective C中进行BDD时,最好的模仿协作者的方法是什么?

时间:2012-02-18 15:58:17

标签: objective-c cocoa mocking bdd

我一直在使用Objective C和Cocoa / iOS,并且像个模仿者一样进行测试。 (Definition

  • 我想使用OCMock模拟对象的协作者。
  • 在Objective C中有两种方法可以做到这一点我知道:

    1. 依赖注入
    2. 设置内部状态 - 通过Accessors或setValue:forKey:

我应该使用哪种方式?

我不喜欢其中任何一个。但是我必须使用一个...除非有其他选择我不知道。

1。依赖注入

这会使我的代码变得混乱,特别是当SUT有2/3个合作者时。如果SUT需要传递1/2参数,事情看起来确实非常混乱。

我明白,除了3之外,还有太多的依赖关系,对象应该被分成其他部分......但即使有2个依赖关系和1个参数,它仍然像罪一样难看。

2。设置内部状态

这与课堂内部混淆 - 我认为这是测试中的一个大禁忌。

访问者肯定是出局的 - 他们会公开没有人应该知道的数据。 我可以使用setValue:forKey:...但这感觉就像一个可怕的黑客。

这也意味着我必须初始化SUT然后将真正的协作者替换为模拟协作者,然后运行测试中的方法,这感觉很乱。

我的问题

在做BDD时,在Objective 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 

1 个答案:

答案 0 :(得分:0)

我们采用的方法是让合作者单身,并提供一种在测试时注入模拟实例的方法:

static JGCommandFinder *sharedFinder = nil;

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

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

它非常灵活,因为您可以使用实际对象,即使真实对象已经初始化也会注入模拟,并通过将实例设置为nil来重置它以使用真实对象。