XNAはGameオブジェクトをモックするか、ゲームを分離します
-
03-07-2019 - |
解決
このフォーラムには、単体テストのトピックに関する良い投稿がいくつかあります。 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);
}
}
編集:コメントのその他のメモ:
マイエンティティクラス: すべてのゲームオブジェクトを、次のような中央集中型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);
}
}
}
これにより、おそらくUpdate()をオーバーライドするEntityのサブクラス(AIEntity、NonInteractiveEntity ...)を柔軟に作成できます。また、 AnimatedSpriteAIEntity
、 ParticleEffectNonInteractiveEntity
、および AnimatedSpriteNoninteractiveEntity
のようなn ^ 2サブクラスの地獄なしで、Drawableを自由にサブクラス化できます。代わりに、私はこれを行うことができます:
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);
}
サブクラスは独自の描画ロジックを定義します。タンクの例では、いくつかのことができます:
- 各弾丸に新しいエンティティを追加
- リストを定義するTankEntityクラスを作成し、Draw()をオーバーライドして箇条書き(独自の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);
}
}
}
他のヒント
MOQ や Rhino Mocks は特にインターフェースを必要としません。シールされていないクラスや抽象クラスもモックできます。ゲームは抽象クラスであるため、モックするのに問題はないはずです:-)
少なくともこれら2つのフレームワークで注意すべきことは、メソッドまたはプロパティに期待を設定するには、それらが仮想または抽象でなければならないということです。この理由は、それが生成する模擬インスタンスがオーバーライドできる必要があるためです。 IAmCodeMonkeyが言及しているtypemockはこれを回避する方法があると信じていますが、typemockが無料であるとは思いませんが、言及した2つはそうです。
余談ですが、モックを作成することなくXNAゲームの単体テストを作成するのに役立つ私のプロジェクトをチェックアウトすることもできます。 http://scurvytest.codeplex.com/
それをあざける必要はありません。偽のゲームオブジェクトを作成してみませんか?
ゲームから継承し、テストで使用するメソッドをオーバーライドして、必要なメソッド/プロパティの既定値またはショートカット計算を返します。次に、テストに偽物を渡します。
フレームワークをモックする前に、人々は自分のモック/スタブ/偽物を転がしました-それほど速くて簡単ではないかもしれませんが、あなたはまだできます。
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...
}
少々退屈ですが、モック性を実現できます。
mine はあまりアクティブではないようで、すでに担当者を配置しています;)
あなたの投稿を読んでいるとき(こことXNAForumの両方)、より親しみやすいフレームワークであると同時に、完璧ではない私の(私たちの)デザインでもあると思います。
フレームワークは、拡張しやすいように設計されている可能性があります。 ショーンを信じるのに苦労しています perfヒットの主な引数。私をバックアップするには、同僚は、perfヒットが簡単に回避。
フレームワークにはIUpdatableおよびIDrawableインターフェイスが既にあることに注意してください。どうして最後まで行かないの?
一方で、私(そしてあなたの)デザインは完璧ではないと思います。 Gameオブジェクトに依存していない場合、GraphicsDeviceオブジェクトに大きく依存しています。これを回避する方法を見ていきます。コードがより複雑になりますが、私は確かにそれらの依存関係を破ることができると思います。
このようなことの出発点として、 XNA WinFormsサンプルを見つけました。 。このサンプルをモデルとして使用すると、WinFormでコンポーネントを視覚化する1つの方法は、サンプルのSpinningTriangleControlと同じスタイルでコントロールを作成することであるように見えます。これは、GameインスタンスなしでXNAコードをレンダリングする方法を示しています。本当にゲームは重要ではありません。あなたにとって重要なのはそのことです。したがって、クラスのコンポーネントのロード/描画ロジックを持つライブラリプロジェクトを作成し、他のプロジェクトで、それぞれの環境でライブラリコードのラッパーであるコントロールクラスとコンポーネントクラスを作成します。このように、テストするコードは複製されず、2つの異なるシナリオで常に実行可能なコードを記述することを心配する必要はありません。