การทดสอบหน่วยเฟรมเวิร์ก stateful เช่น Phaser?


9

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 (... ) เรียกใช้ตรรกะเฉพาะบริบทหรือสภาวะที่ฉันไม่สามารถแยกได้ในการทดสอบของฉัน


2
ฉันสับสน คุณกำลังพยายามทดสอบว่า Phaser กำลังทำการโหลด tilemap หรือคุณกำลังพยายามทดสอบเนื้อหาของ tilemap เองหรือไม่? หากเป็นอดีตคุณจะไม่ทดสอบว่าการพึ่งพาของคุณทำงานของพวกเขาหรือไม่ นั่นคืองานของผู้ดูแลห้องสมุด ถ้าอย่างหลังตรรกะของเกมของคุณนั้นแน่นเกินไปกับกรอบ เท่าที่ประสิทธิภาพจะช่วยให้คุณต้องการรักษาผลงานภายในของเกมของคุณให้บริสุทธิ์และปล่อยให้ผลข้างเคียงของเลเยอร์บนสุดของโปรแกรมเพื่อหลีกเลี่ยงความยุ่งเหยิงเช่นนี้
Doval

ไม่ฉันกำลังทดสอบการใช้งานของฉันเอง ฉันขอโทษถ้าการทดสอบไม่เป็นแบบนั้น แต่มีบางอย่างเกิดขึ้นภายใต้ฝาครอบ โดยพื้นฐานแล้วฉันกำลังมองผ่านผังแผนที่และค้นหาไทล์พิเศษที่ฉันแปลงเป็นรายการเกมเช่นรายการสิ่งมีชีวิตและอื่น ๆ ตรรกะนี้เป็นของฉันทั้งหมดและต้องได้รับการทดสอบอย่างแน่นอน
IAE

1
คุณสามารถอธิบายได้ว่า Phaser เกี่ยวข้องกับเรื่องนี้อย่างไร? ยังไม่ชัดเจนสำหรับฉันที่ Phaser ถูกเรียกใช้และทำไม แผนที่มาจากไหน
Doval

ฉันขอโทษสำหรับความสับสน! ฉันได้เพิ่มรหัส Tilemap ของฉันเป็นตัวอย่างของหน่วยงานที่ฉันพยายามทดสอบ Tilemap เป็นส่วนขยาย (หรือเป็นทางเลือกคือ - a) Phaser.Tilemap ที่ให้ฉันแสดงภาพ tilemap ด้วยฟังก์ชันพิเศษมากมายที่ฉันต้องการใช้ ย่อหน้าสุดท้ายเน้นว่าทำไมฉันไม่สามารถทดสอบแยกได้ ในฐานะที่เป็นองค์ประกอบช่วงเวลาที่ฉันเพิ่งnew Tilemap(...)Phaser เริ่มขุดในแคชของมัน ฉันต้องเลื่อนไปก่อน แต่นั่นหมายความว่าไทล์แมปของฉันอยู่ในสองสถานะหนึ่งที่ไม่สามารถแสดงผลได้อย่างถูกต้องและสร้างขึ้นอย่างสมบูรณ์
IAE

สำหรับฉันแล้วดูเหมือนว่าฉันได้กล่าวไว้ในความคิดเห็นแรกของฉันแล้วตรรกะของเกมของคุณนั้นเกินกว่ากรอบ คุณควรจะสามารถรันเกมตรรกะของคุณโดยไม่ต้องนำกรอบเลย การเชื่อมต่อแผนที่ย่อยเข้ากับสินทรัพย์ที่ใช้ในการวาดลงบนหน้าจอกำลังเข้ามา
Doval

คำตอบ:


2

ไม่ทราบว่า Phaser หรือ Typescipt ฉันยังคงพยายามให้คำตอบกับคุณเพราะปัญหาที่คุณเผชิญอยู่นั้นเป็นปัญหาที่มองเห็นได้ด้วยกรอบอื่น ๆ มากมาย ปัญหาคือส่วนประกอบต่าง ๆ จะต้องเชื่อมโยงกันอย่างแน่นหนา (ทุกสิ่งชี้ไปที่วัตถุของพระเจ้า นี่คือสิ่งที่ไม่น่าจะเกิดขึ้นถ้าผู้สร้างเฟรมเวิร์กสร้างการทดสอบหน่วยด้วยตนเอง

โดยทั่วไปคุณมีสี่ตัวเลือก:

  1. หยุดการทดสอบหน่วย
    ไม่ควรเลือกตัวเลือกนี้เว้นแต่ว่าตัวเลือกอื่น ๆ จะล้มเหลว
  2. เลือกกรอบงานอื่นหรือเขียนของคุณเอง
    การเลือกกรอบการทำงานอื่นที่ใช้การทดสอบหน่วยและการสูญเสียการเชื่อมต่อจะทำให้ชีวิตง่ายขึ้นมาก แต่อาจไม่มีใครที่คุณชอบและดังนั้นคุณติดอยู่กับกรอบที่คุณมีอยู่ตอนนี้ การเขียนของคุณเองอาจใช้เวลานาน
  3. มีส่วนร่วมในกรอบงานและทำให้การทดสอบเป็นมิตร
    อาจเป็นวิธีที่ง่ายที่สุดที่จะทำ แต่จริง ๆ แล้วมันขึ้นอยู่กับว่าคุณมีเวลามากแค่ไหนและผู้สร้างเฟรมเวิร์กเต็มใจที่จะรับคำขอดึงหรือไม่
  4. ล้อมกรอบ
    ตัวเลือกนี้น่าจะเป็นตัวเลือกที่ดีที่สุดในการเริ่มต้นกับการทดสอบหน่วย ห่อวัตถุบางอย่างที่คุณต้องการในการทดสอบหน่วยและสร้างวัตถุปลอมสำหรับที่เหลือ

2

เช่นเดียวกับดาวิดฉันไม่คุ้นเคยกับ Phaser หรือ typescript แต่ฉันตระหนักถึงข้อกังวลของคุณว่าเป็นเรื่องปกติในการทดสอบหน่วยด้วยเฟรมเวิร์กและไลบรารี

คำตอบสั้น ๆใช่ shimming เป็นวิธีที่ถูกต้องและร่วมกันในการจัดการนี้มีการทดสอบหน่วย ฉันคิดว่าการปลดการเชื่อมต่อนั้นเข้าใจความแตกต่างระหว่างการทดสอบหน่วยแยกและการทดสอบการทำงาน

การทดสอบหน่วยพิสูจน์ว่าส่วนเล็ก ๆ ของรหัสของคุณให้ผลลัพธ์ที่ถูกต้อง เป้าหมายของการทดสอบหน่วยไม่รวมการทดสอบรหัสบุคคลที่สาม สมมติฐานคือรหัสนั้นผ่านการทดสอบการทำงานตามที่คาดไว้โดยบุคคลที่สาม เมื่อเขียนการทดสอบหน่วยสำหรับโค้ดที่อาศัยเฟรมเวิร์กมันเป็นเรื่องธรรมดาที่จะกำหนดการพึ่งพาบางอย่างเพื่อเตรียมสิ่งที่ดูเหมือนว่าเป็นสถานะเฉพาะของโค้ดหรือเพื่อกำหนดเฟรมเวิร์ก / ไลบรารีทั้งหมด ตัวอย่างง่ายๆคือการจัดการเซสชันสำหรับเว็บไซต์: บางทีชิมอาจส่งคืนสถานะที่ถูกต้องและสอดคล้องกันแทนที่จะอ่านจากที่เก็บข้อมูล อีกตัวอย่างทั่วไปคือการปรับข้อมูลในหน่วยความจำและข้ามไลบรารีใด ๆ ที่จะสืบค้นฐานข้อมูลเนื่องจากเป้าหมายไม่ได้ทดสอบฐานข้อมูลหรือไลบรารีที่คุณใช้เชื่อมต่ออยู่เพียงว่าโค้ดของคุณประมวลผลข้อมูลอย่างถูกต้อง

แต่การทดสอบหน่วยที่ดีไม่ได้หมายความว่าผู้ใช้จะเห็นสิ่งที่คุณคาดหวัง การทดสอบฟังก์ชั่นใช้เวลามากขึ้นในมุมมองระดับสูงว่าคุณลักษณะทั้งหมดทำงานกรอบและทั้งหมด กลับไปที่ตัวอย่างของเว็บไซต์ง่าย ๆ การทดสอบการใช้งานอาจทำให้เว็บร้องขอรหัสของคุณและตรวจสอบการตอบสนองสำหรับผลลัพธ์ที่ถูกต้อง มันครอบคลุมรหัสทั้งหมดที่จำเป็นในการสร้างผลลัพธ์ การทดสอบนี้ใช้สำหรับการทำงานมากกว่าความถูกต้องของรหัสเฉพาะ

ดังนั้นฉันคิดว่าคุณกำลังถูกทางด้วยการทดสอบหน่วย เพื่อเพิ่มการทดสอบการทำงานของทั้งระบบฉันจะสร้างการทดสอบแยกต่างหากที่เรียกใช้ Phaser runtime และตรวจสอบผลลัพธ์

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.