Как выполнить модульное тестирование UIViewController - TDD/BDD

Модульное тестирование - это просто то, что мне никогда не удается осознать, но я понимаю, почему это важно и может сильно сэкономить время (если вы знаете, что делаете). Я надеюсь, что кто-то может указать мне правильное направление.

У меня есть следующее UIViewController

QBElectricityBaseVC.h

@interface QBElectricityBaseVC : QBStateVC

@property (nonatomic, strong) QBElectricityUsage *electricityUsage;
@property (nonatomic, assign) CGFloat tabBarHeight;

- (void)updateElectricityUsage;

@end

QBElectricityBaseVC.m

@implementation QBElectricityBaseVC

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.tabBarItem = [[UITabBarItem alloc] initWithTitle:NSLocalizedString(@"electricity_title", nil) image:nil tag:0];
    }
    return self;
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    [self.notificationCenter addObserver:self selector:@selector(updateElectricityUsage)
                                                 name:kUpdatedElectricityUsageKey object:nil];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];

    [self.notificationCenter removeObserver:self];
}

- (void)updateElectricityUsage
{
    self.electricityUsage = [self.stateManager electricityUsage];
}

- (CGFloat)tabBarHeight
{
    return self.tabBarController.tabBar.frame.size.height;
}

@end

Что я должен проверить?

  • Наблюдатель от kUpdatedElectricityUsageKey добавлен
  • self.electricityUsage становится примером QBElectricityUsage
  • tabBarHeight возвращается
  • Наблюдатель от kUpdatedElectricityUsageKey устранен

Я что-то упускаю из-за того, что я должен проверить, или из-за того, что я действительно не должен?

Как мне проверить?

Поэтому я пытаюсь сделать это, используя Specta и Expexta. Если мне нужно что-то издеваться, я бы использовал OCMockito.

Я действительно не знаю, как проверить, добавлен / удален наблюдатель. Я вижу следующее в документации Expexta, но не уверен, насколько это уместно / как его использовать:

expect(^{ /* code */ }).to.notify(@"NotificationName"); passes if a given block of code generates an NSNotification named NotificationName.

expect(^{ /* code */ }).to.notify(notification); passes if a given block of code generates an NSNotification equal to the passed notification.

Чтобы проверить это self.electricityUsage становится примером QBElectricityUsage Я мог бы создать категорию, которая имеет метод, который просто притворяется, что уведомление запущено и вызывает updateElectricityUsage метод, но это лучший способ?

А что касается tabBarHeightДолжен ли я просто проверить, что он возвращает действительный CGFloat и не волнуйтесь, что это за значение?


ОБНОВИТЬ

Я изменил свой viewWillAppear способ выглядеть так:

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self addNotificationObservers];
}

- (void)addNotificationObservers
{
    [self.notificationCenter addObserver:self selector:@selector(updateElectricityUsage)
                                    name:kUpdatedElectricityUsageKey object:nil];
}

И тогда я создал следующий тест:

#import "Specs.h"

#import "QBElectricityBaseVC.h"
#import "ElectricityConstants.h"

SpecBegin(QBElectricityBaseVCSpec)

    describe(@"QBElectricityBaseVC", ^{
        __block QBElectricityBaseVC *electricityBaseVC;
        __block NSNotificationCenter *mockNotificationCenter;

        beforeEach(^{
            electricityBaseVC = [QBElectricityBaseVC new];
            mockNotificationCenter = mock([NSNotificationCenter class]);
            electricityBaseVC.notificationCenter = mockNotificationCenter;
        });

        afterEach(^{
            electricityBaseVC = nil;
            mockNotificationCenter = nil;
        });

        it(@"should have a notification observer for updated electricity usage", ^{
            [electricityBaseVC addNotificationObservers];
            [verify(mockNotificationCenter) addObserver:electricityBaseVC selector:@selector(updateElectricityUsage)
                                               name:kUpdatedElectricityUsageKey object:nil];
        });
    });

SpecEnd

Этот тест сейчас проходит, но это правильный / лучший способ проверить это?

2 ответа

Вы только что почувствовали один большой недостаток iOS ViewControllers: они сосут на тестируемость.

  • ViewControllers смешивают логику управления видом и моделью

Другая большая проблема с MVC заключается в том, что он не рекомендует разработчикам писать модульные тесты. Поскольку контроллеры представлений смешивают логику управления представлениями с бизнес-логикой, разделение этих компонентов для модульного тестирования становится непростой задачей. Задача, которую многие игнорируют в пользу... просто ничего не проверяя.

Статья - источник

Возможно, вам следует подумать об использовании MVVM вместо этого. Это отличная статья, объясняющая разницу между iOS MVC и MVVM.

Преимущество использования MVVM заключается в том, что вы можете использовать привязку данных с использованием Reactive Cocoa. Вот учебник, который объяснит привязку данных с MVVM и реактивное программирование в iOS.

Я следую 2 практикам для тестирования частей UIViewController.

  1. MVVM - с помощью шаблона MVVM вы можете очень легко выполнить модульное тестирование содержимого ваших представлений в своих модульных тестах для ваших классов ViewModel. Это также делает логику ViewController очень легкой, поэтому вам не нужно писать столько тестов пользовательского интерфейса, чтобы охватить все эти сценарии.
  2. KIF - тогда для тестирования пользовательского интерфейса я использую KIF, потому что его актер помогает обрабатывать асинхронные и просматривать задержки загрузки. С помощью KIF я могу опубликовать уведомление из своего кода, и мой тест будет ждать, чтобы увидеть результаты моего обработчика уведомлений в представлении.

Между этими двумя системами я могу протестировать практически все, а затем очень легко написать тесты пользовательского интерфейса, чтобы охватить заключительные части.

Также обратите внимание на ваш код: я бы не добавил ваших наблюдателей в viewWillAppear, потому что он вызывается более одного раза. Тем не менее, это может не быть проблемой, так как вы, вероятно, не получите избыточные вызовы вашего обработчика из-за объединения уведомлений.

Другие вопросы по тегам