繁体   English   中英

TyphoonPatcher,用于单元测试中的模拟

[英]TyphoonPatcher for mocking in unit tests

我有Assembly

@interface MDUIAssembly : TyphoonAssembly

@property (nonatomic, strong, readonly) MDServiceAssembly *services;
@property (nonatomic, strong, readonly) MDModelAssembly *models;

- (id)choiceController;

@end

@implementation MDUIAssembly

- (void)resolveCollaboratingAssemblies
{
    _services = [TyphoonCollaboratingAssemblyProxy proxy];
    _models = [TyphoonCollaboratingAssemblyProxy proxy];
}

- (id)choiceController
{
    return [TyphoonDefinition withClass:[MDChoiceViewController class]
                          configuration: ^(TyphoonDefinition *definition) {
        [definition useInitializer:@selector(initWithAnalytics:diary:)
                        parameters: ^(TyphoonMethod *initializer) {
            [initializer injectParameterWith:[_services analytics]];
            [initializer injectParameterWith:[_models diary]];
        }];
    }];
}

@end

这是我要在测试中尝试做的事情:

- (void)setUp
{
    patcher = [TyphoonPatcher new];
    MDUIAssembly *ui = (id) [TyphoonComponentFactory defaultFactory];
    [patcher patchDefinition:[ui choiceController] withObject:^id{
       return mock([MDChoiceViewController class]);
    }];
    [[TyphoonComponentFactory defaultFactory] attachPostProcessor:patcher];
}

- (void) tearDown 
{
   [super tearDown];
   [patcher rollback];
}

不幸的是我setUp失败,下一条消息:

-[MDChoiceViewController key]: unrecognized selector sent to instance 0xbb8aaf0

我做错了什么?

您在台风方面遇到了一个糟糕的设计选择,但是有一个简单的解决方法。

您正在使用此方法:

[patcher patchDefinition:[ui choiceController] withObject:^id{
   return mock([MDChoiceViewController class]);
}];

这需要TyphoonDefinition作为参数。 引导台风时:

  • 我们从一个或多个TyphoonAssembly子类开始,Typhoon通过TyphoonAssembly子类来获取用于构建组件的配方。 然后将这些TyphoonAssembly子分类丢弃。
  • 现在,我们有了一个TyphoonComponentFactory ,它将允许您的任何TyphoonAssembly接口在它前面。 (这样一来,您可以拥有同一个类的多个配置,同时又避免了魔术字符串,并允许在IDE中自动完成等)。

编写TyphoonPatcher时,它是为您的测试准备一个新的TyphoonComponentFactory的情况而设计的(推荐),如下所示:

//This is an actual TyphoonAssembly not the factory posing as an assembly
MiddleAgesAssembly* assembly = [MiddleAgesAssembly assembly];

TyphoonComponentFactory* factory = [TyphoonBlockComponentFactory factoryWithAssembly:assembly];

TyphoonPatcher* patcher = [[TyphoonPatcher alloc] init];
[patcher patchDefinition:[assembly knight] withObject:^id
{
    Knight* mockKnight = mock([Knight class]);
    [given([mockKnight favoriteDamsels]) willReturn:@[
        @"Mary",
        @"Janezzz"
    ]];

    return mockKnight;
}];

[factory attachPostProcessor:patcher];
Knight* knight = [(MiddleAgesAssembly*) factory knight];

发生了什么:

所以,问题是, TyphoonPatcher期待TyphoonDefinitionTyphoonAssembly ,而是它正从一个实际元件TyphoonComponentFactory

非常令人困惑,应该弃用获取修补程序的方式。

解:

请改用以下内容:

[patcher patchDefinitionWithSelector:@selector(myController) withObject:^id{
     return myFakeController;
}];

这里是一些额外的建议以及主要答案。

单元测试与集成测试:

在台风中,我们遵循传统术语:

  • 单元测试 :与协作者隔离地测试您的班级。 在这里,您可以插入模拟或存根之类的测试双打来代替所有实际依赖项。

  • 集成测试:使用真正的协作者测试您的班级。 尽管您可能会打补丁我们的组件以使系统处于该测试所需的状态。

因此,任何使用TyphoonPatcher测试都可能是集成测试。

此处更多信息: 台风整合测试

解决协作程序集:

在早期版本的Typhoon中,这是必需的,但不再需要。 TyphoonAssembly的子类的任何属性都将被视为协作程序集。 删除以下内容:

- (void)resolveCollaboratingAssemblies
{
    _services = [TyphoonCollaboratingAssemblyProxy proxy];
    _models = [TyphoonCollaboratingAssemblyProxy proxy];
}

测试实例化他们自己的程序集:

我们建议测试实例化并在TyphoonComponentFactory上拆除它们。 优点是:

  • [TyphoonComponentFactory defaultFactory]是全局的,有一些缺点。
  • 集成测试可以定义自己的补丁程序,而不必担心将系统恢复到原始状态。
  • 除了使用TyphoonPatcher,如果您愿意,还可以创建一个程序集,其中某些零部件的定义将被覆盖。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM