国产gaysexchina男同gay,japanrcep老熟妇乱子伦视频,吃奶呻吟打开双腿做受动态图,成人色网站,国产av一区二区三区最新精品

測(cè)試ViewModels

2018-08-01 16:25 更新

  在本書的最后,講一下與測(cè)試相關(guān)的問(wèn)題,其中單元測(cè)試尤為重要。測(cè)試這個(gè)話題相對(duì)于iOS開(kāi)發(fā)社區(qū)來(lái)說(shuō)還是頗具爭(zhēng)議性的,在理想的情況下,我們?cè)诰帉懸晥D模型的時(shí)候就該為其編寫單元測(cè)試了。之所以將測(cè)試這一章放在最后一節(jié)來(lái)講解,就是考慮到大家在學(xué)習(xí)使用這種新的模式來(lái)進(jìn)行編碼的時(shí)候不是一件簡(jiǎn)單的事情了,再要試著測(cè)試一些沒(méi)有吃透的東西是非常有難度的,而學(xué)到最后的話就大致上已經(jīng)掌握了這種編碼方式了,這樣理解起來(lái)也相對(duì)容易。

  當(dāng)然我也注意到,并不是每個(gè)人都以相同的方式來(lái)測(cè)試,或者能夠測(cè)試到相同的程度。我有.Net編程背景,在.net中使用mocks來(lái)測(cè)試系統(tǒng)的實(shí)現(xiàn)細(xì)節(jié)是最平常不過(guò)的了。其他平臺(tái)背景的開(kāi)發(fā)者較少使用mocks來(lái)做,甚至從來(lái)沒(méi)有這樣的經(jīng)驗(yàn)。本節(jié)我只將我的單元測(cè)試方法分享給大家,如果你覺(jué)得合適就采用。

  確保你的Podfile文件包含下面這些庫(kù):

target "FRPTests" do

pod 'ReactiveCocoa', '2.1.4'
pod 'ReactiveViewModel', '0.1.1'
pod 'libextobjc', '0.3'
pod '500px-iOS-api', '1.0.5'
pod 'Specta', '~> 0.2.1'
pod 'Expecta', '~> 0.2'
pod 'OCMock', '~> 2.2.2'

end

  然后運(yùn)行pod install.

  首先我們來(lái)看看FRPFullSizePhotoViewModel,因?yàn)樗罹逴bjective-C風(fēng)范(沒(méi)有太多ReactiveCocoa).

@interface FRPFullSizePhotoViewModel ()
//Private access
@property (nonatomic, assign) NSInteger initialPhotoIndex;

@end

@implementation FRPFullSizePhotoViewModel

- (instancetype)initWithPhotoArray:(NSArray *)photoArray initialPhotoIndex:(NSInteger)initialPhotoIndex {
    self = [self initWithModel:photoArray];
    if(!self) return nil;

    self.initialPhotoIndex = initialPhotoIndex;

    return self;
}

- (NSString *)initialPhotoName {
    return [self.model[self.initialPhotoIndex] photoName];
}

- (FRPPhotoModel *)photoModelAtIndex:(NSInteger)index {
    if(index < 0 || index > self.model.count - 1) {
        //Index was out of bounds, return nil
        return nil;
    }
    else {
        return self.model[index];
    }
}

@end

好了,我們先來(lái)測(cè)試這個(gè)初始化方法,然后在轉(zhuǎn)移到其他兩個(gè)方法上。

我們想印證初始化我們的視圖模型時(shí),它的兩個(gè)屬性modelinitialPhotoIndex被正確地賦值了。

#import 
#define EXP_SHORTHAND
#import 
#import 
#import "FRPPhotoModel.h"

#import "FRPFullSizePhotoViewModel.h"

SpecBegin(FRPFullSizePhotoViewModel)

describe(@"FRPFullSizePhotoModel", ^{
    it (@"Should assign correct attributes when initialized", ^{
        NSArray *model = @[];
        NSInteger initialPhotoIndex = 1337;

        FRPFullSizePhotoViewModel *viewModel =\
         [[FRPFullSizePhotoViewModel alloc] initWithPhotoArray:model
                                                     initialPhotoIndex: initialPhotoIndex];

        expect(model).to.equal(viewModel.model);
        expect(initialPhotoIndex).to.equal(viewModel.initialPhotoIndex);

    });
});

SpecEnd

  在該代碼段頂部,我們導(dǎo)入了一些頭文件,包括一個(gè)奇怪的預(yù)定義EXP_SHORTHAND,我們把他放在那里以便于可以使用類似expect()這樣的shorthand matchers(速記匹配)的語(yǔ)法。然后我們引入我們的私有接口SpecBegin(...)/SpecEnd來(lái)為我們正在測(cè)試的視圖模型屏蔽編譯警告,最后的部分就是我們的單元測(cè)試本身。Specta的測(cè)試規(guī)范相當(dāng)簡(jiǎn)單,你可以閱讀更多的關(guān)于這方面的信息,但本書不會(huì)深入講解它的一些細(xì)節(jié)??傊愕臏y(cè)試始于SpecBegin并終止于SpecEnd,測(cè)試?yán)逃妙愃朴?code>@"應(yīng)該。。。",^{ 預(yù)測(cè)正常的情況應(yīng)該如何 }寫在中間。

  好了,停止模擬器中正在運(yùn)行的應(yīng)用,按下cmd+U快捷鍵,你就可以運(yùn)行這段單元測(cè)試了。如果一切正常,你就能通過(guò)測(cè)試。

接下來(lái)我們來(lái)看看photoModelAtIndex:方法

- (FRPPhotoModel *)photoModelAtIndex:(NSInteger)index {
    if(index < 0 || index > self.model.count - 1 ) {
        // Index was out of bounds ,return nil
        return nil;
    }
    else {
        return self.model[ index ];
    }
}

這里面沒(méi)有太多的業(yè)務(wù)邏輯,但是我們看到其他地方都要使用它,所以我們的測(cè)試應(yīng)該是健壯的。

it(@"Should return nil for an out-of-bounds photo index", ^{
    NSArray *model = @[[NSobject new]];
    NSInteger initialPhotoIndex = 0;

    FRPFullSizePhotoViewModel *viewModel = \
        [[FRPFullSizePhotoViewModel alloc] initWithPhotoArray:model initialPhotoIndex:initialPhotoIndex];

    id subzeroModel = [viewModel photoModelAtIndex:-1];
    expect(subzeroModel).to.beNil();

    id aboveBoundsModel = [viewModel photoModelAtIndex:model.count];
    expect(aboveBoundsModel).to.beNil();
});

it(@"Should return the correct model for photoModelAtIndex:",^{
    id photoModel = [NSObject new];
    NSArray *model = @[photoModel];
    NSInteger initialPhotoIndex = 0;

    FRPFullSizePhotoViewModel *viewModel = \
        [[FRPFullSizePhotoViewModel alloc] initWithPhotoArray:model initialPhotoIndex:initialPhotoIndex];

    id returnModel = [viewModel photoModelAtIndex:0];
    expect(returnModel).to.equal(photoModel);

});

太棒了!我們這個(gè)新的測(cè)試保證了我們的代碼具有完全的代碼覆蓋率。它檢測(cè)了photoModelAtIndex:參數(shù)的三種可能的情況:少于0、在作用范圍內(nèi)以及越界。

最后,我們來(lái)看下initialPhotoName方法:

- (NSString *)initialPhotoName {
    return [self.model[self.initialPhotoIndex] photoName];
}

方法看起來(lái)很簡(jiǎn)單,但實(shí)際上這里面包含了更深層級(jí)的東西。恰當(dāng)?shù)刂貥?gòu)一些代碼并為它寫一點(diǎn)不一樣的更小的測(cè)試代碼,來(lái)嚴(yán)格地測(cè)試這個(gè)方法。

- (NSString *)initialPhotoName {
    FRPPhotoModel *photoModel = [self initialPhotoModel];
    return [photoModel photoName];
}

- (FRPPhotoModel *)initialPhotoModel {
    return [self photoModelAtIndex:self.initialPhotoIndex];
}

這更清晰簡(jiǎn)單了,一個(gè)方法確切地只做一件事情,就像一棵樹(shù)的樹(shù)皮,層層疊疊相互依存。只要我們一路下來(lái)所有的代碼都測(cè)試,那么最后我們就可以很確切地保證代碼的健壯性。

initialPhotoModel是一個(gè)私有方法,所以測(cè)試它我們需要在測(cè)試文件中申明它。

@interface FRPFullSizePhotoViewModel ()

- (FRPPhotoModel *)initialPhotoModel;

@end

你看到的所有我們的測(cè)試代碼都非常簡(jiǎn)單。

it (@"Should return the correct initial photo model", ^{
    NSArray *model = @[[NSobject new]];
    NSInteger initialPhotoIndex = 0;

    FRPFullSizePhotoViewModel *viewModel = \
        [[FRPFullSizePhotoViewModel alloc] initWithPhotoArray:model initialPhotoIndex:initialPhotoIndex];

    id mockViewModel = [OCMockObject partialMockForObject:viewModel];
    [[[mockViewModel expect] andReturn:model[0]] photoModelAtIndex:initialPhotoIndex];

    id returnedObject = [mockViewModel initialPhotoModel];

    expect(returnedObject).to.equal(model[0]);

    [mockViewModel verify];
});

這個(gè)測(cè)試是用來(lái)確認(rèn)當(dāng)initialPhotoModel被調(diào)用時(shí),接下來(lái)它應(yīng)該調(diào)用photoModelAtIndex:方法并將initialPhotoIndex作為參數(shù)傳入。這個(gè)測(cè)試是否簡(jiǎn)單取決于我們測(cè)試photoModelAtIndex:是否充分。

接下來(lái),就讓我們一起來(lái)看看FRPGalleryViewModel,這看似非常簡(jiǎn)單:

- (instancetype)init {
    self = [super init];
    if(!self) return nil;

    RAC(self, model) = [[[FRPPhotoImporter importPhotos] logError] catchTo:[RACSignal empty]];

    return self;
}

然而,它可測(cè)性不高,需要重構(gòu)。

我們簡(jiǎn)單地重構(gòu)下視圖模型。新的實(shí)現(xiàn)如下:

@implementation FRPGalleryViewModel

- (instancetype)init {
    self = [super init];
    if(!self) return nil;

    RAC(self, model) = [self importPhotosSignal];

    return self;
}

- (RACSignal *)importPhotosSignal {
    return [[[FRPPhotoImporter importPhotos] logError] catchTo:[RACSignal empty]];
}

@end

我們把importPhotos的調(diào)用抽出來(lái),以方便測(cè)試這個(gè)方法是否被調(diào)用。我們不會(huì)測(cè)試FRPPhotoImporter,關(guān)于它的測(cè)試(即單例測(cè)試)已經(jīng)超出了本書的范疇。

這部分的測(cè)試代碼如下:

#import "Specta.h"
#import 

#import "FRPGalleryViewModel.h"

@interface FRPGalleryViewModel ()

- (RACSignal *)importPhotosSignal;

@end

SpecBegin(FRPGalleryViewModel)

describe(@"FRPGalleryViewModel",^{
    it(@"should be initialized and call importPhotos", ^{
        id mockObject = [OCMockObject mockForClass:[FRPGalleryViewModel class]];
        [[[mockObject expect] andReturn:[RACSignal empty]] importPhotosSignal];

        mockObject = [mockObject init];

        [mockObject verify];
        [mockObject stopMocking];
    });
});

  為了測(cè)試一個(gè)方法,測(cè)試代碼也太多了吧! 我知道,我知道~ 這是OCMock沒(méi)落的原因之一,它竟然需要這么多的模板。但你不能責(zé)怪它,因?yàn)樗ぷ髟诹钏缓醯腛bjective-C平臺(tái)上!

  我們創(chuàng)建了一個(gè)FRPGalleryViewModel的mock版本,告訴它期望importPhotoSignal被調(diào)用。然后才進(jìn)行對(duì)象的初始化。這里使用了一點(diǎn)點(diǎn)技巧,因?yàn)槲覀冊(cè)趍ockObject上調(diào)用了init方法,但它(init)實(shí)際上是一個(gè)NSProxy的子類。然后,對(duì)OCMock來(lái)講,它足夠聰明,它了解這一切,有能力做出正確的選擇。只是看起來(lái)有點(diǎn)詭異罷了。我們使用[mockObject init]mockObject賦值,也是為了屏蔽編譯警告。最后我們驗(yàn)證了所有預(yù)期可能被調(diào)用的方法。

  這個(gè)例子中表現(xiàn)出來(lái)的測(cè)試很困難的情況也說(shuō)明了另一個(gè)問(wèn)題,你應(yīng)該避免視圖模型的初始化方法產(chǎn)生"副作用"(參見(jiàn)前面章節(jié)提到的“函數(shù)的副作用”),應(yīng)該使用didBecomeActiveSignal來(lái)代理。

下面我們來(lái)測(cè)試FRPPhotoViewModel.再次突出引起函數(shù)副作用和使用didBecomeActiveSignal的區(qū)別。

快速瀏覽下實(shí)現(xiàn):


@implementation FRPPhotoViewModel

- (intancetype)initWithModel:(FRPPhotoModel *)photoModel {
    self = [super initWithModel:photoModel];
    if(!self) return nil;

    @weakify(self);
    [self.didBecomeActiveSignal subscribeNext:^ (id x) {
        @strongify(self);
        self.loading = YES;
        [[FRPPhotoImporter fetchPhotoDetails:self.model]
            subscribeError: ^ (NSError *error) {
                NSLog(@"Could not fetch photo details: %@",error);
            }
            completed: ^ {
                self.loading = NO;
                NSLog(@"Fetched photo details");
            }];
    }];

    RAC(self, photoImage) = [RACObserve(self.model, fullsizedData) map:^id (id value) {
        return [UIImage imageWithData:value];
    }];

    return self;
}

- (NSString *)photoName {
    return self.model.photoName;
}

@end

首先我們來(lái)測(cè)試photoName方法:

#import 
#define EXP_SHORTHAND
#import 
#import 

#import "FRPPhotoViewModel.h"
#import "FRPPhotoModel.h"

SpecBegin(FRPPhotoViewModel)

describe (@"FRPPhotoViewModel", ^{
    it(@"should return the photo's name property when photoName is invoked", ^{
        NSString *name = @"Ash";

        id mockPhotoModel = [OCMockObject mockForClass:[FRPPhotoModel class]];
        [[[mockPhotoModel stub] andReturn:name] photoName];

        FRPPhotoViewModel *viewModel = [[FRPPhotoViewModel alloc] initWithModel:nil];
        id mockViewModel = [OCMockObject partialMockForObject:viewModel];
        [[[mockViewModel stub] andReturn:mockPhotoModel] model];

        id returnName = [mockViewModel photoName];

        expect(returnedName).to.equal(name);
        [mockPhotoModel stopMocking];
    });
});

我們?yōu)閙ock的視圖模型的model屬性添加了一個(gè)mockPhotoModel,它會(huì)mocks所有的途徑。

現(xiàn)在來(lái)看這個(gè)復(fù)雜的初始化方法,這東西看起來(lái)真巨大!近20行純粹的未經(jīng)測(cè)試的代碼。哎呀!讓我們來(lái)一點(diǎn)點(diǎn)簡(jiǎn)化這個(gè)事情,并逐步加上我們的測(cè)試代碼。

- (instancetype)initWithModel:(FRPPhotoModel *)photoModel {
    self = [super initWithModel:photoModel];
    if(!self) return nil;

    @weakify(self);
    [self.didBecomeActiveSignal subscribeNext:^(id x) {
        @strongify(self);
        [self downloadPhotoModelDetails];
    }];

    RAC(self, photoImage) = [RACObserve(self.model, fullsizedData) map:^id (id value) {
        return [UIImage imageWithData:value];
    }];

    return self;
}

- (void)downloadPhotoModelDetails {
    self.loading = YES;
    [[FRPPhotoImporter fetchPhotoDetails:self.model] subscribeError:^(NSError *error) {
        NSLog(@"Could not fetch photo details : %@",error);
    } completed:^ {
        self.loading = NO;
        NSLog(@"Fetched photo details.");
    }];
}

我們選擇了不直接測(cè)試fetchPhotoDetails:,所以我們把它置于一個(gè)實(shí)例方法中,以便更容易對(duì)它進(jìn)行測(cè)試。這個(gè)方法(即fetchPhotoDetails:)實(shí)現(xiàn)的細(xì)節(jié)在這里對(duì)我們不重要。

現(xiàn)在開(kāi)始寫關(guān)于它的測(cè)試代碼吧:

it(@"should download photo model details when it becomes active", ^{
    FRPPhotoViewModel *viewModel = [[FRPPhotoViewModel alloc] initWithModel:nil];

    id mockViewModel = [OCMockObject partialMockForObject:viewModel];
    [[mockViewModel expect] downloadPhotoModelDetails];

    [mockViewModel setActive:YES];
    [mockViewModel verify];
});

注意看初始化方法中不產(chǎn)生(函數(shù))副作用而是把這種副作用放在訂閱didBecomeActiveSignal的Block塊中時(shí),測(cè)試視圖模型的代碼是多么簡(jiǎn)單!

現(xiàn)在我們需要測(cè)試剩下的那些視圖模型,他們?nèi)糠浅:?jiǎn)單。我們使用更少的mock,因?yàn)楹芏嗟臉I(yè)務(wù)邏輯僅僅是視圖模型的model值到他自己的屬性的映射。

it (@"should return the photo's name property when photoName is invoked", ^{
    NSString *name = @"Ash";

    id mockPhotoModel = [OCMockObject mockForClass:[FRPPhotoModel class]];
    [[[mockPhotoModel stub] andReturn:name] photoName];

    FRPPhotoViewModel *viewModel = [[FRPPhotoViewModel alloc] initWithModel:nil];
    id mockViewModel = [OCMockObject partialMockForObject:viewModel];
    [[[mockViewModel stub] andReturn:mockPhotoModel] model];

    id returnedName = [mockViewModel photoName];

    expect(returnedName).to.equal(name);

    [mockPhotoModel stopMocking];
});

it (@"should correctly map image data to UIImage", ^{
    UIImage *image = [[UIImage alloc] init];
    NSData *imageData = [NSData data];

    id mockImage = [OCMockObject mockForClass:[UIImage class]];
    [[[mockImage stub] andReturn:image] imageWithData:imageData];

    FRPPhotoModel *photoModel = [[FRPPhotoModel alloc] init];

    photoModel.fullsizedData = imageData;

    __unused FRPPhotoViewModel *viewModel = [[FRPPhotoViewModel alloc] initWithModel:photoModel];

    [mockImage verify];
    [mockImage stopMocking];

});

it(@"should return the correct photo name", ^{
    NSString *name = @"Ash";

    FRPPhotoModel *photoModel = [[FRPPhotoModel alloc] init];
    photoModel.photoName = name;

    FRPPhotoViewModel *viewModel = [[FRPPhotoViewModel alloc] initWithModel:photoModel];

    NSString *returnedName = [viewModel photoName];

    expect(name).to.equal(returnedName);
});

  這就是為視圖模型撰寫單元測(cè)試的全部?jī)?nèi)容了。

  在理想的情況下,單元測(cè)試能幫助改進(jìn)你的代碼質(zhì)量。小巧而高內(nèi)聚的方法比隨意的滿是副作用的方法更招人待見(jiàn),它簡(jiǎn)單而完美地詮釋了函數(shù)響應(yīng)型編程的精髓。

  測(cè)試MVVM的好處是:我們不用觸及UIKit。請(qǐng)記住,寫得好的MVVM視圖模型的特點(diǎn)是:該視圖模型不會(huì)與用戶交互的接口類有任何交互。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)