2011-05-22 94 views
6

我正在研究一款應用程序,只有在遊戲進度已經達到時纔會按下退出按鈕纔會顯示UIAlertView。我想知道如何使用OCUnit攔截UIAlertView並與之交互,甚至可以檢測它是否已經呈現。我唯一能想到的就是monkypatch [UIAlertViewDelegate willPresentAlertView],但這讓我想哭。使用OCUnit測試是否存在UIAlertView

有沒有人知道這樣做的更好方法?

回答

4

更新:請參見我的博客文章http://qualitycoding.org/testing-alerts/

的問題與我的其他答案是:-showAlertWithMessage:方法本身是從來沒有單元測試行使。 「使用手動測試來驗證一次」對於簡單的場景來說不是太糟糕,但是錯誤處理通常涉及難以複製的異常情況。 ...另外,我感覺到我已經停止了短暫的嘮叨感覺,並且可能會有更徹底的方式。有。

在被測試的類中,不要直接實例化UIAlertView。相反,定義方法

+ (Class)alertViewClass 
{ 
    return [UIAlertView class]; 
} 

可以使用「子類和覆蓋」進行替換。 (可替代地,使用依賴注入和在作爲初始化參數傳遞這個類。)

調用此,以確定該類實例來顯示警報:

Class alertViewClass = [[self class] alertViewClass]; 
id alert = [[alertViewClass alloc] initWithTitle:...etc... 

現在定義一個模擬警報視圖類。它的任務是要記住它的初始化參數,並張貼通知,傳遞本身作爲對象:

- (void)show 
{ 
    [[NSNotificationCenter defaultCenter] postNotificationName:MockAlertViewShowNotification 
                 object:self 
                 userInfo:nil]; 
} 

您的測試子類(TestingFoo)重新定義+alertViewClass來替代模擬:

+ (Class)alertViewClass 
{ 
    return [MockAlertView class]; 
} 

使測試級通知登記。被調用的方法現在可以驗證傳遞給警報初始值設定項的參數以及-show消息的次數。

附加尖端:除了模擬警報,我所定義的警報驗證類:

  • 寄存器用於通知
  • 讓我組期望值
  • 一旦通知,驗證狀態與預期值相關

因此,我現在所做的所有警報測試都是創建驗證者,設置期望值以及行使電話。

+2

我覺得在這裏上課會更有用。如果我的單元測試框架阻礙了我的真實代碼,我會感到噁心。我可以編寫一個UIAlertViewPoser,在顯示警告時設置一個標誌。 http://www.cocoadev.com/index.pl?ClassPosing – wjl 2012-03-28 07:29:55

+1

@wjlafrance有趣的,如果它有效,會很好。不幸的是,poseAsClass:在Mac上已被棄用,並且在iOS上從不支持。 – 2012-04-02 01:35:54

+0

是的,我注意到我發佈後不久。呵呵.. :( – wjl 2012-04-10 18:00:41

3

注意:請參閱我的其他答案。我推薦它在這一個。

在實際的類,定義了一個簡短的方法來顯示一個警告,是這樣的:

- (void)showAlertWithMessage:(NSString message *)message 
{ 
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil 
                message:message 
                delegate:self 
              cancelButtonTitle:@"OK" 
              otherButtonTitles:nil]; 
    [alert show]; 
    [alert release]; 
} 

爲您的測試,沒有測試這種方法實際。相反,使用「子類和重寫」來定義一個簡單記錄它的調用和參數的間諜。假設原來的班級名爲「Foo」。下面是用於測試的子類:

@interface TestingFoo : Foo 
@property(nonatomic, assign) NSUInteger countShowAlert; 
@property(nonatomic, retain) NSString *lastShowAlertMessage; 
@end 

@implementation TestingFoo 
@synthesize countShowAlert; 
@synthesize lastShowAlertMessage; 

- (void)dealloc 
{ 
    [lastShowAlertMessage release]; 
    [super dealloc]; 
} 

- (void)showAlertWithMessage:(NSString message *)message 
{ 
    ++countShowAlert; 
    [self setLastShowAlertMessage:message]; 
} 

@end 

現在只要

  • 代碼調用的-showAlertWithMessage:而不是直接顯示警告,並
  • 測試代碼實例TestingFoo,而不是Foo

您可以檢查顯示警報的呼叫數量和最後一條消息。

由於這不會執行顯示警報的實際代碼,因此請使用手動測試來驗證一次。

+0

如果您的Obj C類已經是測試框架的一個子類,例如,您可以執行子類並覆蓋範例工作。 SenTestCase?唯一的選擇是依賴注入嗎? – 2012-04-12 10:27:34

+1

我不明白爲什麼你不應該能夠自定義你自己定義的任何具體類,以覆蓋特定的方法。 – 2012-04-16 01:08:08

+0

啊我想我已經明白了。感謝你。 – 2012-04-16 07:57:29

0

通過交換UIAlertView的'show'實現,您可以完全無縫地獲得警報視圖的單元測試。例如,這個接口給你提供測試能力的一些量:

@interface UIAlertView (Testing) 

+ (void)skipNext; 
+ (BOOL)didSkip; 

@end 

本實施

#import <objc/runtime.h> 
@implementation UIAlertView (Testing) 

static BOOL skip = NO; 

+ (id)alloc 
{ 
    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
     Method showMethod = class_getInstanceMethod(self, @selector(show)); 
     Method show_Method = class_getInstanceMethod(self, @selector(show_)); 
     method_exchangeImplementations(showMethod, show_Method); 
    }); 
    return [super alloc]; 
} 

+ (void)skipNext 
{ 
    skip = YES; 
} 

+ (BOOL)didSkip 
{ 
    return !skip; 
} 

- (void)show_ 
{ 
    NSLog(@"UIAlertView :: would appear here (%@) [ title = %@; message = %@ ]", skip ? @"predicted" : @"unexpected", [self title], [self message]); 
    if (skip) { 
     skip = NO; 
     return; 
    } 
} 

@end 

可以例如編寫單元測試像這樣:

[UIAlertView skipNext]; 
// do something that you expect will give an alert 
STAssertTrue([UIAlertView didSkip], @"Alert view did not appear as expected"); 

如果你想在警報視圖中自動點擊一個特定的按鈕,你將需要更多的魔力。接口增加了兩個新的類方法:

@interface UIAlertView (Testing) 

+ (void)skipNext; 
+ (BOOL)didSkip; 

+ (void)tapNext:(NSString *)buttonTitle; 
+ (BOOL)didTap; 

@end 

這是這樣的

static NSString *next = nil; 

+ (void)tapNext:(NSString *)buttonTitle 
{ 
    [next release]; 
    next = [buttonTitle retain]; 
} 

+ (BOOL)didTap 
{ 
    BOOL result = !next; 
    [next release]; 
    next = nil; 
    return result; 
} 

和表演方法成爲

- (void)show_ 
{ 
    if (next) { 
     NSLog(@"UIAlertView :: simulating alert for tapping %@", next); 
     for (NSInteger i = 0; i < [self numberOfButtons]; i++) 
      if ([next isEqualToString:[self buttonTitleAtIndex:i]]) { 
       [next release]; 
       next = nil; 
       [self alertView:self clickedButtonAtIndex:i]; 
       return; 
      } 
     return; 
    } 
    NSLog(@"UIAlertView :: would appear here (%@) [ title = %@; message = %@ ]", skip ? @"predicted" : @"unexpected", [self title], [self message]); 
    if (skip) { 
     skip = NO; 
     return; 
    } 
} 

這可以類似的測試,但不是向下跳讀你」 d說哪個按鈕可以點擊。例如。

[UIAlertView tapNext:@"Download"]; 
// do stuff that triggers an alert view with a "Download" button among others 
STAssertTrue([UIAlertView didTap], @"Download was never tappable or never tapped"); 
4

最新版本的OCMock(2.2.1在撰寫本文時)具有使這一切變得簡單的功能。下面是一些示例測試代碼,它存根UIAlertView的「alloc」類方法返回一個模擬對象,而不是真正的UIAlertView。

id mockAlertView = [OCMockObject mockForClass:[UIAlertView class]]; 
[[[mockAlertView stub] andReturn:mockAlertView] alloc]; 
(void)[[[mockAlertView expect] andReturn:mockAlertView] 
      initWithTitle:OCMOCK_ANY 
       message:OCMOCK_ANY 
       delegate:OCMOCK_ANY 
     cancelButtonTitle:OCMOCK_ANY 
     otherButtonTitles:OCMOCK_ANY, nil]; 
[[mockAlertView expect] show]; 

[myViewController doSomething]; 

[mockAlertView verify]; 
+1

謝謝!男人,我記得在我學習過關於嘲笑之前測試過,那些是黑暗的年齡。 – wjl 2013-11-21 20:34:11

+0

我可以使用相同的方法來發送郵件作曲嗎?查看我的問題:http://stackoverflow.com/questions/21968556/ocmock-unexpected-method-invoked-though-expected – 2014-02-23 14:27:37

+0

This works great! – vincentjames501 2014-02-26 19:43:08