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

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

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

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

该论坛有一些关于unit testing主题的好post。 这是我在XNA中进行unit testing的个人方法:

  • 忽略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); } } } 

这使我可以灵活地创建实体的子类(AIEntity,NonInteractiveEntity …),它可能会覆盖Update()。 它还允许我自由地子类Drawable,没有像AnimatedSpriteAIEntityParticleEffectNonInteractiveEntityAnimatedSpriteNoninteractiveEntity这样的n ^ 2子类的地狱。 相反,我可以这样做:

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

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

  • 为每个项目符号添加一个新实体
  • 创建一个定义List的TankEntity类,并覆盖Draw()以迭代Bullets(它们定义了自己的Draw方法)
  • 制作一个ListDrawable

这是ListDrawable的示例实现,忽略了如何管理列表本身的问题。

 public class ListDrawable : Drawable { private List Children; // ... public override void Draw(SpriteBatch spriteBatch, Rectangle visibleArea) { if (Children == null) { return; } foreach (Drawable child in children) { child.Draw(spriteBatch, visibleArea); } } } 

像MOQ和Rhino Mocks这样的框架并不特别需要接口。 他们也可以模拟任何非密封和/或抽象类。 游戏是一个抽象类,所以你不应该嘲笑它:-)

至少应该注意这两个框架的一点是,要设置对方法或属性的任何期望,它们必须是虚拟的或抽象的。 原因是它生成的模拟实例需要能够覆盖。 我相信IAmCodeMonkey提到的类型可以解决这个问题,但我不认为typemock是免费的,而我提到的两个是。

顺便说一句,您还可以查看我的一个项目,该项目可以帮助创建XNA游戏的unit testing,而无需制作模拟: http : //scurvytest.codeplex.com/

你不必嘲笑它。 为什么不制作假游戏对象?

inheritance自Game并覆盖您打算在测试中使用的方法,以返回您需要的任何方法/属性的固定值或快捷方式计算。 然后将假货传递给您的测试。

在嘲笑框架之前,人们推出自己的模拟/存根/假货 – 也许它不是那么快捷,但你仍然可以。

你可以使用一个名为TypeMock的工具,我认为它不需要你有一个接口。 您的另一个更常用的方法是创建一个inheritance自Game的新类,并实现您创建的与Game对象匹配的接口。 然后,您可以针对该接口进行编码并传入“自定义”Game对象。

 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... } 

它有点乏味,但它可以让你实现可模拟性。

如果你不介意的话,我会背上你的post,因为我似乎不太活跃你已经把你的代表放在了线上;)

当我阅读你的post(这里和XNAForum)时,我认为这个框架既可以更平易近人又我的(我们的)设计不是完美的。

该框架可以设计为更容易扩展。 我很难相信肖恩 在接口上的主要论点。 支持他的同事说,可以轻松地避免打击。
请注意,框架已经具有IUpdatable和IDrawable接口。 为什么不一路走?

另一方面,我也认为我的(和你的)设计确实没有完美无瑕。 在我不依赖于Game对象的地方,我确实依赖于GraphicsDevice对象。 我会看看如何绕过这个。 它会让代码变得更复杂,但我认为我确实可以打破这些依赖关系。

对于像这样的起点,我会尝试使用XNA WinForms示例 。 使用此示例作为模型,似乎在WinForm中可视化组件的一种方法是以与样本中的SpinningTriangleControl相同的样式为其创建控件。 这演示了如何在没有Game实例的情况下呈现XNA代码。 真正的游戏并不重要,它对你而言至关重要。 因此,您要做的是创建一个库项目,该项目在类和其他项目中具有Component的Load / Draw逻辑,创建一个Control类和一个Component类,它们是各自环境中库代码的包装器。 这样,您的测试代码不会重复,您不必担心编写在两种不同情况下始终可行的代码。