解决方案
有一些良好的职位在该论坛上该专题的单元的测试。这是我个人的方式单元的测试,在GO:
- 忽略的画()方法
- 隔离复杂的行为在你自己的类方法
- 测试棘手的东西,不要出汗的休息
这里有一个例子的一个测试来确认我的新方法转移实体的权利之间的距离更新()电话。(我在使用 呢.) 我修剪了一对夫妇线与不同的移动矢量,但是你的想法:你不需要一个游戏要开你测试。
[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);
}
}
}
这给我很多的灵活性,以使子类的实体(AIEntity,NonInteractiveEntity...)其可能复盖更新().它还可以让我的子类绘制自由,没有地狱的正^2子喜欢 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);
我可画课:我有一个抽象的级从所有我的绘制的对象。我选择了一个抽象的类,因为某些行为将是共用的。这将会是完全可以接受的定义这一个 接口 相反,如果这不是真的你的代码。
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);
}
该子类定义自己的绘制的逻辑。在你的坦克例,你可以做一些事情:
- 添加一个新的实体的每一颗子弹
- 做一个TankEntity类其定义清单,并复盖绘制()以迭代的子弹(其定义绘制方法,他们自己)
- 做一个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);
}
}
}
其他提示
MOQ 和 Rhino Mocks 并不特别需要一个界面。他们也可以模拟任何非密封和/或抽象类。游戏是一个抽象类,所以你不应该嘲笑它: - )
至少应该注意这两个框架的一点是,要设置对方法或属性的任何期望,它们必须是虚拟的或抽象的。原因是它生成的模拟实例需要能够覆盖。我相信IAmCodeMonkey提到的类型可以解决这个问题,但我不认为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...
}
它有点乏味,但它可以让你实现可模拟性。
对于类似这样的起点,我会点击 XNA WinForms示例。使用此示例作为模型,似乎在WinForm中可视化组件的一种方法是以与样本中的SpinningTriangleControl相同的样式为其创建控件。这演示了如何在没有Game实例的情况下呈现XNA代码。真正的游戏并不重要,它对你的重要性。因此,您要做的是创建一个库项目,该项目在类和其他项目中具有Component的Load / Draw逻辑,创建一个Control类和一个Component类,它们是各自环境中库代码的包装器。这样,您的测试代码不会重复,您不必担心编写在两种不同情况下始终可行的代码。