TL; DR Мені потрібна допомога у виявленні методів для спрощення автоматизованого тестування одиниць при роботі в стані.
Фон:
В даний час я пишу гру в TypeScript та Phaser Framework . Phaser описує себе як ігровий фреймворк HTML5, який намагається якомога менше обмежувати структуру вашого коду. Це пов'язано з кількома компромісами, а саме тим, що існує бого-об'єкт Phaser.Game, який дозволяє вам отримувати доступ до всього: кешу, фізики, ігрових станів тощо.
Ця державність робить насправді важко перевірити багато функціональних можливостей, таких як мій Tilemap. Давайте подивимось приклад:
Тут я перевіряю, чи правильно виконуються мої шари плитки, і я можу визначити стіни та істоти в межах моєї Tilemap:
export class TilemapTest extends tsUnit.TestClass {
constructor() {
super();
this.map = this.mapLoader.load("maze", this.manifest, this.mazeMapDefinition);
this.parameterizeUnitTest(this.isWall,
[
[{ x: 0, y: 0 }, true],
[{ x: 1, y: 1 }, false],
[{ x: 1, y: 0 }, true],
[{ x: 0, y: 1 }, true],
[{ x: 2, y: 0 }, false],
[{ x: 1, y: 3 }, false],
[{ x: 6, y: 3 }, false]
]);
this.parameterizeUnitTest(this.isCreature,
[
[{ x: 0, y: 0 }, false],
[{ x: 2, y: 0 }, false],
[{ x: 1, y: 3 }, true],
[{ x: 4, y: 1 }, false],
[{ x: 8, y: 1 }, true],
[{ x: 11, y: 2 }, false],
[{ x: 6, y: 3 }, false]
]);
Що б я не робив, як тільки я намагаюся створити карту, Фазер внутрішньо викликає кеш, який заповнюється лише під час виконання.
Я не можу викликати цей тест, не завантажуючи всю гру.
Складним рішенням може бути написання адаптера або проксі, який будує карту лише тоді, коли нам потрібно відобразити її на екрані. Або я міг би самостійно заповнити гру, завантаживши вручну лише потрібні вам активи, а потім використати її лише для певного тестового класу чи модуля.
Я вибрав те, що мені здається, більш прагматичним, але іноземним рішенням цього. Між завантаженням моєї гри та фактичною її грою я прошив TestState
те, що виконує тест із усіма завантаженими активами та кешованими даними.
Це круто, тому що я можу перевірити всю функціональність, яку я хочу, але також не охолоджую, тому що це технічний тест на інтеграцію і задається питанням, чи не міг я просто подивитися на екран і побачити, чи відображаються вороги. Насправді, ні, вони могли бути неправильно ідентифіковані як предмет (стався один раз) або пізніше під час випробувань - їм, можливо, не давали події, пов'язані зі смертю.
Моє запитання - Чи таке мерехтіння в тестовому стані подібне? Чи є кращі підходи, особливо в середовищі JavaScript, про які я не знаю?
Ще один приклад:
Гаразд, ось конкретніший приклад, який допоможе пояснити, що відбувається:
export class Tilemap extends Phaser.Tilemap {
// layers is already defined in Phaser.Tilemap, so we use tilemapLayers instead.
private tilemapLayers: TilemapLayers = {};
// A TileMap can have any number of layers, but
// we're only concerned about the existence of two.
// The collidables layer has the information about where
// a Player or Enemy can move to, and where he cannot.
private CollidablesLayer = "Collidables";
// Triggers are map events, anything from loading
// an item, enemy, or object, to triggers that are activated
// when the player moves toward it.
private TriggersLayer = "Triggers";
private items: Array<Phaser.Sprite> = [];
private creatures: Array<Phaser.Sprite> = [];
private interactables: Array<ActivatableObject> = [];
private triggers: Array<Trigger> = [];
constructor(json: TilemapData) {
// First
super(json.game, json.key);
// Second
json.tilesets.forEach((tileset) => this.addTilesetImage(tileset.name, tileset.key), this);
json.tileLayers.forEach((layer) => {
this.tilemapLayers[layer.name] = this.createLayer(layer.name);
}, this);
// Third
this.identifyTriggers();
this.tilemapLayers[this.CollidablesLayer].resizeWorld();
this.setCollisionBetween(1, 2, true, this.CollidablesLayer);
}
Я будую свою Tilemap з трьох частин:
- Карта
key
- У
manifest
деталізує всіх активи (tilesheets і spritesheets) необхідний карта mapDefinition
, Який описує структуру і шари tilemap в.
По-перше, я повинен зателефонувати супер, щоб сконструювати Tilemap в Phaser. Це частина, яка викликає всі ці виклики до кешування, коли вона намагається шукати фактичні активи, а не лише ключі, визначені в manifest
.
По-друге, я пов'язую плитки та шари плитки з Tilemap. Тепер він може відображати карту.
В- третіх, я ітерація через мої шари і знайти якісь - або спеціальні об'єкти , які я хочу , щоб видавлювати з карти: Creatures
, Items
, Interactables
і так далі. Я створюю і зберігаю ці об’єкти для подальшого використання.
Наразі у мене є ще досить простий API, який дозволяє мені знаходити, видаляти, оновлювати ці сутності:
wallAt(at: TileCoordinates) {
var tile = this.getTile(at.x, at.y, this.CollidablesLayer);
return tile && tile.index != 0;
}
itemAt(at: TileCoordinates) {
return _.find(this.items, (item: Phaser.Sprite) => _.isEqual(this.toTileCoordinates(item), at));
}
interactableAt(at: TileCoordinates) {
return _.find(this.interactables, (object: ActivatableObject) => _.isEqual(this.toTileCoordinates(object), at));
}
creatureAt(at: TileCoordinates) {
return _.find(this.creatures, (creature: Phaser.Sprite) => _.isEqual(this.toTileCoordinates(creature), at));
}
triggerAt(at: TileCoordinates) {
return _.find(this.triggers, (trigger: Trigger) => _.isEqual(this.toTileCoordinates(trigger), at));
}
getTrigger(name: string) {
return _.find(this.triggers, { name: name });
}
Саме цю функціональність я хочу перевірити. Якщо я не додаю шари плитки або тілець, карта не відображатиметься, але я можу її перевірити. Однак навіть виклик супер (...) викликає логіку, залежну від контексту, або яку я не можу виділити у своїх тестах.
new Tilemap(...)
Фазер починає копати у своєму кеші. Мені доведеться відкласти це, але це означає, що мій Tilemap знаходиться у двох станах, в одному, який не може відображати себе належним чином, і повністю сконструйованому.