1
\$\begingroup\$

I'm finding it a bit tedious to write unit tests for my collision class because there are so many variations of collisions that need testing. I see repetition in the set-up and assertion stages of each test, but I don't see how I can reduce the repetition whilst keeping the code clear and concise.

package tests.kris {
  import asunitsrc.asunit.framework.TestCase;
  import flash.geom.Rectangle;
  import kris.RectangleCollision;
  public class RectangleCollisionTest extends TestCase {
    public function RectangleCollisionTest(testMethod:String):void {
      super(testMethod);
    }

    // detect
    public function detect_should_throw_error_when_both_arguments_are_the_same_object():void {
      var callOn:Rectangle = new Rectangle(0, 0, 0, 0);
      assertThrowsError(RectangleCollision.detect, callOn, callOn);
    }

    public function calling_detect_on_intersecting_rectangles_should_return_true():void {
      var callOn:Rectangle = new Rectangle(1, 1, 5, 5);
      var callWith:Rectangle = new Rectangle(2, 2, 5, 5);
      assertTrue(areColliding(callOn, callWith));
    }

    public function calling_detect_on_rectangles_with_a_touching_side_should_return_false():void {
      var callOn:Rectangle = new Rectangle(0, 0, 5, 5);
      var callWith:Rectangle = new Rectangle(5, 0, 5, 5);
      assertFalse(areColliding(callOn, callWith));
    }

    // Currently only works with rectangles that are rougly the same size and have shallow collisions

    // resolve
    public function calling_resolve_should_throw_error_when_both_arguments_are_the_same_object():void {
      var reactionary:Rectangle = newRectangle(0, 0, 0, 0);
      assertThrowsError(RectangleCollision.resolve, reactionary, reactionary);
    }

    public function calling_reslove_on_not_colliding_rectangles_returns_reactionary():void {
      var reactionary:Rectangle = new Rectangle(0, 0, 100, 100);
      var stationary:Rectangle = new Rectangle(200, 200, 100, 100);
      var result:Rectangle = RectangleCollision.resolve(reactionary, stationary);
      assertPosition(reactionary, 0, 0);
      assertPosition(stationary, 200, 200);
      assertPosition(result, 0, 0);
    }

    public function should_reslove_to_left_correctly_1():void {
      var reactionary:Rectangle = new Rectangle(2, 1, 100, 100);
      var stationary:Rectangle = new Rectangle(100, 0, 100, 100);
      var result:Rectangle = RectangleCollision.resolve(reactionary, stationary);
      assertPosition(reactionary, 2, 1);
      assertPosition(stationary, 100, 0);
      assertPosition(result, 0, 1);
    }

    public function should_reslove_to_left_correctly_2():void {
      var reactionary:Rectangle = new Rectangle(2, -1, 100, 100);
      var stationary:Rectangle = new Rectangle(100, 0, 100, 100);
      var result:Rectangle = RectangleCollision.resolve(reactionary, stationary);
      assertPosition(reactionary, 2, -1);
      assertPosition(stationary, 100, 0);
      assertPosition(result, 0, -1);
    }

    public function should_reslove_to_right_correctly_1():void {
      var reactionary:Rectangle = new Rectangle(98, 1, 100, 100);
      var stationary:Rectangle = new Rectangle(0, 0, 100, 100);
      var result:Rectangle = RectangleCollision.resolve(reactionary, stationary);
      assertPosition(reactionary, 98, 1);
      assertPosition(stationary, 0, 0);
      assertPosition(result, 100, 1);
    }

    public function should_reslove_to_right_correctly_2():void {
      var reactionary:Rectangle = new Rectangle(98, -1, 100, 100);
      var stationary:Rectangle = new Rectangle(0, 0, 100, 100);
      var result:Rectangle = RectangleCollision.resolve(reactionary, stationary);
      assertPosition(reactionary, 98, -1);
      assertPosition(stationary, 0, 0);
      assertPosition(result, 100, -1);
    }

    public function should_reslove_to_top_correctly_1():void {
      var reactionary:Rectangle = new Rectangle(1, 2, 100, 100);
      var stationary:Rectangle = new Rectangle(0, 100, 100, 100);
      var result:Rectangle = RectangleCollision.resolve(reactionary, stationary);
      assertPosition(reactionary, 1, 2);
      assertPosition(stationary, 0, 100);
      assertPosition(result, 1, 0);
    }

    public function should_reslove_to_top_correctly_2():void {
      var reactionary:Rectangle = new Rectangle(-1, 2, 100, 100);
      var stationary:Rectangle = new Rectangle(0, 100, 100, 100);
      var result:Rectangle = RectangleCollision.resolve(reactionary, stationary);
      assertPosition(reactionary, -1, 2);
      assertPosition(stationary, 0, 100);
      assertPosition(result, -1, 0);
    }

    public function should_reslove_to_bottom_correctly_1():void {
      var reactionary:Rectangle = new Rectangle(1, 98, 100, 100);
      var stationary:Rectangle = new Rectangle(0, 0, 100, 100);
      var result:Rectangle = RectangleCollision.resolve(reactionary, stationary);
      assertPosition(reactionary, 1, 98);
      assertPosition(stationary, 0, 0);
      assertPosition(result, 1, 100);
    }

    public function should_reslove_to_bottom_correctly_2():void {
      var reactionary:Rectangle = new Rectangle(-1, 98, 100, 100);
      var stationary:Rectangle = new Rectangle(0, 0, 100, 100);
      var result:Rectangle = RectangleCollision.resolve(reactionary, stationary);
      assertPosition(reactionary, -1, 98);
      assertPosition(stationary, 0, 0);
      assertPosition(result, -1, 100);
    }

    private function assertPosition(rect:Rectangle, x:Number, y:Number):void {
      assertEquals(x, rect.x);
      assertEquals(y, rect.y);
    }

    private function assertThrowsError(... args):void {
      assertTrue(functionThrowsError.apply(null, args));
    }

    private function functionThrowsError(functionToCall:Function, ... args):Boolean {
      try {
          functionToCall.apply(null, args);
          return false;
      } catch (error:Error) {
          return true;
      }
      throw new Error("Should not reach this point");
    }

    private function newRectangle(x:Number = 0, y:Number = 0, w:Number = 0, h:Number = 0):Rectangle {
      return new Rectangle(x, y, w, h);
    }

    private function areColliding(rectangle1:Rectangle, rectangle2:Rectangle):Boolean {
      return RectangleCollision.detect(rectangle1, rectangle2);
    }
  }
}
\$\endgroup\$
1
\$\begingroup\$

I don't know what's best practice, but you could set up the needed Rectangles/Positions/expected Intersections in the setup method, so that the tests itselfs are shorter and faster to understand.

I wrote some intersection unit tests myself the last weeks, i know that sometime you'll get confused about what the test ought to do.

Also, you should maybe check for degenerate cases, i.e. a rectangle with zero width and/or height.

\$\endgroup\$
  • \$\begingroup\$ i would do, but i have to pass the function different arguments each time, so i can't put it in the setUp method. i know these don't test the edge cases enough, i wanted to ask if there was a way to relieve the repetition before i tested it more. \$\endgroup\$ – Kris Welsh Aug 15 '13 at 17:21
  • \$\begingroup\$ Why are you asserting the positions of the input rectangles in every test? Can they be modified by the collision tests? If so, you could extract a method for the repeating creation of rectangles and testing it by making an array with input and expected positions/intersection points. \$\endgroup\$ – weeska Aug 15 '13 at 17:45
  • \$\begingroup\$ No they can't, I put those there to test that they had not moved. If I make a modification later and that modification changes the position of one of the inputs it will fail the test and I will know I have introduced a bug. \$\endgroup\$ – Kris Welsh Aug 15 '13 at 17:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.