您提供可能會有點混亂,因爲很明顯,不留任何有意義的mobileTest
的單元測試,測試,不過考慮到你還可能會質疑爲什麼簡單NSLog
呼叫外包給另一個類中的例子第一個地方(並且測試NSLog
電話當然是毫無意義的)。當然,我明白你只是以此爲例,並設想一個更復雜的不同場景,在這種場景中你要確保發生特定的呼叫。
在這種情況下,你應該總是問自己「這是爲了驗證這個正確的地方嗎?」如果答案是肯定的,那應該暗示你想要測試的任何消息被調用需要去一個不完全在被測試類範圍內的對象。例如,它可能是一個單例(某些全局日誌類)或該類的一個屬性。然後你有一個句柄,一個可以正確模擬的對象(例如,將該屬性設置爲部分模擬對象)並進行驗證。
在極少數情況下,可能會導致您爲對象提供句柄/屬性,只是爲了能夠在測試過程中用模擬替換它,但這通常表示次優類和/或方法設計(I'儘管如此,我不會認爲情況總是如此)。
讓我提供了從我的項目之一三個例子來說明類似的情況了一句:
例1:驗證一個URL打開(在移動Safari瀏覽器):這基本上驗證openURL:
是調用共享的NSApplication
實例,這與您的想法非常相似。請注意,共享實例是不是「完全的測試方法的範圍內」,因爲它是一個單獨的」
id mockApp = OCMPartialMock([UIApplication sharedApplication]);
OCMExpect([mockApp openURL:[OCMArg any]]);
// call the method to test that should call openURL:
OCMVerify([mockApp openURL:[OCMArg any]]);
注意這是由於部分模擬的細節:即使openURL:
不叫上模擬因爲模擬具有在被測試的方法來同一個實例的關係它仍然可以驗證該呼叫。如果實例不是那就沒辦法了單身,你將無法創建一個從模擬在您的方法中使用的相同對象
示例2:改編版本的代碼以允許「gra bbing「內部對象。
@interface HelloWorldClass
@property (nonatomic, strong) AnotherClass *lazyLoadedClass;
@end
@implementation HelloWorldClass()
// ...
// overridden getter
-(AnotherClass *)lazyLoadedClass {
if (!_lazyLoadedClass) {
_lazyLoadedClass = [[AnotherClass alloc] init];
}
return _lazyLoadedClass;
}
-(void)mobileTest{
[self.lazyLoadedClass makeNoise];
}
@end
而現在的測試:
-(void)testMobileTest{
HelloWorldClass *helloWorldObject = [[HelloWorld alloc] init];
id mockObject = OCMPartialMock([helloWorldObject lazyLoadedClass]);
OCMExpect([mockObject makeNoise]);
[helloWorldObject mobileTest];
OCMVerify([mockObject makeNoise]);
}
的lazyLoadedClass
方法,甚至可能在類延伸,即 「私人」。在這種情況下,只複製根據類別定義,測試文件的頂部(我通常這樣做,是的,這就是,IMO,基本上「測試私有方法」有效的情況下)。如果AnotherClass
更復雜,需要精心設置或其他方法,這種方法纔有意義。通常像這樣的東西,然後導致你在第一個地方有情況,即它的複雜性,使得它不僅僅是一個幫手比可以在方法完成之後被扔掉了。這也會使你得到更好的代碼結構,因爲你的初始化器在一個單獨的方法中,並且可以相應地測試它。
例3:如果AnotherClass
有一個非標準的初始化(如單身,或者它來自一個工廠類),您可以存根,並返回一個嘲笑對象(這是怎樣的一個大腦結的,但我已經用它)
@implementation AnotherClass()
// ...
-(AnotherClass *)crazyInitializer { // this is in addition to the normal one...
return [[AnotherClass alloc] init];
}
@end
-(void)testMobileTest{
HelloWorldClass *helloWorldObject = [[HelloWorld alloc] init];
id mockForStubbingClassMethod = OCMClassMock([AnotherClass class]);
AnotherClass *baseForPartialMock = [[AnotherClass alloc] init];
// maybe do something with it for test settup
id mockObject = OCMPartialMock(baseForPartialMock);
OCMStub([mockForStubbingClassMethod crazyInitializer]).andReturn(mockObject);
OCMExpect([mockObject makeNoise]);
[helloWorldObject mobileTest];
OCMVerify([mockObject makeNoise]);
}
這看起來挺傻的,我承認這是醜陋的,但我已經在一些測試中(你知道一個項目,點...)用這個。在這裏,我試圖使它更易於閱讀和使用的兩個嘲笑,一個存根類方法(即初始化),另一個則返回。然後mobileTest
方法顯然應當使用AnotherClass
定製初始化,然後它會嘲笑對象(如布穀鳥的蛋...)。如果您想專門準備對象,這非常有用(這就是爲什麼我在這裏使用了部分模擬的原因)。我實際上並不確定atm是否也可以只用一個類模擬來完成這個工作(在它上面存根類方法/初始化程序,所以它會自動返回,然後期望你想驗證的方法調用)......正如我所說,打結。