使用 OCUnit 测试是不是存在 UIAlertView

Posted

技术标签:

【中文标题】使用 OCUnit 测试是不是存在 UIAlertView【英文标题】:Using OCUnit to test if an UIAlertView is presented使用 OCUnit 测试是否存在 UIAlertView 【发布时间】:2011-05-22 05:36:30 【问题描述】:

我正在开发一个应用程序,该应用程序将在点击退出按钮时显示UIAlertView,前提是游戏已取得进展。我想知道您将如何使用 OCUnit 来拦截 UIAlertView 并与之交互,甚至检测它是否已呈现。我唯一能想到的就是猴子补丁[UIAlertViewDelegate willPresentAlertView],但这让我想哭。

有人知道更好的方法吗?

【问题讨论】:

【参考方案1】:

更新:见我的博文How to Unit Test Your Alerts and Action Sheets

我的另一个答案的问题是 -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 被发送消息的次数。

附加提示: 除了模拟警报之外,我还定义了一个警报验证器类:

注册通知 让我设置预期值 收到通知后,根据预期值验证状态

所以我现在所做的所有警报测试都是创建验证器、设置期望值并执行调用。

【讨论】:

我认为课堂姿势在这里会更有用。如果我的单元测试框架妨碍了我的真实代码,我会有一种恶心的感觉。我可以编写一个 UIAlertViewPoser 在显示警报时设置一个标志。 cocoadev.com/index.pl?ClassPosing @wjlafrance 很有趣,如果它有效,那就太好了。不幸的是,poseAsClass: 在 Mac 上已被弃用,并且从未在 ios 上得到支持。 是的,我在发布后不久就注意到了这一点。哦,好吧.. :( 对于任何使用 OCMock 的人:如果您将 alertViewClass 定义为实例方法,您可以为您的被测对象使用部分模拟以使其返回 MockAlertView 类。这消除了拥有 TestingFoo 子类的需要。【参考方案2】:

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];

【讨论】:

谢谢!伙计,我记得在我了解模拟之前进行了测试。那是黑暗的时代。 我可以对邮件作曲家使用相同的方法吗?查看我的问题:***.com/questions/21968556/…【参考方案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

您可以查看呼叫次数以显示警报,以及最后一条消息。

由于这不会执行显示警报的实际代码,因此请使用手动测试来验证一次。

【讨论】:

如果您的 Obj C 类已经是测试框架的子类,那么子类和覆盖范式是否工作,例如SenTestCase ?唯一的选择是依赖注入吗? 我不明白为什么你不能继承你自己定义的任何具体类,以便覆盖特定的方法。【参考方案4】:

您可以通过交换 UIAlertView 的“显示”实现来相当无缝地获得警报视图的单元测试。例如,此界面为您提供了一些测试能力:

@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;

show 方法变成了

- (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;
    

这可以进行类似的测试,但不是skipNext,你会说要点击哪个按钮。例如

[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");

【讨论】:

以上是关于使用 OCUnit 测试是不是存在 UIAlertView的主要内容,如果未能解决你的问题,请参考以下文章

OCUnit 是不是允许在 iPhone 模拟器上运行应用程序测试?

Xcode 的 OCUnit 测试能否在 iOS 设备而不是模拟器上运行

iPhone 上的 ocunit 测试

Swift:函数中的 UIAlert - 使用未解析的标识符“存在”

在 OCUnit 中使用属性是不是安全?

如何使用 Xcode 4 和 OCUnit 运行应用程序测试?