TL; DRฉันต้องการความช่วยเหลือในการระบุเทคนิคเพื่อลดความซับซ้อนของการทดสอบหน่วยอัตโนมัติเมื่อทำงานภายในกรอบงานที่เป็นมลรัฐ
พื้นหลัง:
ฉันกำลังเขียนเกมใน typescript และเป็นกรอบ Phaser Phaser อธิบายว่าตัวเองเป็นเกมเฟรมเวิร์ก HTML5 ที่พยายามอย่างน้อยที่สุดเพื่อ จำกัด โครงสร้างของรหัสของคุณ สิ่งนี้มาพร้อมกับการแลกเปลี่ยนไม่กี่อย่างนั่นคือมีPhaser God-object อยู่ซึ่งอนุญาตให้คุณเข้าถึงทุกสิ่ง: แคชฟิสิกส์สถานะเกมและอื่น ๆ
สถานะนี้ทำให้ยากต่อการทดสอบการใช้งานมากมายเช่นไทล์แมปของฉัน ลองดูตัวอย่าง:
ที่นี่ฉันกำลังทดสอบว่าเลเยอร์ไทล์ของฉันถูกต้องหรือไม่และฉันสามารถระบุกำแพงและสิ่งมีชีวิตภายในไทล์แมปของฉัน:
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]
]);
ไม่ว่าฉันจะทำอะไรทันทีที่ฉันพยายามสร้างแผนที่ Phaser จะเรียกใช้แคชภายในซึ่งจะถูกเติมในระหว่างรันไทม์เท่านั้น
ฉันไม่สามารถเรียกใช้การทดสอบนี้โดยไม่โหลดเกมทั้งหมด
วิธีแก้ปัญหาที่ซับซ้อนอาจจะเขียนอะแดปเตอร์หรือพร็อกซี่ที่สร้างแผนที่เมื่อเราต้องการที่จะแสดงบนหน้าจอ หรือฉันสามารถเติมเกมด้วยตนเองโดยการโหลดเฉพาะเนื้อหาที่ฉันต้องการด้วยตนเองจากนั้นใช้มันสำหรับคลาสการทดสอบหรือโมดูลเฉพาะเท่านั้น
ฉันเลือกสิ่งที่ฉันรู้สึกว่าเป็นทางปฏิบัติมากขึ้น แต่วิธีการแก้ปัญหาต่างประเทศนี้ ระหว่างการโหลดเกมของฉันกับการเล่นจริงของฉันฉัน shimmed a 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) ที่จำเป็นโดยแผนที่- A
mapDefinition
ที่อธิบายถึงโครงสร้างและเลเยอร์ของ tilemap
ก่อนอื่นฉันต้องเรียก super เพื่อสร้าง Tilemap ภายใน Phaser manifest
นี้เป็นส่วนหนึ่งที่จะเรียกทุกสายเหล่านั้นไปยังแคชในขณะที่มันพยายามที่จะมองขึ้นในสินทรัพย์ที่เกิดขึ้นจริงและไม่ได้เป็นเพียงปุ่มที่กำหนดไว้ใน
ประการที่สองฉันเชื่อมโยง tilesheets และเลเยอร์ไทล์กับ 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 });
}
นี่เป็นฟังก์ชั่นที่ฉันต้องการตรวจสอบ หากฉันไม่เพิ่ม Tile Layers หรือ Tilesets แผนที่จะไม่แสดงผล แต่ฉันอาจทดสอบได้ อย่างไรก็ตามการเรียก super (... ) เรียกใช้ตรรกะเฉพาะบริบทหรือสภาวะที่ฉันไม่สามารถแยกได้ในการทดสอบของฉัน
new Tilemap(...)
Phaser เริ่มขุดในแคชของมัน ฉันต้องเลื่อนไปก่อน แต่นั่นหมายความว่าไทล์แมปของฉันอยู่ในสองสถานะหนึ่งที่ไม่สามารถแสดงผลได้อย่างถูกต้องและสร้างขึ้นอย่างสมบูรณ์