Pregunta

¿Estaba vagando si es posible simular un objeto de Juego para probar mi componente DrawableGameComponent?

Sé que los marcos burlones necesitan una interfaz para funcionar, pero necesito burlarme del Game object.

editar: aquí hay un enlace a la discusión correspondiente en los foros de la Comunidad XNA . ¿Alguna ayuda?

¿Fue útil?

Solución

Hay algunas buenas publicaciones en ese foro sobre el tema de las pruebas unitarias. Este es mi enfoque personal para las pruebas unitarias en XNA:

  • Ignora el método Draw ()
  • Aísle el comportamiento complicado en sus propios métodos de clase
  • Prueba las cosas difíciles, no te preocupes por el resto

Este es un ejemplo de una prueba para confirmar que mi método de Actualización mueve a las Entidades a la distancia correcta entre las llamadas de Actualización (). (Estoy usando NUnit .) Recorté un par de líneas con diferentes vectores de movimiento, pero entiendes la idea: no necesitas un juego para conducir tus pruebas.

[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);
    }
}

Editar: Algunas otras notas de los comentarios:

Mi clase de entidad : Elegí envolver todos mis objetos de juego en una clase de Entidad centralizada, que se parece a esto:

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);
        }
    }
}

Esto me da mucha flexibilidad para crear subclases de Entity (AIEntity, NonInteractiveEntity ...) que probablemente anulen Update (). También me permite crear la subclase Drawable libremente, sin el infierno de n ^ 2 subclases como AnimatedSpriteAIEntity , ParticleEffectNonInteractiveEntity y AnimatedSpriteNoninteractiveEntity . En su lugar, puedo hacer esto:

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);

Mi clase dibujable : tengo una clase abstracta de la que se derivan todos mis objetos dibujados. Elegí una clase abstracta porque se compartirá parte del comportamiento. Sería perfectamente aceptable definir esto como una interface en su lugar, si eso no es cierto de tu código.

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);
}

Las subclases definen su propia lógica de Draw. En el ejemplo de su tanque, podría hacer algunas cosas:

  • Agregar una nueva entidad para cada viñeta
  • Cree una clase TankEntity que defina una lista y anule Draw () para iterar sobre las viñetas (que definen un método de Draw propio)
  • Hacer un ListDrawable

Aquí hay un ejemplo de implementación de ListDrawable, ignorando la pregunta de cómo administrar la lista en sí.

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);
        }
    }
}

Otros consejos

marcos como MOQ y Rhino Mocks no necesitan específicamente una interfaz. También pueden burlarse de cualquier clase no sellada y / o abstracta. El juego es una clase abstracta, así que no deberías tener ningún problema en burlarte de él :-)

Lo único que se debe tener en cuenta al menos en estos dos marcos es que para establecer cualquier expectativa sobre los métodos o las propiedades, deben ser virtuales o abstractos. La razón de esto es que la instancia simulada que genera debe ser capaz de anular. El typemock mencionado por IAmCodeMonkey creo que hay una forma de solucionar esto, pero no creo que typemock sea gratuito, mientras que los dos que mencioné son.

Además, también puedes ver un proyecto mío que podría ayudar a crear pruebas unitarias para juegos XNA sin la necesidad de hacer simulacros: http://scurvytest.codeplex.com/

No tienes que burlarte de ello. ¿Por qué no hacer un objeto de juego falso?

Herede del juego y anula los métodos que pretendes usar en tus pruebas para devolver valores fijos o cálculos de atajos para cualquier método / propiedad que necesites. Luego pasa el falso a tus pruebas.

Antes de burlarse de los marcos, la gente hizo sus propios simulacros / talones / falsos, tal vez no sea tan rápido y fácil, pero todavía puedes.

Puede usar una herramienta llamada TypeMock que creo que no requiere que tenga una interfaz. Su otro y más seguido método es crear una nueva clase que herede de Juego y también implementa una interfaz que usted crea que coincida con el objeto de Juego. Luego puedes codificar contra esa interfaz y pasar tu objeto de juego "personalizado".

public class MyGameObject : Game, IGame
{
    //you can leave this empty since you are inheriting from Game.    
}

public IGame
{
    public GameComponentCollection Components { get; set; }
    public ContentManager Content { get; set; }
    //etc...
}

Es un poco tedioso, pero te permite lograr burlas.

Me llevaré a cuestas tu publicación si no te importa ya que mine parece estar menos activo y ya has puesto tu representante en la línea;)

Mientras leo sus publicaciones (tanto aquí como amp; XNAForum), creo que es el marco el que podría ser más accesible y mi (nuestro) diseño no es perfecto.

El marco podría haber sido diseñado para ser más fácil de ampliar. Me cuesta creerlo Shawn 's argumento principal de un perf hit en las interfaces . Para respaldarme su colega dice que el golpe perfecto podría ser evadido fácilmente.
Tenga en cuenta que el marco ya tiene una interfaz IUpdatable e IDrawable. ¿Por qué no ir hasta el final?

Por otro lado, también creo que, de hecho, mi diseño (y el tuyo) no es perfecto. Donde no dependo del objeto Game, sí dependo mucho del objeto GraphicsDevice. Voy a ver cómo puedo evitar esto. Hará que el código sea más complicado, pero creo que puedo romper esas dependencias.

Para un punto de partida en algo como esto, presionaría Muestra de WinForms de XNA . Utilizando esta muestra como modelo, parece que una forma de visualizar componentes en un WinForm es crear un control para él en el mismo estilo que el SpinningTriangleControl de la muestra. Esto demuestra cómo generar código XNA sin una instancia de juego. Realmente el juego no es importante, lo que importa es lo que hace por ti. Entonces, lo que haría es crear un proyecto de biblioteca que tenga la lógica de carga / extracción del componente en una clase y en sus otros proyectos, crear una clase de control y una clase de componente que sean envoltorios para el código de biblioteca en sus respectivos entornos. De esta manera, el código de sus pruebas no se duplica y no tiene que preocuparse por escribir código que siempre será viable en dos escenarios diferentes.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top