如何存根/注入视图控制器以在 iOS 5 运行时进行基于状态的测试?

Posted

技术标签:

【中文标题】如何存根/注入视图控制器以在 iOS 5 运行时进行基于状态的测试?【英文标题】:How to stub out / inject a view controller for state based testing on the iOS 5 runtime? 【发布时间】:2012-03-17 20:33:45 【问题描述】:

我正在寻找一种“最佳实践”/“低测试摩擦”的方法来对我的基础 AppDelegate 类中的视图控制器进行基于状态的测试。目前,以下提供了一种简单的方法来存根我自己的 UIViewController(使用 ocmock),当它在类的方法中发生某些事情时。

-(FirstViewController *)getFirstViewController

    if (self.viewController1)
     return self.viewController1; 

    self.viewController1 = [[FirstViewController alloc] initWithNibName:@"FirstViewController" bundle:nil];

    return self.viewController1;

我的第一个问题 - 这是一种有效的方法来存根/注入我自己的模拟视图控制器进行测试吗? (似乎效果很好,但我不确定这是否是专业人士今天进行基于状态的测试的方式)

我的下一个问题 - 像这样在内存中保留 1 个视图控制器副本是否有效(在应用程序的生命周期内只从头开始创建一次)?

**注意-我会依赖注入它,但我的 init 已经足够大,只需注入导航控制器和标签栏控制器,因此遗憾的是,这不是这个大型类的选项

【问题讨论】:

【参考方案1】:

如果它是根视图控制器,则应将其设为应用委托的属性:

@interface MyAppDelegate : NSObject <UIApplicationDelegate>
@property(retain)FirstViewController *firstViewController;
@end

@implementation MyAppDelegate
@synthesize firstViewController;
...
@end

除非您正在测试的方法是您初始化firstViewController 的方法,否则您不需要任何类型的延迟加载方法。您只需在测试中获取应用委托,创建FirstViewController 的实例并将其分配给您的委托的属性,然后定义测试:

-(void)testSomething 
    MyAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
    FirstViewController *firstViewController = [[FirstViewController alloc] init];
    appDelegate.firstViewController = firstViewController;

    // test some app delegate method
    ...

如果你想模拟你正在测试的控制器,你也可以这样做:

-(void)testSomething 
    MyAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
    id mockController = [OCMockObject mockForClass:[FirstViewController class]];
    appDelegate.firstViewController = mockController;

    [[mockController expect] someControllerMethod];

    // test some app delegate method
    ...

    [mockController verify];

【讨论】:

【参考方案2】:

依赖注入不需要你通过 init 方法注入所有的依赖。首选是有原因的,但这是另一个讨论。

您可以简单地为您的类添加一个 -setFirstViewController: 方法。您将在测试中使用该方法来注入您的模拟。如果您不喜欢在您的应用中使用该方法,您可以使用测试代码中的类别添加该方法。

【讨论】:

公平点-但是如果我删除此方法并进行基本的 setter 注入,那么在我关于何时在 AppDelegate 中新建这些依赖项的问题的第二部分中,这将如何发挥作用? 您仍然可以在上面包含的 getter 中保留惰性初始化。只需确保在您的测试中,在第一次调用 getter 之前调用我建议的 setter。【参考方案3】:

对于这种测试,我会让它像你一样,嗯,略有不同。

第一视图控制器的延迟加载被封装在一个属性中。

在.h文件中

@interface AppDelegate 
    FirstViewController *viewController1_;

然后

@property (nonatomic, readonly) FirstViewController viewController1;

在 .m 文件中

- (FirstViewController *)viewController1 
    if (!viewController1_) 
        viewController1_ = [[FirstViewController alloc] initWithNibName:@"FirstViewController" bundle:nil];
    

    return viewController1_;

2nd- 如果我想注入一个模拟对象,我在我的测试代码中使用 KVC

[appDelegateUnderTest setValue:mockViewController forKey:@"viewController1_"];

问候,

【讨论】:

以上是关于如何存根/注入视图控制器以在 iOS 5 运行时进行基于状态的测试?的主要内容,如果未能解决你的问题,请参考以下文章

重写 iOS 应用程序以在 iOS 和 Mac 上运行——如何组织控制器代码?

如何将依赖项注入 iOS 视图控制器?

如何从视图控制器发送数据以在 swift 5 中嵌入视图控制器?

iOS - 从一个视图控制器传递数据以在另一个视图控制器的 init 方法中使用

iOS:滑动手势以在同一控制器中加载不同的数据

如何按下 tableview 单元格以在导航控制器中显示带有文本的视图控制器