Please join me at my new location bryankyle.com

Friday, May 23, 2008

Unit Testing Graphics Code

I was reading reddit today when I came across a post about the popularity of unit tests waning. After reading a few of the comments I came across an interesting one:

How do you assert that this image draws centered in this button? Writing an automated test for that is fiendishly hard. The few automated tests I attempted to write for graphical components were fragile, produced lots of failures-that-weren't, and became a maintenance headache. Meanwhile, testing the button by just-looking-at-the-damn-thing was trivial, and what I should have been doing all along.

I've recently had to write unit tests for graphics code so I thought I'd share my experience testing that code.

One of the benefits of unit testing is that it forces you to think about your structure. Typically if you can't unit test something then it isn't factored properly, and drawing routines are no different. In the cited example, the developer needs to test to ensure that an image is displayed in the center of the components canvas. Let's assume that the code that needs to be tested looks something like the following.

public class Button {

   private Image image = null;

   public Button(Image image) {
      this.image = image;
   }

   ...

   public void paint(Graphics g) {
      super.paint(g);

      int x = (this.getWidth() / 2) - (this.image.getWidth() / 2);
      int y = (this.getHeight() / 2) - (this.image.getHeight() / 2);

      g.drawImage(this.image, x, y);
   }
}

How can we test this? Well, looking at the code I see a few approaches to testing this. One way is to stub out the Image and Graphics classes and test to make sure that methods are called with the arguments we expect. Another is to factor out the logic inside the paint method.

Stubbing out the classes is you're best bet here if you can. Given that you will probably need to test other code that uses these classes I think it'd be a wise investment of your time.But what if you can't stub out the classes? What if the classes don't implement any interfaces, or the classes are declared as final?

But what if you can't stub out the classes? What if the classes don't implement any interfaces, or the classes are declared as final? If you find yourself in this situation, you can break the logic of the paint method out into smaller utility methods. From there you can call those methods directly and assert that the results they give are what you expect.

public class Button {

   private Image image = null;

   public Button(Image image) {
      this.image = image;
   }

   ...

   public void paint(Graphics g) {
      super.paint(g);
      g.drawImage(this.image, calculateImagePositionX(), calculateImagePositionY());
   }

   private Point calculateImagePositionX() {
      return (this.getWidth() / 2) - (this.image.getWidth() / 2);
   }

   private int calculateImagePositionY() {
      return (this.getHeight() / 2) - (this.image.getHeight() / 2);
   }
}

Granted this example is a bit trivial, but I think it goes to show that it is possible to write unit tests for graphics code. That being said, without physically looking at the canvas there's no way of being 100% sure, but at least you have tests that can run as part of any build.