Как выполнить модульное тестирование 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 смешивают логику управления видом и моделью
- Это приводит к массовым ViewControllers
- Это нарушает правило единой ответственности
- Это делает код не пригодным для повторного использования
Другая большая проблема с MVC заключается в том, что он не рекомендует разработчикам писать модульные тесты. Поскольку контроллеры представлений смешивают логику управления представлениями с бизнес-логикой, разделение этих компонентов для модульного тестирования становится непростой задачей. Задача, которую многие игнорируют в пользу... просто ничего не проверяя.
Возможно, вам следует подумать об использовании MVVM вместо этого. Это отличная статья, объясняющая разницу между iOS MVC и MVVM.
Преимущество использования MVVM заключается в том, что вы можете использовать привязку данных с использованием Reactive Cocoa. Вот учебник, который объяснит привязку данных с MVVM и реактивное программирование в iOS.
Я следую 2 практикам для тестирования частей UIViewController.
- MVVM - с помощью шаблона MVVM вы можете очень легко выполнить модульное тестирование содержимого ваших представлений в своих модульных тестах для ваших классов ViewModel. Это также делает логику ViewController очень легкой, поэтому вам не нужно писать столько тестов пользовательского интерфейса, чтобы охватить все эти сценарии.
- KIF - тогда для тестирования пользовательского интерфейса я использую KIF, потому что его актер помогает обрабатывать асинхронные и просматривать задержки загрузки. С помощью KIF я могу опубликовать уведомление из своего кода, и мой тест будет ждать, чтобы увидеть результаты моего обработчика уведомлений в представлении.
Между этими двумя системами я могу протестировать практически все, а затем очень легко написать тесты пользовательского интерфейса, чтобы охватить заключительные части.
Также обратите внимание на ваш код: я бы не добавил ваших наблюдателей в viewWillAppear, потому что он вызывается более одного раза. Тем не менее, это может не быть проблемой, так как вы, вероятно, не получите избыточные вызовы вашего обработчика из-за объединения уведомлений.