Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can animation be unit tested?

Modern user interfaces, especially MacOS and iOS, have lots of “casual” animation -- views that appear through brief animated sequences largely orchestrated by the system.

[[myNewView animator] setFrame: rect]

Occasionally, we might have a slightly more elaborate animation, something with an animation group and a completion block.

Now, I can imagine bug reports like this:

Hey -- that nice animation when myNewView appears isn't happening in the new release!

So, we'd want unit tests to do some simple things:

  • confirm that the animation happens
  • check the duration of the animation
  • check the frame rate of the animation

But of course all these tests have to be simple to write and mustn't make the code worse; we don’t want to spoil the simplicity of the implicit animations with a ton of test-driven complexity!

So, what is a TDD-friendly approach to implementing tests for casual animations?


Justifications for unit testing

Let's take a concrete example to illustrate why we'd want a unit test. Let's say we have a view that contains a bunch of WidgetViews. When the user makes a new Widget by double-clicking, it’s supposed to initially appear tiny and transparent, expanding to full size during the animation.

Now, it's true that we don't want to need to unit test system behavior. But here are some things that might go wrong because we fouled things up:

  1. The animation is called on the wrong thread, and doesn't get drawn. But in the course of the animation, we call setNeedsDisplay, so eventually the widget gets drawn.

  2. We're recycling disused widgets from a pool of discarded WidgetControllers. NEW WidgetViews are initially transparent, but some views in the recycle pool are still opaque. So the fade doesn't happen.

  3. Some additional animation starts on the new widget before the animation finishes. As a result, the widget begins to appear, and then starts jerking and flashing briefly before it settles down.

  4. You made a change to the widget's drawRect: method, and the new drawRect is slow. The old animation was fine, but now it's a mess.

All of these are going to show up in your support log as, "The create-widget animation isn't working anymore." And my experience has been that, once you get used to an animation, it’s really hard for the developer to notice right away that an unrelated change has broken the animation. That's a recipe for unit testing, right?

like image 420
Mark Bernstein Avatar asked Feb 06 '13 16:02

Mark Bernstein


People also ask

How can unit testing be done?

Unit tests can be performed manually or automated. Those employing a manual method may have an instinctual document made detailing each step in the process; however, automated testing is the more common method to unit tests. Automated approaches commonly use a testing framework to develop test cases.

What must be tested by a unit test?

A unit test is a way of testing a unit - the smallest piece of code that can be logically isolated in a system. In most programming languages, that is a function, a subroutine, a method or property. The isolated part of the definition is important.

What is unit test assessment?

Unit Testing is a type of software testing where individual units or components of a software are tested. The purpose is to validate that each unit of the software code performs as expected. Unit Testing is done during the development (coding phase) of an application by the developers.


1 Answers

The animation is called on the wrong thread, and doesn't get drawn. But in the course of the animation, we call setNeedsDisplay, so eventually the widget gets drawn.

Don't unit test for this directly. Instead use assertions and/or raise exceptions when animation is on the incorrect thread. Unit test that the assertion will raise an exception appropriately. Apple does this aggressively with their frameworks. It keeps you from shooting yourself in the foot. And you will know immediately when you are using an object outside of valid parameters.

We're recycling disused widgets from a pool of discarded WidgetControllers. NEW WidgetViews are initially transparent, but some views in the recycle pool are still opaque. So the fade doesn't happen.

This is why you see methods like dequeueReusableCellWithIdentifier in UITableView. You need a public method to get the reused WidgetView which is the perfectly opportunity to test properties like alpha are reset appropriately.

Some additional animation starts on the new widget before the animation finishes. As a result, the widget begins to appear, and then starts jerking and flashing briefly before it settles down.

Same as number 1. Use assertions to impose your rules on your code. Unit test that the assertions can be triggered.

You made a change to the widget's drawRect: method, and the new drawRect is slow. The old animation was fine, but now it's a mess.

A unit test can be just timing a method. I often do this with calculations to ensure they stay within a reasonable time limit.

-(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);
    }
}
like image 143
Fruity Geek Avatar answered Oct 10 '22 18:10

Fruity Geek