XNA模拟Game对象或解耦你的游戏

And*_*yuk 10 c# xna unit-testing mocking

如果可以模拟一个Game对象来测试我的DrawableGameComponent组件,那我就是在徘徊?

我知道模拟框架需要一个接口才能运行,但我需要模拟实际的Game对象.

编辑:这是XNA社区论坛上各自讨论的链接.有帮助吗?

ojr*_*rac 14

该论坛有一些关于单元测试主题的好帖子.这是我在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);
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:评论中的其他一些注释:

我的实体类:我选择将所有游戏对象包装在一个集中的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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这使我可以灵活地创建实体的子类(AIEntity,NonInteractiveEntity ...),它可能会覆盖Update().这也让我继承可绘制自由,没有N ^ 2子像地狱AnimatedSpriteAIEntity,ParticleEffectNonInteractiveEntityAnimatedSpriteNoninteractiveEntity.相反,我可以这样做:

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);
Run Code Online (Sandbox Code Playgroud)

我的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);
}
Run Code Online (Sandbox Code Playgroud)

子类定义了自己的Draw逻辑.在你的坦克示例中,你可以做一些事情:

  • 为每个项目符号添加一个新实体
  • 创建一个定义List的TankEntity类,并覆盖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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)