我正在研究一款应用程序,只有在游戏进度已经达到时才会按下退出按钮才会显示UIAlertView
。我想知道如何使用OCUnit拦截UIAlertView
并与之交互,甚至可以检测它是否已经呈现。我唯一能想到的就是monkypatch [UIAlertViewDelegate willPresentAlertView]
,但这让我想哭。使用OCUnit测试是否存在UIAlertView
有没有人知道这样做的更好方法?
我正在研究一款应用程序,只有在游戏进度已经达到时才会按下退出按钮才会显示UIAlertView
。我想知道如何使用OCUnit拦截UIAlertView
并与之交互,甚至可以检测它是否已经呈现。我唯一能想到的就是monkypatch [UIAlertViewDelegate willPresentAlertView]
,但这让我想哭。使用OCUnit测试是否存在UIAlertView
有没有人知道这样做的更好方法?
更新:请参见我的博客文章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
消息的次数。
附加尖端:除了模拟警报,我所定义的警报验证类:
因此,我现在所做的所有警报测试都是创建验证者,设置期望值以及行使电话。
注意:请参阅我的其他答案。我推荐它在这一个。
在实际的类,定义了一个简短的方法来显示一个警告,是这样的:
- (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?唯一的选择是依赖注入吗? – 2012-04-12 10:27:34
我不明白为什么你不应该能够自定义你自己定义的任何具体类,以覆盖特定的方法。 – 2012-04-16 01:08:08
啊我想我已经明白了。感谢你。 – 2012-04-16 07:57:29
通过交换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");
最新版本的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];
谢谢!男人,我记得在我学习过关于嘲笑之前测试过,那些是黑暗的年龄。 – wjl 2013-11-21 20:34:11
我可以使用相同的方法来发送邮件作曲吗?查看我的问题:http://stackoverflow.com/questions/21968556/ocmock-unexpected-method-invoked-though-expected – 2014-02-23 14:27:37
This works great! – vincentjames501 2014-02-26 19:43:08
我觉得在这里上课会更有用。如果我的单元测试框架阻碍了我的真实代码,我会感到恶心。我可以编写一个UIAlertViewPoser,在显示警告时设置一个标志。 http://www.cocoadev.com/index.pl?ClassPosing – wjl 2012-03-28 07:29:55
@wjlafrance有趣的,如果它有效,会很好。不幸的是,poseAsClass:在Mac上已被弃用,并且在iOS上从不支持。 – 2012-04-02 01:35:54
是的,我注意到我发布后不久。呵呵.. :( – wjl 2012-04-10 18:00:41