Jest kilka dobrych postów na tym forum na temat testów jednostkowych. Oto moje osobiste podejście do testów jednostkowych w XNA:
- Zignoruj Draw() metoda
- Isolate skomplikowane zachowanie w swoich własnych metod klasy
- przetestować trudne rzeczy, nie pocą się odpoczynek
Oto przykład testu, który potwierdza, że moja metoda Update przenosi Entry na właściwą odległość między wywołaniami Update(). (Używam NUnit.) Wyciąłem kilka linii z różnymi wektorami ruchu, ale wpadłeś na pomysł: nie powinieneś potrzebować Game, aby prowadzić testy.
[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);
}
}
Edycja: Niektóre inne notatki z uwagami:
My Entity Class: Wybrałem zawinąć wszystko moja gra obiektów w centralnej klasy podmiotu, który wygląda mniej więcej tak:
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);
}
}
}
Daje mi to dużą elastyczność w tworzeniu podklas Entity (AIEntity, NonInteractiveEntity ...), które prawdopodobnie zastępują Update(). Pozwala mi to również na podklasę Drawable swobodnie, bez piekielnych podklas n^2, takich jak AnimatedSpriteAIEntity
,i AnimatedSpriteNoninteractiveEntity
. Zamiast tego, można to zrobić:
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);
Moja rozciągliwej klasa: Mam klasy abstrakcyjnej, z których wszystkie moje narysowane obiekty są pochodną. Wybrałem klasę abstrakcyjną, ponieważ niektóre zachowania będą udostępniane. Byłoby zupełnie do przyjęcia, aby zdefiniować to jako interface, jeśli nie jest to prawdą w twoim kodzie.
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);
}
Podklasy definiują własną logikę rysowania. W przykładzie zbiornika, można zrobić kilka rzeczy:
- Dodaj nowy podmiot dla każdego pocisku
- Dodać do klasy TankEntity który określa listy, a zastępuje Draw() iteracyjne nad Bullets (które określają metoda wyciągania własnych)
- Zrób ListDrawable
Oto przykład implementacja ListDrawable, ignorując kwestię jak zarządzać samą listę.
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);
}
}
}
Powodzenia. W SlimDX analizujemy przejście hurtowe na interfejsy do obsługi tego przypadku użycia. – Promit
@Promit, Interfejsy nie są srebrną kulą ... w niektórych przypadkach klasa abstrakcyjna to właściwy wybór. Chociaż interfejsy są w pewnych sytuacjach użyteczne i poprawne, nie zawsze jest to właściwe narzędzie :-) –
@Joel Martinez - jeśli chodzi o kpiny/fałszowanie, kiedy interfejsy nie są odpowiednim narzędziem? –