TyphoonPatcher在unit testing中进行模拟
我有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
我做错了什么?
这里有一些额外的建议,以配合主要答案。 。 。
unit testing与集成测试:
在台风中,我们坚持传统的术语:
-
unit testing :与合作者隔离测试您的课程。 这是您注入测试双打的地方,如模拟或存根,而不是所有真正的依赖项。
-
集成测试:使用真正的协作者测试您的课程。 虽然您可以修补我们的组件,以便将系统置于该测试所需的状态。
所以任何使用TyphoonPatcher
测试都可能是集成测试。
更多信息: 台风集成测试
解决协作assembly:
这在早期版本的Typhoon中是必需的,但不再需要。 任何属于TyphoonAssembly的子类的属性都将被视为协作程序集。 删除以下内容:
- (void)resolveCollaboratingAssemblies { _services = [TyphoonCollaboratingAssemblyProxy proxy]; _models = [TyphoonCollaboratingAssemblyProxy proxy]; }
测试实例化自己的程序集:
我们建议测试实例化并拆除他们的TyphoonComponentFactory。 优点是:
-
[TyphoonComponentFactory defaultFactory]
是一个全局性的并且有一些缺点。 - 集成测试可以定义自己的补丁,而不必担心将系统恢复到原始状态。
- 除了使用TyphoonPatcher之外,如果您希望可以创建一个组件,其中某些组件的定义被覆盖。
你在Typhoon的部分遇到了糟糕的设计选择,但有一个简单的解决方法。
你正在使用这种方法:
[patcher patchDefinition:[ui choiceController] withObject:^id{ return mock([MDChoiceViewController class]); }];
。 。 期待TyphoonDefinition
作为参数。 在引导台风时:
- 我们从一个或多个
TyphoonAssembly
子类开始,Typhoon仪器用于获取构建组件的配方。 然后丢弃这些TyphoonAssembly
子clases。 - 我们现在有一个
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
期待TyphoonPatcher
中的TyphoonDefinition
,而是从TyphoonAssembly
获得一个实际的组件。
非常混乱,并且应该弃用获取修补程序的方式。
解:
请改用以下内容:
[patcher patchDefinitionWithSelector:@selector(myController) withObject:^id{ return myFakeController; }];