有一条规则说
仅模拟您拥有的对象。
我想我理解这个的原因 - 框架提供的模拟类可能会导致奇怪的行为。
有什么替代方案?
使用NSDate何时需要虚拟日期?
过去我把日期方法从NSDate调到我自己的类 - NSDateMock - 但有些东西告诉我这是非常错误的!
一个解决方案 - 包装器?
创建一个围绕NSDate的包装器,但是你必须实现它的所有方法。
或者你会实施你正在使用的那些?这似乎是一种混乱的方式。
我的问题
有什么方法可以模拟你不拥有的类,例如NSDate?
更新1
我发现this article on mocking似乎意味着编写一个薄的包装器是可行的方法。我不太清楚为什么,但我觉得这是一个黑客。然后,它可以使代码更具表现力。
但这提出了一个问题,在NSDate的情况下,你是否将包装类注入需要知道日期的每个类?!当然不是......
更新2
在这个问题上已经有了一些很好的答案,但我仍然坚持要求其他答案 - 必须有一种确定的方法来做到这一点,当然?我仍然没有看到类别如何给我一个我可以控制的虚拟对象。
答案 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)
我不确定这是否会影响到你想要做的事情,但是当我遇到类似的情况时,我做了类似以下的事情:
每当我有一个依赖于某些课程的课程时,我可能想稍后嘲笑, 我在我的类的方法中封装了对该类的访问。例如,而不是 调用[NSDate date]来获取当前时间,我将实现一个名为currentTime的方法。 该方法的实现只返回[NSDate time]。
我针对从我的测试对象创建的“部分模拟”对象执行我的测试, 将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为存根提供了许多不同的变体,我发现它对这种事情非常有效。
答案 4 :(得分:0)
示例:模拟 NSDate:timeIntervalSince1970
以返回已知值
远离基于意见的响应,专注于解决方案,假设您有以下调用要控制以返回已知值:
[[NSDate new] timeIntervalSince1970]]
...您可以模拟 new
方法以返回您想要的任何内容,包括 nil
†。
以下示例首先创建了一个带有 42 的 NSDate
,用于 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
!仅模拟您拥有的对象。