Tuesday, August 24, 2010

Unit testing rendering code in Slick

Several weeks ago, I developed a product backlog for my game programming class, and I tagged the first two tasks --- the highest priority tasks --- for demonstration purposes. Specifically, the plan was to complete these two tasks on Wednesday of the first week of classes (tomorrow):
  • Create a Position component.
  • Create an ImageRenderable component with corresponding ImageRenderSystem.
The first is quite easy and provides a good introduction to TDD. The Position component doesn't do anything: it's just a bounding rectangle in 2D space, so the test is really just over accessors and mutators.

For the second, I wrote a little note to myself that my unit test should load a 1x1 image and check that the color values match as expected. Rather routinely this morning, I decided to make sure I remembered how to do this before the class meeting tomorrow. The best I can figure now is that I never actually wrote this before, I only saw that Slick's Graphics class has a getPixel method. The mere presence of this method means that the test should be a simple one, right?

Well, no. Slick sits on top of LWJGL, and so one cannot simply create a Graphics object: there is all sorts of OpenGL initialization that happens behind the scenes. In fact, a similar thing happens when just trying to create a Slick Image object: this fails if done outside of the context of a Slick Game in a GameContainer since the OpenGL subsystem is not initialized.

Mock objects really won't help here, since the best one could do is check whether the ImageRenderSystem calls a specific rendering method on a mock Graphics object. This would be fragile and only test how the ImageRenderSystem uses the Slick API, not whether it behaves as it should.

After some lunchtime tinkering, I've come up with a solution that seems to work fine. The crux of it is to create one-off BasicGame implementations within the test case and embed the test code within. BasicGame is the default starting place for a Slick game implementation, itself implementing the Game interface. BasicGame is abstract and requires implementation of three methods: init, update, and render. So, let's look at the "easier" one first. This is what ImageRenderable looks like:

import org.newdawn.slick.Image;

public class ImageRenderable implements Component {
 private Image image;

  public Image image() {
    return image;
  }

  public ImageRenderable image(Image image) {
    this.image = image;
    return this;
  }
}

Easy enough, as long as you don't mind my Smalltalk-esque style — using the same method name for both an accessor and mutator, and returning this from the mutator.
This conceptually-simple unit test just checks that if we set an image, that the property accessor returns that image:

@Test
    public void testImageAccessorAndMutator() {
        final String RESOURCE_LOC = "images/white-1x1.png";
        try {
            new AppGameContainer(new BasicGame("Test") {
                @Override
                public void render(GameContainer container, Graphics g)
                        throws SlickException {
                    final Image image = new Image(RESOURCE_LOC);
                    ImageRenderable ir = new ImageRenderable().image(image);
                    assertEquals(image, ir.image());
                    container.exit();
                }

                @Override
                public void update(GameContainer container, int delta)
                        throws SlickException {
                }

                @Override
                public void init(GameContainer container) throws SlickException {
                }
            }).start();

        } catch (SlickException e) {
            fail("Failed to create game: " + e);
        }
    }

Here's a breakdown:

  • The game needs a container in order to run, so we create an AppGameContainer.
    This is the class used when launching games as applications, in contrast with
    AppletGameContainer.
  • The inner class overrides the three methods required, but only render is interesting to this test, so it's the only one that matters.
  • The image variable references the actual Slick image object, which is then referenced by ir. We don't actually need any Entity objects here: we're just testing the component.
  • assertEquals ensures that the image we put into the component is the one we get out.
  • After the test, we call the container's exit method to gracefully shut down Slick.
  • The start() call on the game container actually starts the test game.

Unit tests for accessors and mutators are nigh unnecessary, but this set-up allows us to do something more interesting with the system that processes these components. Here's a simple implementation of ImageRenderSystem, which assumes that its Entity arguments have both Position and ImageRenderable components.

import java.util.List;

import org.newdawn.slick.Graphics;

public class ImageRenderSystem {

    public void render(Graphics g, List entities) {
        for (Entity e : entities)
            render(g, e);
    }

    public void render(Graphics g, Entity e) {
        Position p = e.as(Position.class);
        ImageRenderable i = e.as(ImageRenderable.class);

        g.drawImage(i.image(), p.x(), p.y());
    }
}

Simple. Here's the corresponding unit test (which was written first, natch).

@Test
    public void testImageRendering() throws Exception {
        new AppGameContainer(new BasicGame("Test") {
            private Entity e;

            @Override
            public void render(GameContainer container, Graphics g)
                    throws SlickException {
                ImageRenderSystem sys = new ImageRenderSystem();
                sys.render(g, e);
                assertEquals(Color.white, g.getPixel(0, 0));
                container.exit();
            }

            @Override
            public void update(GameContainer container, int delta)
                    throws SlickException {
            }

            @Override
            public void init(GameContainer container) throws SlickException {
                BasicEntityManager mgr = new BasicEntityManager();
                e = mgr.createEntity();
                e.add(new Position().x(0).y(0));
                e.add(new ImageRenderable().image(new Image(
                        "assets/white-1x1.png")));
            }
        }).start();
    }

This has the same structure as the previous one. I am using the init method to set up the test. Technically, this could be done in render as in the previous example, but it seems that init is a good place to do initialization. The render method calls out to the ImageRenderSystem render method, and then we check that the pixel in the upper-left corner is indeed a white one. To finish the test, we should check that the rest are black, but that's left as an exercise for the reader.

This shows how to set up rendering-related unit tests in Slick. The real moral of the story, though, is: make sure you know what you're doing before you show up in class. This was supposed to take about fifteen minutes, and it took me over an hour to puzzle through all the parts. It's always easier the second time.

Thanks to David Craft for the post explaining how to set up SyntaxHighlighter in blogger.

No comments:

Post a Comment