На этом форуме есть несколько хороших сообщений на тему юнит-тестирования. Вот мой личный подход к модульному тестированию в XNA:
- Игнорировать метод Draw ()
- Изолировать сложное поведение в ваших собственных методах класса
- Испытай хитрые вещи, не парься по остальным
Вот пример теста для подтверждения того, что мой метод Update перемещает объекты на правильное расстояние между вызовами Update (). (Я использую NUnit .) Я обрезал пару строк с разными векторами движения, но вы поняли: вам не нужна игра для проведения тестов.
[TestFixture]
public class EntityTest {
[Test]
public void testMovement() {
float speed = 1.0f; // units per second
float updateDuration = 1.0f; // seconds
Vector2 moveVector = new Vector2(0f, 1f);
Vector2 originalPosition = new Vector2(8f, 12f);
Entity entity = new Entity("testGuy");
entity.NextStep = moveVector;
entity.Position = originalPosition;
entity.Speed = speed;
/*** Look ma, no Game! ***/
entity.Update(updateDuration);
Vector2 moveVectorDirection = moveVector;
moveVectorDirection.Normalize();
Vector2 expected = originalPosition +
(speed * updateDuration * moveVectorDirection);
float epsilon = 0.0001f; // using == on floats: bad idea
Assert.Less(Math.Abs(expected.X - entity.Position.X), epsilon);
Assert.Less(Math.Abs(expected.Y - entity.Position.Y), epsilon);
}
}
Редактировать: Некоторые другие заметки из комментариев:
Мой класс сущности :
Я решил обернуть все свои игровые объекты в централизованный класс Entity, который выглядит примерно так:
public class Entity {
public Vector2 Position { get; set; }
public Drawable Drawable { get; set; }
public void Update(double seconds) {
// Entity Update logic...
if (Drawable != null) {
Drawable.Update(seconds);
}
}
public void LoadContent(/* I forget the args */) {
// Entity LoadContent logic...
if (Drawable != null) {
Drawable.LoadContent(seconds);
}
}
}
Это дает мне большую гибкость для создания подклассов Entity (AIEntity, NonInteractiveEntity ...), которые, вероятно, переопределяют Update (). Это также позволяет мне создавать подклассы Drawable свободно, без черта n ^ 2 подклассов, таких как AnimatedSpriteAIEntity
, ParticleEffectNonInteractiveEntity
и AnimatedSpriteNoninteractiveEntity
. Вместо этого я могу сделать это:
Entity torch = new NonInteractiveEntity();
torch.Drawable = new AnimatedSpriteDrawable("Animations\litTorch");
SomeGameScreen.AddEntity(torch);
// let's say you can load an enemy AI script like this
Entity enemy = new AIEntity("AIScritps\hostile");
enemy.Drawable = new AnimatedSpriteDrawable("Animations\ogre");
SomeGameScreen.AddEntity(enemy);
Мой класс Drawable : У меня есть абстрактный класс, из которого получены все мои нарисованные объекты. Я выбрал абстрактный класс, потому что часть поведения будет распространена. Было бы вполне приемлемо определить это как интерфейс вместо этого, если это не соответствует вашему коду.
public abstract class Drawable {
// my game is 2d, so I use a Point to draw...
public Point Coordinates { get; set; }
// But I usually store my game state in a Vector2,
// so I need a convenient way to convert. If this
// were an interface, I'd have to write this code everywhere
public void SetPosition(Vector2 value) {
Coordinates = new Point((int)value.X, (int)value.Y);
}
// This is overridden by subclasses like AnimatedSprite and ParticleEffect
public abstract void Draw(SpriteBatch spriteBatch, Rectangle visibleArea);
}
Подклассы определяют свою собственную логику рисования. В вашем примере с танком вы можете сделать несколько вещей:
- Добавить новую сущность для каждой пули
- Создайте класс TankEntity, который определяет List, и переопределяет Draw () для итерации по Bullets (которые определяют собственный метод Draw)
- Сделать ListDrawable
Вот пример реализации ListDrawable, игнорирующий вопрос о том, как управлять самим списком.
public class ListDrawable : Drawable {
private List<Drawable> Children;
// ...
public override void Draw(SpriteBatch spriteBatch, Rectangle visibleArea) {
if (Children == null) {
return;
}
foreach (Drawable child in children) {
child.Draw(spriteBatch, visibleArea);
}
}
}