使用 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 设备而不是模拟器上运行