XNA издевается над объектом Game или развязывает вашу игру - PullRequest
10 голосов
/ 30 апреля 2009

Я бродил, можно ли смоделировать объект Game для проверки моего компонента DrawableGameComponent?

Я знаю, что для функционирования фреймворк-фреймворкам необходим интерфейс, но мне нужно высмеивать реальный объект Game .

edit: Вот ссылка на соответствующее обсуждение на форумах сообщества XNA. Любая помощь?

Ответы [ 6 ]

14 голосов
/ 06 мая 2009

На этом форуме есть несколько хороших сообщений на тему юнит-тестирования. Вот мой личный подход к модульному тестированию в 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);
        }
    }
}

Это дает мне большую гибкость для создания подклассов Entity (AIEntity, NonInteractiveEntity ...), которые, вероятно, переопределяют Update (). Это также позволяет мне создавать подклассы Drawable свободно, без черта n ^ 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);

Мой класс 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, который определяет List, и переопределяет 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);
        }
    }
}
3 голосов
/ 05 мая 2009

Ты не должен насмехаться над этим. Почему бы не сделать поддельный игровой объект?

Унаследуйте от Game и переопределите методы, которые вы намереваетесь использовать в своих тестах, для возврата стандартных значений или быстрых вычислений для любых методов / свойств, которые вам нужны. Затем передайте фальшивку своим тестам.

Перед тем, как издеваться над фреймворками, люди катали свои собственные издевательства / заглушки / подделки - возможно, это не так быстро и просто, но вы все равно можете.

3 голосов
/ 05 мая 2009

фреймворки, такие как MOQ и Rhino Mocks специально не нуждаются в интерфейсе. Они могут издеваться над любым незапечатанным и / или абстрактным классом. Игра является абстрактным классом, поэтому вам не составит труда насмехаться над ней: -)

Единственное, что следует отметить, по крайней мере, с этими двумя средами, это то, что для установки каких-либо ожиданий в отношении методов или свойств они должны быть виртуальными или абстрактными. Причина этого заключается в том, что генерируемый экземпляр, который он генерирует, должен иметь возможность переопределения. Я думаю, что typemock, упомянутый IAmCodeMonkey, способен обойти это, но я не думаю, что typemock бесплатен, в то время как два, о которых я упомянул:

Кроме того, вы можете также проверить мой проект, который мог бы помочь в создании модульных тестов для игр XNA без необходимости создавать насмешки: http://scurvytest.codeplex.com/

2 голосов
/ 04 мая 2009

Вы можете использовать инструмент под названием TypeMock, который, я считаю, не требует наличия интерфейса. Ваш другой и наиболее часто используемый метод заключается в создании нового класса, который наследуется от 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...
}

Это немного утомительно, но позволяет вам достигнуть насмешливости.

1 голос
/ 06 мая 2009

Я вернусь к вашему посту, если вы не возражаете, так как мой , кажется, менее активен, и вы уже поставили своего представителя на линию;)

Когда я читаю ваши посты (как здесь, так и XNAForum), я думаю, что это может быть как структура, которая может быть более доступной, так и мой (наш) дизайн, который не безупречен.

Каркас можно было бы спроектировать так, чтобы его было легче расширять. Мне трудно поверить в то, что главный аргумент Шона о совершенном ударе по интерфейсам . Чтобы поддержать меня его коллега говорит, что от удара можно легко избежать.
Обратите внимание, что фреймворк уже имеет интерфейс IUpdatable и IDrawable. Почему бы не пройти весь путь?

С другой стороны, я также считаю, что действительно мой (и ваш) дизайн не безупречен. Там, где я не зависел от объекта Game, я сильно зависел от объекта GraphicsDevice. Я посмотрю, как мне это обойти. Это сделает код более сложным, но я думаю, что я действительно могу сломать эти зависимости.

0 голосов
/ 06 мая 2009

В качестве отправной точки для чего-то подобного я бы выбрал XNA WinForms Sample . Используя этот образец в качестве модели, представляется, что один из способов визуализации компонентов в WinForm - создать для него элемент управления в том же стиле, что и SpinningTriangleControl из образца. Это демонстрирует, как визуализировать код XNA без экземпляра Game. На самом деле игра не важна, важно то, что она делает для вас. Итак, вы должны создать проект библиотеки, который имеет логику загрузки / отрисовки компонента в классе и в других ваших проектах, создать класс Control и класс компонентов, которые являются обертками для кода библиотеки в соответствующих средах. Таким образом, код, который вы тестируете, не дублируется, и вам не нужно беспокоиться о написании кода, который всегда будет жизнеспособным в двух разных сценариях.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...