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

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

[[myNewView animator] setFrame: rect]

иногда у нас может быть немного более сложная анимация, что-то с группой анимации и блоком завершения.

теперь я могу представить себе такие сообщения об ошибках:

Эй -- эта хорошая анимация, когда появляется myNewView, не происходит в новом отпустите!

Итак, мы хотели бы, чтобы модульные тесты делали некоторые простые вещи:

  • подтвердите, что анимация происходит
  • проверьте продолжительность анимации
  • проверьте частоту кадров анимации

но, конечно, все эти тесты должны быть простыми для записи и не должны ухудшать код; мы не хотим портить простоту неявных анимаций тонной тест-драйв сложность!

и что такое TDD-дружественный подход к реализации тестов для случайных анимаций?


оснований для модульного тестирования

давайте возьмем конкретный пример, чтобы проиллюстрировать, почему нам нужен модульный тест. Предположим, у нас есть представление, содержащее кучу WidgetViews. Когда пользователь делает новый виджет двойным щелчком мыши, он должен сначала казаться крошечным и прозрачным, расширяясь до полного размера во время анимация.

теперь, это правда, что нам не нужно поведение системы модульного тестирования. Но вот некоторые вещи, которые могут пойти не так, потому что мы все испортили:

  1. анимация вызывается в неправильном потоке и не рисуется. Но в процессе анимации мы вызываем setNeedsDisplay, поэтому в конечном итоге виджет рисуется.

  2. мы перерабатываем неиспользуемые виджеты из пула выброшенных WidgetControllers. НОВЫЙ WidgetViews изначально прозрачны, но некоторые представления в пуле рециркуляции по-прежнему непрозрачны. Так что увядания не происходит.

  3. некоторая дополнительная анимация начинается на новом виджете до завершения анимации. В результате виджет начинает появляться, а затем начинает дергаться и мигать ненадолго, прежде чем он успокоится.

  4. вы внесли изменения в метод drawRect: виджета, и новый drawRect является медленным. Старая анимация была хороша, но теперь это бардак.

все они будут отображаться в вашем журнале поддержки как "анимация create-widget больше не работает."И мой опыт заключается в том, что, как только вы привыкнете к анимации, разработчику действительно трудно сразу заметить, что несвязанное изменение нарушило анимацию. Это рецепт для модульного тестирования, верно?

3 ответов


анимация вызывается в неправильном потоке и не рисуется. Но в процессе анимации мы называем setNeedsDisplay, поэтому в конце концов виджет рисуется.

не unit-тест для этого напрямую. Вместо этого используйте утверждения и / или вызывайте исключения, когда анимация находится в неправильном потоке. модульный тест, что утверждение вызовет исключение соответствующим образом. Apple делает это агрессивно со своими фреймворками. Это удерживает тебя от стреляешь себе в ногу. И вы сразу узнаете, когда вы используете объект вне допустимых параметров.

мы перерабатываем неиспользуемые виджеты из пула выброшенных WidgetControllers. Новые WidgetViews изначально прозрачны, но некоторые представления в пуле рециркуляции по-прежнему непрозрачны. Так что увядание не случаться.

вот почему вы видите такие методы, как dequeueReusableCellWithIdentifier в UITableView. Вам нужен общественный способ получить повторно WidgetView, который является ли прекрасная возможность проверить свойства, такие как альфа, сбрасываются соответствующим образом.

некоторые дополнительные анимации начинается на новый виджет перед анимация заканчивается. В результате, виджет начинает появляться, а затем начинает дергаться и мигать, пока не успокоится.

то же, что и номер 1. Используйте утверждения, чтобы навязать свои правила коду. Модульный тест, который может быть вызван утверждениями.

вы внесли изменения к методу drawRect: виджета и к новому drawRect медленно. Старая анимация была хороша, но теперь это беспорядок.

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

-(void)testAnimationTime
{
    NSDate * start = [NSDate date];
    NSView * view = [[NSView alloc]init];
    for (int i = 0; i < 10; i++)
    {
        [view display];
    }

    NSTimeInterval timeSpent = [start timeIntervalSinceNow] * -1.0;

    if (timeSpent > 1.5)
    {
        STFail(@"View took %f seconds to calculate 10 times", timeSpent);
    }
}

Я могу прочитать ваш вопрос двумя способами, поэтому я хочу разделить их.

Если вы спрашиваете: "как я могу проверить, что система действительно выполняет анимацию, которую я запрашиваю?- Я бы сказал, что оно того не стоит. Мой опыт говорит мне, что это много боли, не много выгоды и в таком случае тест будет хрупким. Я обнаружил, что в большинстве случаев, когда мы вызываем API операционной системы, это обеспечивает наибольшее значение, чтобы предположить, что они работают и будут продолжать работать пока не доказано обратное.

Если вы спрашиваете: "как я могу проверить, что мой код запрашивает правильную анимацию?- тогда это более интересно. Вы хотите основу для теста удваивается как OCMock. Или вы можете использовать киви, который является моим любимым фреймворком тестирования и имеет встроенные stubbing и mocking.

С киви вы можете сделать что-то вроде следующего, например:

id fakeView = [NSView nullMock];
id fakeAnimator = [NSView nullMock];
[fakeView stub:@selector(animator) andReturn:fakeAnimator];
CGRect newFrame = {.origin = {2,2}, .size = {11,44}};
[[[fakeAnimator should] receive] setFrame:theValue(newFrame)];

[myController enterWasClicked:nil];

вы не хотите на самом деле ждать анимации; это займет время, необходимое для запуска анимации. Если у вас есть несколько тысяч тестов, это может сложиться.

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

#import "UIView+SpecFlywheel.h"

@implementation UIView (SpecFlywheel)

#pragma mark - Animation
+ (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion {
    if (animations)
        animations();
    if (completion)
        completion(YES);
}

@end

выше просто выполняет блок анимации немедленно, и блок завершения немедленно, если это предусмотрено, а также.