XNA высмеивает объект игры или развязывает вашу игру

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

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

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

Ответ 1

В этом форуме есть хорошие сообщения по теме модульного тестирования. Вот мой личный подход к модульному тестированию в XNA:

  • Игнорировать метод Draw()
  • Изолировать сложное поведение в ваших собственных методах класса
  • Испытайте хитрые вещи, не потейте остальные

Вот пример теста, подтверждающий, что мой метод Update перемещает Entities на правильное расстояние между вызовами 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 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);
        }
    }
}

Ответ 2

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

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

Как в стороне, вы также можете проверить мой проект, который может помочь в создании модульных тестов для игр XNA без необходимости делать издевки: < а2 >

Ответ 3

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

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

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

Ответ 4

Вы можете использовать инструмент под названием 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...
}

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

Ответ 5

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

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

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

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

Ответ 6

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