11

最新のユーザー インターフェイス、特に MacOS と iOS には、多くの「カジュアルな」アニメーションがあります。これらのビューは、主にシステムによって調整された短いアニメーション シーケンスを通じて表示されます。

[[myNewView animator] setFrame: rect]

場合によっては、アニメーション グループと完了ブロックを含む、もう少し精巧なアニメーションを作成することもあります。

さて、次のようなバグ レポートを想像できます。

ちょっと -- myNewView が表示されたときの素敵なアニメーションは、新しいリリースでは発生していません!

したがって、単体テストでいくつかの簡単なことを行う必要があります。

  • アニメーションが発生することを確認する
  • アニメーションの長さを確認する
  • アニメーションのフレームレートを確認する

しかしもちろん、これらすべてのテストは簡単に記述できなければならず、コードを悪化させてはなりません。テスト主導の複雑さで暗黙のアニメーションの単純さを台無しにしたくありません!

では、カジュアル アニメーションのテストを実装するための TDD に適したアプローチとは何でしょうか?


単体テストの正当化

単体テストが必要な理由を説明するために、具体的な例を見てみましょう。多数の WidgetView を含むビューがあるとします。ユーザーがダブルクリックして新しいウィジェットを作成すると、最初は小さくて透明に見え、アニメーション中にフルサイズに拡大するはずです。

さて、システムの動作を単体テストする必要がないことは事実です。しかし、私たちが物事を汚したためにうまくいかないかもしれないことがいくつかあります:

  1. アニメーションが間違ったスレッドで呼び出され、描画されません。しかし、アニメーションの過程で setNeedsDisplay を呼び出すため、最終的にウィジェットが描画されます。

  2. 破棄された WidgetControllers のプールから、使用されていないウィジェットをリサイクルしています。NEW WidgetViews は最初は透明ですが、リサイクル プールの一部のビューはまだ不透明です。だからフェードは起こりません。

  3. アニメーションが終了する前に、新しいウィジェットでいくつかの追加のアニメーションが開始されます。その結果、ウィジェットが表示され始め、落ち着く前に短い間隔で点滅し始めます。

  4. ウィジェットの drawRect: メソッドに変更を加えましたが、新しい drawRect は低速で​​す。昔のアニメは良かったのに今はめちゃくちゃ。

これらはすべて、サポート ログに「ウィジェットの作成アニメーションが機能しなくなりました」と表示されます。私の経験では、一度アニメーションに慣れると、関係のない変更によってアニメーションが壊れていることに開発者がすぐに気付くのは非常に困難です。それが単体テストのレシピですよね?

4

3 に答える 3

4

アニメーションが間違ったスレッドで呼び出され、描画されません。しかし、アニメーションの過程で setNeedsDisplay を呼び出すため、最終的にウィジェットが描画されます。

これを直接単体テストしないでください。アニメーションが不適切なスレッドにある場合は、代わりにアサーションを使用したり、例外を発生させたりしてください。アサーションが適切に例外を発生させる単体テスト。Apple はフレームワークでこれを積極的に行っています。それはあなたが自分の足を撃たないようにします。また、有効なパラメーター以外のオブジェクトを使用している場合はすぐにわかります。

破棄された WidgetControllers のプールから、使用されていないウィジェットをリサイクルしています。NEW 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);
    }
}
于 2013-02-11T23:21:10.497 に答える
0

私はあなたの質問を2つの方法で読むことができるので、それらを分けたいと思います。

「システムが要求したアニメーションを実際に実行することをユニットテストするにはどうすればよいですか?」と質問する場合、それは価値がないと言えます。私の経験によれば、それは多くの利益を伴わない多くの苦痛であり、この種の場合、テストはもろくなります。オペレーティングシステムAPIを呼び出すほとんどの場合、それらが機能し、そうでないことが証明されるまで機能し続けると想定することが最も価値があることがわかりました。

「コードが正しいアニメーションを要求していることをユニットテストするにはどうすればよいですか?」と尋ねる場合、それはさらに興味深いことです。OCMockのようなテストダブル用のフレームワークが必要になります。または、私のお気に入りのテストフレームワークであり、スタブとモックが組み込まれているKiwiを使用することもできます。

Kiwiを使用すると、たとえば次のようなことができます。

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];
于 2013-02-07T14:48:32.030 に答える
-1

実際にアニメーションを待つ必要はありません。アニメーションの実行に時間がかかります。数千のテストがある場合、これは合計される可能性があります。

より効果的なのは、カテゴリ内の 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

上記は単にアニメーションブロックをすぐに実行し、完了ブロックも提供されている場合はすぐに実行します。

于 2013-12-05T20:15:39.437 に答える