2011-05-22 63 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