Использование OCUnit для проверки представления UIAlertView - PullRequest
6 голосов
/ 22 мая 2011

Я работаю над приложением, которое будет отображать UIAlertView при нажатии на кнопку выхода, только если был достигнут прогресс в игре. Мне было интересно, как вы будете использовать OCUnit для перехвата UIAlertView и взаимодействия с ним, или даже для обнаружения, если он был представлен. Единственное, о чем я могу подумать, - это сделать обезьяну-патч [UIAlertViewDelegate willPresentAlertView], но это заставляет меня плакать.

Кто-нибудь знает лучший способ сделать это?

Ответы [ 4 ]

4 голосов
/ 18 ноября 2013

Последняя версия OCMock (2.2.1 на момент написания этой статьи) имеет функции, которые облегчают эту задачу. Вот некоторый пример тестового кода, который заглушает метод класса «alloc» UIAlertView для возврата фиктивного объекта вместо реального 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];
4 голосов
/ 30 мая 2011

Обновление : см. Сообщение в моем блоге Как выполнить модульное тестирование своих предупреждений и листов действий

Проблема с моим другим ответом заключается в том, что сам метод -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 было отправлено.

Дополнительный совет: В дополнение к ложному предупреждению я определил класс верификатора предупреждений, который:

  • Регистры для уведомления
  • Позволяет установить ожидаемые значения
  • После уведомления проверяет состояние по ожидаемым значениям

Итак, все мои тесты предупреждений сейчас - это создание верификатора, установка ожиданий и выполнение вызова.

3 голосов
/ 22 мая 2011

Примечание: пожалуйста, смотрите мой другой ответ. Я рекомендую это по этому.

В реальном классе определите короткий метод для отображения предупреждения, например:

- (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 голосов
/ 30 июня 2013

Вы можете получить модульные тесты для представлений предупреждений довольно легко, заменив реализацию 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;
}

, и метод 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");
...