2

我有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

我做错了什么?

4

2 回答 2

3

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

您正在使用此方法:

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

. . 这是期待一个TyphoonDefinition作为论点。引导台风时:

  • 我们从一个或多TyphoonAssembly个子类开始,Typhoon 使用这些子类获取构建组件的配方。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期望TyphoonDefinitionTyphoonAssemblya 得到一个实际的组件,而不是从TyphoonComponentFactory.

非常令人困惑,应该不推荐使用这种获取修补程序的方式。

解决方案:

请改用以下内容:

[patcher patchDefinitionWithSelector:@selector(myController) withObject:^id{
     return myFakeController;
}];
于 2014-07-25T23:17:49.707 回答
2

这里有一些额外的建议与主要答案一起使用。. .

单元测试与集成测试:

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

  • 单元测试:在与协作者隔离的情况下测试您的课程。在这里,您可以注入模拟或存根等测试替身来代替所有真正的依赖项。

  • 集成测试:使用真正的协作者测试您的课程。尽管您可以修补我们的组件以使系统处于该测试所需的状态。

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

更多信息:Typhoon 集成测试

解决协作程序集:

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

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

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

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

  • [TyphoonComponentFactory defaultFactory]是全球性的,有一些缺点。
  • 集成测试可以定义自己的补丁,而不必担心将系统恢复到原始状态。
  • 除了使用 TyphoonPatcher 之外,如果您希望可以创建一个组件,其中某些组件的定义被覆盖。
于 2014-07-25T23:24:11.263 回答