我正在开发一个应用程序,该应用程序将UIAlertView
在点击它的退出按钮时显示,只有在游戏取得进展的情况下。我想知道你将如何使用 OCUnit 来拦截它UIAlertView
并与之交互,甚至检测它是否已经呈现。我唯一能想到的就是猴子补丁[UIAlertViewDelegate willPresentAlertView]
,但这让我想哭。
有谁知道这样做的更好方法?
我正在开发一个应用程序,该应用程序将UIAlertView
在点击它的退出按钮时显示,只有在游戏取得进展的情况下。我想知道你将如何使用 OCUnit 来拦截它UIAlertView
并与之交互,甚至检测它是否已经呈现。我唯一能想到的就是猴子补丁[UIAlertViewDelegate willPresentAlertView]
,但这让我想哭。
有谁知道这样做的更好方法?
更新:请参阅我的博客文章如何对警报和操作表进行单元测试
我的另一个答案的问题是该-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
。
附加提示:除了模拟警报之外,我还定义了一个警报验证器类:
因此,我现在所做的所有警报测试都是创建验证器、设置期望值并执行调用。
最新版本的 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];
注意:请参阅我的其他答案。我推荐它而不是这个。
在实际的类中,定义一个简短的方法来显示警报,例如:
- (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
,您可以检查显示警报的呼叫次数和最后一条消息。
由于这不会执行显示警报的实际代码,因此请使用手动测试对其进行一次验证。
通过交换 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");