2012-02-18 19 views
3

有一条规则说你应该如何模拟你在Objective C/Cocoa中没有的类? (例如NSDate)

只有您拥有的模拟对象。

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

有什么选择?

当你需要使用NSDate的虚拟日期时呢?

在过去,我将日期方法从NSDate调到我自己的类--NSDateMock - 但有些事情告诉我这是错误的!

One Solution - Wrapper?

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

或者你会实施你正在使用的?这似乎是一个混乱的做法。

我的问题

什么是你不拥有如NSDate的嘲讽类的好办法吗?

更新1

我发现this article on mocking这似乎意味着,写一个瘦包装是要走的路。我不太清楚为什么,但我觉得这是一个黑客。再次,它可以使代码更具表现力。

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

更新2

已经有关于这个问题的一些很好的答案,但我仍然坚持寻找其他的答案 - 必须有这样的明确的方式,肯定?我仍然没有看到类别如何给我一个我可以控制的虚拟对象。

+1

只是好奇 - 为什么你想创建模拟日期?如果不了解你的设置,我的第一个想法是想知道为什么你需要首先嘲笑经过充分测试的系统课程。 – 2012-02-18 22:05:44

+0

@Tim如果一个应用程序正在处理时间敏感的操作,我可能想要模拟发生的事情,比如接近午夜。我将如何实现这一目标? – 2012-02-19 00:47:55

回答

3

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

我认为部分模拟是一种代码气味,因为它表明您的类正在测试(CUT)不可测试 - >重构时间。

我已经在过去的2种方式解决了这个:

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相同的对象,但有一个设置器允许您根据需要将行为更改为模拟提供程序。默认为真实。

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

简言之,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

您可以改为创建NSDate一个类别,如

[NSDate mockDate]; 

没有必要创建一个全新的类来实现的另一种方法。

+0

然后...调整模拟的真实日期方法?但是,我将如何指定模拟日期应该返回的内容 - 类别不能有实例变量... – 2012-02-19 01:33:07

+0

似乎很脏,但是您可以在任何地方使用自己的[NSDate mockDate],并且测试用例只是返回您自己的日期。在所有其他情况下,它应该简单地返回'[NSDate date]' – fscheidl 2012-02-19 02:40:32

+0

我不明白这将如何工作。 [NSDate mockDate]在任何地方都意味着我仍然不得不交换一个返回我自己的日期的方法,这意味着我仍然必须调整(我试图避免) - 肯定会增加额外的复杂性只是调整真正的日期方法而没有真正的回报?加上我的代码将散布与[NSDate mockDate]这是混乱! – 2012-02-19 13:25:45

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... 
} 
+0

从我的例子应用程序:我有用户花费在文档上的日志条目。计时器类具有启动和停止方法。我想模拟在午夜之前启动计时器时发生的情况,并在测试后立即停止计时。还有更多潜在的例子 - 任何一类的行为取决于当前的时间。 – 2012-02-19 09:46:05

+0

增加了一些示例代码。 – 2012-02-20 18:33:54

+0

但是这取决于被注入时间的待测方法。所以我想你的解决方案是“传入的对象是你不拥有的类的实例” – 2012-02-21 00:36:12

1

我不知道,如果这得到什么你正在尝试做的,但是当我碰到类似的情况我已经做了类似如下:

  1. 每当我有一个类,取决于我可能想稍后模拟出的一些类, 我在我的类的一个方法中封装了对该类的访问。例如,我将调用[NSDate date]来获取当前时间,而不是调用 ,而是实现一种名为currentTime的方法。 该方法的实现将简单地返回[NSDate时间]。

  2. 我执行我的测试针对从我的对象创建“部分模拟”测试物, 捻熄currentTime的方法返回我的首选测试值,而不是让 从得到调用真正的currentTime的实现。

我使用OCMock框架做这一切,所以它看起来是这样的:

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

[mockTestObject doSomethingThatUsersCurrentTime]; 
[mockTestObject verify]; 

OCMock提供了很多不同的变种,对捻熄,我发现它工作得很好对于这种事情。