如果可以模拟 Game 对象以测试我的 DrawableGameComponent 组件,我在徘徊吗?
我知道模拟框架需要一个接口才能运行,但我需要模拟实际的Game对象。
编辑:这里是XNA 社区论坛上各自讨论的链接。有什么帮助吗?
该论坛上有一些关于单元测试主题的好帖子。这是我个人在 XNA 中进行单元测试的方法:
这是一个测试示例,以确认我的 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);
}
}
编辑:评论中的其他一些注释:
我的实体类:我选择将我所有的游戏对象包装在一个集中的实体类中,看起来像这样:
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);
}
}
}
这给了我很大的灵活性来制作可能覆盖 Update() 的实体(AIEntity、NonInteractiveEntity...)的子类。它还让我可以自由地对 Drawable 进行子类化,而无需像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);
}
子类定义了它们自己的 Draw 逻辑。在您的坦克示例中,您可以执行以下操作:
这是 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);
}
}
}
MOQ和Rhino Mocks等框架并不特别需要接口。他们也可以模拟任何非密封和/或抽象类。Game 是一个抽象类,所以你不应该有任何问题来模拟它:-)
至少这两个框架需要注意的一件事是,要对方法或属性设置任何期望,它们必须是虚拟的或抽象的。原因是它生成的模拟实例需要能够覆盖。我相信 IAmCodeMonkey 提到的 typemock 可以解决这个问题,但我不认为 typemock 是免费的,而我提到的两个是免费的。
顺便说一句,您还可以查看我的一个项目,它可以帮助为 XNA 游戏创建单元测试,而无需制作模拟:http ://scurvytest.codeplex.com/
你不必嘲笑它。为什么不制作一个假游戏对象?
从 Game 继承并覆盖您打算在测试中使用的方法,以便为您需要的任何方法/属性返回预设值或快捷计算。然后将假货传递给您的测试。
在模拟框架之前,人们推出了他们自己的模拟/存根/假货——也许它没有那么快速和容易,但你仍然可以。
你可以使用一个名为 TypeMock 的工具,我相信它不需要你有一个界面。您的另一个更常用的方法是创建一个继承自 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...
}
它有点乏味,但它可以让你实现可模拟性。
如果你不介意的话,我会背上你的帖子,因为我的帖子似乎不太活跃,而且你已经把你的代表放在了线上;)
当我阅读您的帖子(包括此处和 XNAForum)时,我认为这既是框架可能更平易近人,也是我(我们的)设计并非完美无缺。
该框架可以设计得更容易扩展。我很难相信肖恩关于接口性能命中的主要论点。为了支持我,他的同事说性能命中可以很容易地规避。
请注意,该框架已经具有 IUpdatable 和 IDrawable 接口。为什么不一路走?
另一方面,我也认为我(和你)的设计确实不是完美无缺的。在我不依赖 Game 对象的地方,我确实非常依赖 GraphicsDevice 对象。我要看看我怎么能绕过这个。它会使代码变得更复杂,但我认为我确实可以打破这些依赖关系。
对于这样的事情的起点,我会点击XNA WinForms Sample。使用此示例作为模型,在 WinForm 中可视化组件的一种方法似乎是为它创建一个控件,其样式与示例中的 SpinningTriangleControl 相同。这演示了如何在没有 Game 实例的情况下呈现 XNA 代码。游戏真的不重要,重要的是它为你做了什么。因此,您要做的是创建一个库项目,该项目在一个类和您的其他项目中具有组件的加载/绘制逻辑,创建一个控件类和一个组件类,它们是各自环境中库代码的包装器。这样,您的测试代码就不会重复,您不必担心编写在两种不同场景中始终可行的代码。