成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

玩轉(zhuǎn) Objective-C 的 Mock 對(duì)象

Jason / 3378人閱讀

摘要:測(cè)試期間,模擬對(duì)象會(huì)記錄你發(fā)送給它的每一條消息。這種使用模擬對(duì)象的模式叫做謙卑對(duì)象。這意味著需要所有的文物清單,并對(duì)這些對(duì)象做一些測(cè)試,而我們所做的是使方法與庫存對(duì)象進(jìn)行溝通。這個(gè)測(cè)試類也會(huì)影響文物數(shù)據(jù)模型的設(shè)計(jì)。

測(cè)試驅(qū)動(dòng)開發(fā)(TDD)中,開發(fā)者經(jīng)常使用模擬對(duì)象進(jìn)行系統(tǒng)設(shè)計(jì),模擬對(duì)象到底是什么呢?部分模擬對(duì)象和全部模擬對(duì)象又是什么呢?模擬對(duì)象真的讓人又愛又恨嗎?讓我們以O(shè)bjective-C測(cè)試框架OCMock來探個(gè)究竟。

模擬對(duì)象設(shè)計(jì)

模擬對(duì)象可以解決兩種問題。第一種是(它們也是因此而提出的)用于設(shè)計(jì)測(cè)試驅(qū)動(dòng)開發(fā)的測(cè)試類。想象一下,你已經(jīng)完成了第一個(gè)測(cè)試,并知道了一些關(guān)于第一個(gè)類的API的信息。你的測(cè)試調(diào)用了新類的方法,你知道,應(yīng)該從它們協(xié)作者之一種抓取一些信息。問題是,協(xié)作者尚不存在,而你又不想放棄這個(gè)已經(jīng)設(shè)計(jì)出來的并開始測(cè)試的類。

此時(shí),你可以創(chuàng)建一個(gè)模擬對(duì)象代表這個(gè)尚未“出生”的協(xié)作者。你可以設(shè)定你想要通過該“協(xié)作者”測(cè)試調(diào)用對(duì)象的期望值,而且,如果需要的話,還可以返回一個(gè)可以測(cè)試控制的值。你的測(cè)試可以驗(yàn)證你所期望調(diào)用的方法是否真的被調(diào)用了,如果沒有,則測(cè)試失敗。

在這種情況下,模擬對(duì)象就像一臺(tái)VCR,只是沒有上世紀(jì)八十年代的矮胖的造型和易受損的磁帶。測(cè)試期間,模擬對(duì)象會(huì)記錄你發(fā)送給它的每一條消息。然后,可以通過重放與消息列表做比較來看是不是你所需要的。就像用VCR,如果你想要看的是小精靈2(Gremlins 2),但是記錄的卻是上半年的新聞和歡樂酒店(Cheers),這就讓人較為失望。

關(guān)鍵的部分是,你實(shí)際上并不需要建立真正的協(xié)作對(duì)象。事實(shí)上,你完全不需要關(guān)心它是怎么實(shí)施的。唯一需要關(guān)注的是它需要返回的消息,這樣就可以驗(yàn)證他們是否被發(fā)送了。實(shí)際上,模擬對(duì)象可以讓你覺得說,“我知道,在某些時(shí)候,我會(huì)考慮這一點(diǎn),但我不希望因此而分心?!?對(duì)于測(cè)試驅(qū)動(dòng)開發(fā)者,這就像一個(gè)待辦事項(xiàng)清單一樣清晰。

讓我們來看一個(gè)例子。假設(shè)書呆子Ranch發(fā)現(xiàn)了市場(chǎng)上對(duì)博物館庫存管理App的需求。通常博物館收藏了大量的文物,他們需要了解所有的庫存,并能按主題,國家,年代等在畫廊組織展覽。關(guān)于庫存的需求類似如下:

  

作為策展人,我想知道所有需要展出的文物,這樣我就可以給我的游客們講故事了。

我會(huì)寫一個(gè)可以提供一個(gè)所有文物的清單的庫存類用來測(cè)試。當(dāng)然,磁盤上還有其他類也存儲(chǔ)了所有的文物,但是我不關(guān)心他們是如何工作的,我只要?jiǎng)?chuàng)建一個(gè)庫存接口的模擬對(duì)象。我的測(cè)試類如下:

@implementation BNRMuseumInventoryTests

- (void)testArtefactsAreRetrievedFromTheStore

{

    //Assemble

    id store = [OCMockObject mockForProtocol:@protocol(BNRInventoryStore)];

    BNRMuseumInventory *inventory = [[BNRMuseumInventory alloc] initWithStore:store];

    NSArray *expectedArtefacts = @[@"An artefact"];

    [[[store expect] andReturn:expectedArtefacts] fetchAllArtefacts];

    //Act

    NSArray *allArtefacts = [inventory allArtefacts];

    //Assert

    XCTAssertEqualObjects(allArtefacts, expectedArtefacts);

    [store verify];

}

@end

為了讓這個(gè)類編譯通過,我需要?jiǎng)?chuàng)建BNRMuseumInventory類和它的initWithStore:和allArtefacts方法。

@interface BNRMuseumInventory : NSObject

- (id)initWithStore:(id )store;

- (NSArray *)allArtefacts;

@end

@implementation BNRMuseumInventory

- (id)initWithStore:(id )store

{

return nil;

}

- (NSArray *)allArtefacts

{

return nil;

}

@end

我還要定義BNRInventoryStore協(xié)議及其-fetchAllArtefacts方法,但我現(xiàn)在還不需要實(shí)現(xiàn)它們。為什么要我將它定義為一個(gè)協(xié)議,而不是另一個(gè)類?是為了提高靈活性:我知道我想發(fā)送給BNRInventoryStore的消息,但我并不需要關(guān)心它是如何處理這些消息的。使用協(xié)議能讓我靈活的處理實(shí)現(xiàn)存儲(chǔ)的方法:只要它能響應(yīng)我所關(guān)心的消息,它可以是任何類型的類。

@protocol BNRInventoryStore 

- (NSArray *)fetchAllArtefacts;

@end

現(xiàn)在有足夠的信息讓編譯器來編譯和運(yùn)行測(cè)試,但它還是不能通過。

Test Case "-[BNRMuseumInventoryTests testArtefactsAreRetrievedFromTheStore]" started.

/Users/leeg/BNRMuseumInventory/BNRMuseumInventory Tests/BNRMuseumInventoryTests.m:91: error: -[BNRMuseumInventoryTests testArtefactsAreRetrievedFromTheStore] : ((allArtefacts) equal to (expectedArtefacts)) failed: ("(null)") is not equal to ("(

"An artefact"

)")

:0: error: -[BNRMuseumInventoryTests testArtefactsAreRetrievedFromTheStore] : OCMockObject[BNRInventoryStore]: expected method was not invoked: fetchAllArtefacts

// snip more output

在測(cè)試中斷言檢測(cè)到期待的文物集合并未返回,fetchAllArtefacts方法沒有被調(diào)用,模擬對(duì)象驗(yàn)證失敗。只有修復(fù)這兩個(gè)問題,我們才可以通過測(cè)試。

@implementation BNRMuseumInventory

{

id  _store;

}

- (id)initWithStore:(id )store

{

self = [super init];

if (self)

{

_store = store;

}

return self;

}

- (NSArray *)allArtefacts

{

return [_store fetchAllArtefacts];

}

@end
模擬一體化

第二種使用模擬對(duì)象的方法是使用外部代碼,如蘋果的框架或第三方庫,進(jìn)行一體化。模擬對(duì)象可以簡(jiǎn)化使用框架所帶來的復(fù)雜性,因?yàn)闇y(cè)試并不需要搭建一個(gè)成熟的環(huán)境,只需確保我們的應(yīng)用程序能連接到該環(huán)境中的一小部分。這種使用模擬對(duì)象的模式叫做謙卑對(duì)象(Humble Object)。

繼續(xù)VCR的比喻,我們并沒有設(shè)計(jì)一個(gè)與框架交互的類,但我們要檢查我們是否遵守了他們規(guī)定的規(guī)則。就像了你買了一臺(tái)VHS錄像機(jī),但你不需要知道磁帶的類型,你只能使用VHS錄像帶,因?yàn)檫@是廠家規(guī)定的。同樣的,我們可以告訴我們的模擬對(duì)象,期望值是VHS磁帶,所以如果我們給它一個(gè)錄像帶Betamax,測(cè)試將會(huì)失敗。

回到我們的博物館例子中,當(dāng)應(yīng)用程序啟動(dòng)時(shí),首先應(yīng)該看到的是博物館所有文物的清單,這可以使用UIKit設(shè)置窗口的根視圖控制器來實(shí)現(xiàn)。但是要設(shè)置整個(gè)窗口的測(cè)試環(huán)境,會(huì)非常慢且復(fù)雜,所以我們用一個(gè)模擬對(duì)象替換窗口。

- (void)testFirstScreenIsTheListOfAllArtefacts

{

BNRAppDelegate *appDelegate = [[BNRAppDelegate alloc] init];

id window = [OCMockObject mockForClass:[UIWindow class]];

appDelegate.window = window;

[[window expect] setRootViewController:[OCMArg checkWithBlock:^(id viewController) {

return [viewController isKindOfClass:[BNRAllArtefactsTableViewController class]];

}]];

[appDelegate application:nil didFinishLaunchingWithOptions:nil];

[window verify];

}

@end

為了使這個(gè)測(cè)試通過,須實(shí)現(xiàn)應(yīng)用程序的委托方法。

@implementation BNRAppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)options

{

self.window.rootViewController = [[BNRAllArtefactsTableViewController alloc] initWithStyle:UITableViewStyleGrouped];
return YES;

}

@end
完全模擬

該例子中,還有另外一個(gè)需求:當(dāng)加載一個(gè)UIKit的應(yīng)用程序時(shí):包含初始視圖控制器的窗口必須是主要且可見的。我們可以添加一個(gè)測(cè)試表達(dá)這一要求。請(qǐng)注意,由于這個(gè)測(cè)試和之前的測(cè)試使用的是相同的對(duì)象,該構(gòu)造函數(shù)可以被分解成一個(gè)setup方法。

@implementation BNRAppDelegateTests

{

BNRAppDelegate *_appDelegate;

id _window;

}

- (void)setUp

{

_appDelegate = [[BNRAppDelegate alloc] init];

_window = [OCMockObject mockForClass:[UIWindow class]];

appDelegate.window = _window;

}

- (void)testWindowIsMadeKeyAndVisible

{

[[_window expect] makeKeyAndVisible];

[_appDelegate application:nil didFinishLaunchingWithOptions:nil];

[_window verify];

}

- (void)testFirstScreenIsTheListOfArtefacts

{

[[_window expect] setRootViewController:[OCMArg checkWithBlock:^(id viewController) {

return [viewController isKindOfClass:[BNRAllArtefactsTableViewController class]];

}];

[_appDelegate application:nil didFinishLaunchingWithOptions:nil];

[_window verify];

}

@end

現(xiàn)在我們遇到了一個(gè)棘手的問題。新測(cè)試失敗的原因有兩個(gè):預(yù)期的makeKeyAndVisible消息沒有被發(fā)送,卻正在發(fā)送一個(gè)意外的消息setRootViewController:.在 [BNRAppDelegate application:didFinishLaunchingWithOptions:]方法中添加 -makeKeyAndVisible消息 -表示兩個(gè)測(cè)試都失敗了,因?yàn)槟M窗口對(duì)象在每個(gè)測(cè)試都接收了一個(gè)未期待的方法。

完全模擬可以解決這個(gè)問題。完全模擬對(duì)象可記錄它接收到的所有消息,就像一個(gè)普通的模擬消息對(duì)象,包括不期待的消息。這就像說,“我想記錄星際旅行的那個(gè)情節(jié):航海者,但如果在這之前有天氣預(yù)報(bào),我也不介意”,它忽略了額外的信息,并且不考慮導(dǎo)致測(cè)試失敗的消息。

我們可以在setUp方法中把這個(gè)測(cè)試的模擬窗口改成一個(gè)完全模擬。

- (void)setUp

{

_appDelegate = [[BNRAppDelegate alloc] init];

_window = [OCMockObject niceMockForClass:[UIWindow class]];

appDelegate.window = _window;

}

現(xiàn)在,它可以改變應(yīng)用程序的委托,這樣兩個(gè)測(cè)試都可以通過。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)options

{

self.window.rootViewController = [[BNRAllArtefactsTableViewController alloc] initWithStyle:UITableViewStyleGrouped];

[self.window makeKeyAndVisible];
return YES;

}
部分模擬

有時(shí)候,你并不需要用模擬取代所有對(duì)象的行為。你只是想消除一些依賴或復(fù)雜的行為,并在你要測(cè)試的方法使用其結(jié)果。你可以創(chuàng)建一個(gè)子類,并重寫復(fù)雜的方法,此時(shí)使用部分模擬會(huì)更容易。部分模擬作為真正的對(duì)象的代理,截取了部分消息,但是仍然可以使用那些沒有被替換的消息的實(shí)現(xiàn)方法。

再回到我們的博物館庫存的App例子中,策展人需要將文物的原產(chǎn)地作為篩選條件。這意味著需要所有的文物清單,并對(duì)這些對(duì)象做一些測(cè)試,而我們所做的是使allArtefacts方法與庫存對(duì)象進(jìn)行溝通。但這并不是我們?cè)诒敬螠y(cè)試需要關(guān)心的事情:我們要專注于篩選,且不重復(fù)我們?cè)谥耙呀?jīng)完成的測(cè)試工作。使用庫存對(duì)象的部分模擬就可以讓我們?nèi)サ魳秾?duì)象的那部分。這個(gè)測(cè)試類也會(huì)影響文物數(shù)據(jù)模型的設(shè)計(jì)。

@implementation BNRMuseumInventoryTests

{

BNRMuseumInventory *_inventory; //created in -setUp

}

//...

- (void)testArtefactsCanBeFilteredByCountryOfOrigin

{

id romanPot = [OCMockObject mockForProtocol:@protocol(BNRArtefact)];

[[[romanPot stub] andReturn:@"Italy"] countryOfOrigin];

id greekPot = [OCMockObject mockForProtocol:@protocol(BNRArtefact)];

[[[greekPot stub] andReturn:@"Greece"] countryOfOrigin];

id partialInventory = [OCMockObject partialMockForObject:_inventory];

[[[partialInventory stub] andReturn:@[romanPot, greekPot]] allArtefacts];

NSArray *greekArtefacts = [partialInventory artefactsFromCountry:@"Greece"];

XCTAssertTrue([greekArtefacts containsObject:greekPot]);

XCTAssertFalse([greekArtefacts containsObject:romanPot]);

}

@end

在上面的測(cè)試中,我用OCMock的-stub方法,而不是-expect方法。該方法告訴模擬對(duì)象處理該消息并返回指定的值(如果有),但不設(shè)置該測(cè)試稍后需驗(yàn)證的消息的期望值。我可以通過artefactsFromCountry的返回值來辨別代碼是否有用,我并不需要關(guān)心如何實(shí)現(xiàn)(但如果你擔(dān)心硬編碼的一些作弊行為,譬如:通常都會(huì)返回集合中的最后一個(gè)對(duì)象,你可以簡(jiǎn)單地添加更多的測(cè)試)。

這個(gè)測(cè)試告訴我們一些關(guān)于BNRArtefact協(xié)議的事情。

- (NSString *)countryOfOrigin;

@end

現(xiàn)在就可以創(chuàng)建artfactsFromCountry:方法。

- (NSArray *)artefactsFromCountry:(NSString *)country

{

NSArray *artefacts = [self allArtefacts];

NSIndexSet *locationsOfMatchingArtefacts = [artefacts indexesOfObjectsPassingTest:^(id  anArtefact, NSUInteger idx, BOOL *stop){

return [[anArtefact countryOfOrigin] isEqualToString:country];

}];

return [artefacts objectsAtIndexes:locationsOfMatchingArtefacts];

}
結(jié)論

當(dāng)你構(gòu)建應(yīng)用程序的測(cè)試驅(qū)動(dòng)時(shí),模擬對(duì)象能幫助你集中注意力。他們讓你專注于你現(xiàn)在正在做的測(cè)試,同時(shí)推遲對(duì)你未創(chuàng)建對(duì)象的測(cè)試。他們讓你專注于你正在測(cè)試的對(duì)象的部分,忽略你已經(jīng)測(cè)試過或尚未測(cè)試的東西。他們還讓你專注于你自己的代碼,用簡(jiǎn)單的類代替復(fù)雜的框架類。

如果你由文中VCR想到了你家的那部錄音機(jī),那它估計(jì)已經(jīng)到了進(jìn)博物館的年紀(jì)了,而我們剛剛寫的文物庫存管理應(yīng)用程序,它會(huì)在那找到一個(gè)舒適的家。


原文 Making a Mockery with Mock Objects
翻譯 伯樂在線 - Stellar

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/8701.html

相關(guān)文章

  • 一張圖教你快速玩轉(zhuǎn)vue-cli3

    摘要:前言本文系統(tǒng)的梳理了搭建項(xiàng)目的常見用法,目的在于讓你快速掌握獨(dú)立搭建項(xiàng)目的能力。思維導(dǎo)圖接下來,我們根據(jù)思維導(dǎo)圖,一步步來解釋和實(shí)現(xiàn)我們的目標(biāo)。這確保了最終包里數(shù)量的最小化。但是如果其中一個(gè)依賴需要特殊的,默認(rèn)情況下無法將其檢測(cè)出來。 前言 本文系統(tǒng)的梳理了vue-cli3搭建項(xiàng)目的常見用法,目的在于讓你快速掌握獨(dú)立搭建vue項(xiàng)目的能力。你將會(huì)了解如下知識(shí)點(diǎn): 如何安裝項(xiàng)目插件 添加...

    chaosx110 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<