Wednesday, August 1, 2012

XNA and System Testing

In the previous post, we discussed the topic of XNA and Data Driven Design.
Now, let's build on this information to discuss System Testing with XNA.

Note: this post includes complete code sample on CodePlex.
Download code sample here.

System Testing
System Testing is the practice in which complete, end-to-end software is tested to evaluate the system’s compliance with its specified requirements.

Game development that implements data driven design may find system testing effective because it can be used to validate all data before fully integrating into the game.

When experimenting with new ideas, all changes made to game data must be valid, otherwise it may be possible to easily break the system, or have the system behave in an unpredictable fashion.

System testing allows for this feedback quickly, efficiently and without need to execute the entire game.

Here is a common workflow to integrate system testing into game development:
  • Write game code and edit game data
  • Run system tests and validate
  • Launch game with current data
  • Update any game data as necessary
  • Re-run system tests and validate
  • Hot swap updated game data
  • Repeat process: no constant build / deploy
Example
As an example, let’s build a basic snake simulation to demonstrate XNA and System Testing.
First, create a system that can parse text files on demand (not just at start up):
public interface IFileManager
{
 IList<String> LoadTxt(String file);
 T LoadXml<T>(String file);
 XElement LoadXElement(String file);
}
Next, create a component to manage all data that will be loaded into the game:
public interface IDataManager
{
 AllData LoadData(String[] files);
}
Sample
The following code sample builds a simulation to integrate an unlimited number of snakes into a maze using data driven design and validates each snake through system tests.
First, define a simple text file that will store all 3x possible maze sizes: Small, Medium, and Large.
Next, define an XML file that stores an unlimited number of snakes available; each snake defines:
  • Start position in maze and direction
  • Length of the snake tail
  • Time delay on each tile
  • No. tiles before direction change
<?xml version="1.0"?>
<ArrayOfSnakeData>
 <SnakeData>
  <StartPosX>10</StartPosX>
  <StartPosY>5</StartPosY>
  <Direction>Left</Direction>
  <TailLength>20</TailLength>
  <TimeOnTile>50</TimeOnTile>
  <TileToMove>10</TileToMove>
 </SnakeData>
</ArrayOfSnakeData>
Finally, write system tests to validate all data before fully integrating into the game.
Note: an IoC Container will be used to construct all components used throughout.

FILE MANAGER TESTS
[TestFixture]
public class FileManagerTests
{
 // System under test.
 private IFileManager fileManager;
 
 [SetUp]
 public void SetUp()
 {
  fileManager = IoCContainer.Resolve<IFileManager>();
 }

 [Test]
 public void BoardDataTest()
 {
  String boardFile = GetPath("BoardData.txt");

  IList<String> lines = fileManager.LoadTxt(boardFile);
  String line = lines[0];

  GameSize gameSize = (GameSize)Enum.Parse(typeof(GameSize), line, true);
  Assert.AreEqual(GameSize.Small, gameSize);
 }

 [Test]
 public void SnakeDataTest()
 {
  String snakeFile = GetPath("SnakeData.xml");
  SnakeData[] snakeData = fileManager.LoadXml<SnakeData[]>(snakeFile);
  Assert.AreEqual(1, snakeData.Length);

  SnakeData snake = snakeData[0];
  Assert.AreEqual(10, snake.StartPosX);
  Assert.AreEqual(5, snake.StartPosY);
  Assert.AreEqual(Direction.Left, snake.Direction);
  Assert.AreEqual(20, snake.TailLength);
  Assert.AreEqual(50, snake.TimeOnTile);
  Assert.AreEqual(10, snake.TileToMove);
 }
}
DATA MANAGER TESTS
[TestFixture]
public class DataManagerTests
{
 // System under test.
 private IDataManager dataManager;

 [SetUp]
 public void SetUp()
 {
  dataManager = IoCContainer.Resolve<IDataManager>();
 }

 [Test]
 public void AllDataTest()
 {
  String boardFile = GetPath("BoardData.txt");
  String snakeFile = GetPath("SnakeData.xml");

  String[] files = new[] { boardFile, snakeFile };
  AllData data = dataManager.LoadData(files);

  Assert.AreEqual(GameSize.Small, data.GameSize);
  Assert.AreEqual(1, data.SnakeData.Length);
  
 }
}
Download code sample here.

Summary
The snake simulation demonstrates how to integrate an unlimited number of snakes into a maze using data driven design: simply update the XML file to add more snakes without constant need to recompile and validate all data quickly and efficiently through system tests.

In reality, more complex text files will be loaded into a game, for example, to build game levels. Level data may require more complex validation to ensure all data is true and correct, and that all rules are observed before the level is actually loaded into the game.

This will be the topic in the next post.

No comments:

Post a Comment