你应该如何模拟Objective C / Cocoa中你不拥有的类? (例如NSDate)

时间:2012-02-18 18:42:42

标签: objective-c mocking

有一条规则说

  

仅模拟您拥有的对象。

我想我理解这个的原因 - 框架提供的模拟类可能会导致奇怪的行为。

有什么替代方案?

使用NSDate何时需要虚拟日期?

过去我把日期方法从NSDate调到我自己的类 - NSDateMock - 但有些东西告诉我这是非常错误的!

一个解决方案 - 包装器?

创建一个围绕NSDate的包装器,但是你必须实现它的所有方法。

或者你会实施你正在使用的那些?这似乎是一种混乱的方式。

我的问题

有什么方法可以模拟你不拥有的类,例如NSDate?

更新1

我发现this article on mocking似乎意味着编写一个薄的包装器是可行的方法。我不太清楚为什么,但我觉得这是一个黑客。然后,它可以使代码更具表现力。

但这提出了一个问题,在NSDate的情况下,你是否将包装类注入需要知道日期的每个类?!当然不是......

更新2

在这个问题上已经有了一些很好的答案,但我仍然坚持要求其他答案 - 必须有一种确定的方法来做到这一点,当然?我仍然没有看到类别如何给我一个我可以控制的虚拟对象。

5 个答案:

答案 0 :(得分:3)

一般来说,这是一个“如何模拟静态/类级方法”的问题,谷歌搜索显示了很多不同的想法,所以它主要归结为品味。

我认为部分嘲笑是代码气味,因为它表明你的被测试类(CUT)是不可测试的 - >重构的时间。

我过去曾用两种方式解决这个问题:

1。)将DateProvider(这是一个接口)传递给CUT的构造函数

interface DateProvider 
    date timenow()
end

在测试时,这是您的MockDateProvider,您可以从测试类更改状态。我可能会使用我可以在测试中更改的公共静态字段

class MockDateProvider :: DateProvider
   public static field fakeDate   
   date timenow () 
      return fakedate
   end
end   

在真实系统中,它只有您使用的日期创建方法。

class RealDateProvider :: DateProvider
   date timenow()
      return LibraryDateMaker.newDate()
   end
end

我可能会创建2个构造函数,一个使用此接口,另一个使用RealDateProvider而不需要传入任何生产类。

这是最好的OO做事方式。我想!

2.。制作您自己的静态日期提供程序,您可以覆盖。

的行为

而不是LibraryDateMaker.newDate(),而是使ConfigurableDateMaker.newDate()静态。它使用与1相同的对象,但有一个setter,允许您根据需要将行为更改为模拟提供程序。默认为真实。

这样做的好处是您不必将任何内容传递给构造函数,并且可以继续使用静态方法进行非常常见的活动。

简而言之,CUT调用ConfigurableDateMaker.newDate()。默认情况下返回实际日期,但在测试类中,您可以设置行为以在调用CUT之前使用模拟。

class ConfigurableDateMaker
  public static DateProvider provider
   static date timenow()
      return provider.newDate()
   end
   // add the 2 provider classes as inner classes in here
end

希望这是有道理的。

答案 1 :(得分:1)

您可以改为创建NSDate类别并实施其他方法,例如

[NSDate mockDate];

无需创建全新的课程。

答案 2 :(得分:1)

您试图通过模仿NSDate来解决哪些具体问题?通常,永远不必模拟像NSDate这样的值对象。它的界面提供了创建表示任何给定日期/时间的对象的简单方法。

您可以在测试类中创建一个方便的方法来生成所需的日期:

-(NSDate *)dateFromString:(NSString *)dateString {
    // short style is 2/20/12 10:05 AM
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    formatter.dateStyle = NSDateFormatterShortStyle;
    formatter.timeStyle = NSDateFormatterShortStyle;
    return [formatter dateFromString:dateString];
}

-(void)testTimer {
    NSDate *startTime = [self dateFromString:@"2/20/12 11:58 PM"];
    NSDate *endTime = [self dateFromString:@"2/21/12 12:02 AM"];
    // pass dates into your timer method
}

或者,如果您需要测试直接获取当前时间的方法,则可以在[NSDate date]周围提供一个瘦包装:

-(NSDate *)now {
    return [NSDate date];
}

-(void)startTimer {
    self.startTime = [self now];
}

-(void)stopTimer {
    NSDate *endTime = [self now];
    // ... do something 
}

然后,在您的测试中,您使用部分模拟来模拟您的瘦包装器:

-(void)testStopTimer {
    Timer *timer = [[Timer alloc] init];
    timer.startTime = [self dateFromString:@"2/20/12 11:58 PM"];

    id mockTimer = [OCMockObject partialMockForObject:timer];
    [[[mockTimer stub] andReturn:[self dateFromString:@"2/21/12 12:01 AM"] now];

    [timer stopTimer];

    // verify expected behavior...
}

答案 3 :(得分:1)

我不确定这是否会影响到你想要做的事情,但是当我遇到类似的情况时,我做了类似以下的事情:

  1. 每当我有一个依赖于某些课程的课程时,我可能想稍后嘲笑, 我在我的类的方法中封装了对该类的访问。例如,而不是 调用[NSDate date]来获取当前时间,我将实现一个名为currentTime的方法。 该方法的实现只返回[NSDate time]。

  2. 我针对从我的测试对象创建的“部分模拟”对象执行我的测试, 将currentTime方法存根以返回我的首选测试值而不是允许 来自被调用的真实currentTime实现。

  3. 我正在使用OCMock框架来完成所有这些,所以它看起来像:

    NSDate *testDate = // put whatever you want here
    id mockTestObject = [OCMock partialMockForObject:testObject];
    [mockTestObject stub] andReturn:testDate] currentDate];
    
    [mockTestObject doSomethingThatUsersCurrentTime];
    [mockTestObject verify];
    

    OCMock为存根提供了许多不同的变体,我发现它对这种事情非常有效。

答案 4 :(得分:0)

问:如何模拟一个你不拥有的类?

A:就像您拥有的课程一样。

示例:模拟 NSDate:timeIntervalSince1970 以返回已知值

远离基于意见的响应,专注于解决方案,假设您有以下调用要控制以返回已知值:

[[NSDate new] timeIntervalSince1970]]

...您可以模拟 new 方法以返回您想要的任何内容,包括 nil
以下示例首先创建了一个带有 42NSDate,用于 OCMStub

// Prepare a known date
NSDate * life = [NSDate dateWithTimeIntervalSince1970:42];

// Mock the entire NSDate class, and stub `new`
id mockDate = OCMClassMock([NSDate class]);
OCMStub([mockDate new]).andReturn(life);

NSLog(@"%f",[[NSDate new] timeIntervalSince1970]); // "42.000000"

[mockDate stopMocking];

注意事项

  • 不要忘记stopMocking
  • () 这可能就是为什么建议 <块引用>

    仅模拟您拥有的对象。