I've created a small hierarchy. There is the GameTestBase class and the GameTester interface. My approach is very simillar to the Template Method design pattern where the flow is managed by GameTestBase and GameTester defines the implementation.
Instead of using functions, I used anonymous delegates. This allowed me to isolate the objects under test within each concreate GameTester without GameTestBase having to know about the inner workings of each test. Or have each concrete GameTester query the world from GameTestBase.
Program.cs provides entry into the application so the code to run a test is as follows.
new GameTestBase(new GameObjectRenderTester()).Run();
A GameTester is supplied as a parameter to the GameTestBase constructor to plugin the implementation of the test. Once the GameTestBase object has been constructed we tell it to run the test. And that's it! Woot! Now let's take a look at the GameTester (GameObjectRenderTester).
public class GameObjectRenderTester : GameTester
{
private GameObject ball;
public
GameTestBase.LoadContentDelegate LoadContentDelegate
{
get
{
return new GameTestBase.LoadContentDelegate(
delegate(GameTestBase game)
{
ball = new GameObject(
game.Content.Load("ball"));
});
}
}
public GameTestBase.UpdateDelegate UpdateDelegate
{
get { return null; }
}
public GameTestBase.DrawDelegate DrawDelegate
{
get
{
return new GameTestBase.DrawDelegate(
delegate(GameTestBase game)
{
game.SpriteBatch.Begin();
ball.Render(game.SpriteBatch);
game.SpriteBatch.End();
});
}
}
}
A concrete GameTester object is made up of three public properties: LoadContentDelegate, UpdateDelegate, and DrawDelegate. Each delegate is responsible for implementing a particular section of the application flow. The LoadContentDelegate is responsible for loading any content before the application enters the application loop. The UpdateDelegate is responsible for performing any object animations or prerender operations. Finally, the DrawDelegate delegate is responsible for rendering.
The cool thing about this approach is how the tester can have its own state and it be used within each delegate without any interference from the GameTestBase class. This greatly simplified my tests from prior. I was in static hell!
Now let's have a look at the GameTestBase class to see how it uses the GameTester to run a test.
public GameTestBase(GameTester tester)
{
this.drawDelegate = tester.DrawDelegate;
this.loadContentDelegate =
tester.LoadContentDelegate;
this.updateDelegate = tester.UpdateDelegate;
...
}
protected override void LoadContent()
{
...
if (loadContentDelegate != null)
loadContentDelegate(this);
}
protected override void Update(GameTime gameTime)
{
...
if (updateDelegate != null)
updateDelegate(this, gameTime);
}
protected override void Draw(GameTime gameTime)
{
...
if (drawDelegate != null)
drawDelegate(this);
...
}
First, GameTestBase inherits from the Microsoft.Xna.Framework.Game class. It then overrides the LoadContent(), Update(), and Draw() methods. These are the three methods that the GameTester will implement. As demonstrated in the GameTestBase constructor, the GameTester delegates are stored off and used from within the GameTestBase overrides.
And that's all she wrote folks. It is that easy. It even gives me a nice way to organize my tests. This type of testing is a step up from unit testing because now I can test logic such as collision detection to verify it is calculating and rendering properly. This does not replace my unit testing though, but provides a way to perform tests that don't lend themselves well to unit testing.
So, can I get a hell yeah???
No comments:
Post a Comment