Birim Phaser gibi durum bilgisi olan bir çerçeveyi test etmek?


9

TL; DR Durum bilgisi dahilinde çalışırken otomatik birim testini basitleştirmek için tekniklerin tanımlanmasında yardıma ihtiyacım var.


Arka fon:

Şu anda TypeScript ve Phaser çerçevesinde bir oyun yazıyorum . Phaser, kodunuzun yapısını kısıtlamak için mümkün olduğunca az çalışan bir HTML5 oyun çerçevesi olarak tanımlar. Bu, birkaç değiş tokuş ile geliyor, yani her şeye erişmenizi sağlayan bir Tanrı nesnesi Phaser.Game var: önbellek, fizik, oyun durumları ve daha fazlası.

Bu durum, Tilemap'ım gibi birçok işlevselliği test etmeyi gerçekten zorlaştırıyor. Bir örnek görelim:

Burada fayans katmanlarımın doğru olup olmadığını test ediyorum ve Tilemap'ımdaki duvarları ve yaratıkları tanımlayabiliyorum:

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]
            ]);

Ne yaparsam yapayım, haritayı oluşturmaya çalıştığımda, Phaser dahili olarak önbelleğini çağırır ve bu sadece çalışma zamanı sırasında doldurulur.

Tüm oyunu yüklemeden bu testi başlatamıyorum.

Karmaşık bir çözüm, haritayı yalnızca ekranda görüntülememiz gerektiğinde oluşturan bir Adaptör veya Proxy yazmak olabilir. Ya da sadece ihtiyacım olan varlıkları manuel olarak yükleyip yalnızca belirli test sınıfı veya modülü için kullanarak oyunu kendim doldurabilirim.

Hissettiğim şeyin daha pragmatik ama yabancı bir çözüm olduğunu seçtim. Oyunumun yüklenmesi ve gerçek oynanışı TestStatearasında, testi tüm varlıklar ve önceden yüklenmiş önbellek verileri ile çalıştıran bir şimşek .

Bu harika, çünkü istediğim tüm işlevselliği test edebiliyorum, ama aynı zamanda soğutmuyorum, çünkü bu teknik bir entegrasyon testi ve sadece ekrana bakıp düşmanların görüntülenip görüntülenmediğini görüp göremeyeceğimi merak ediyor. Aslında hayır, bir Öğe (zaten bir kez oldu) veya daha sonra testlerde - yanlış tanımlanmış olabilirler, ölümlerine bağlı olaylar verilmemiş olabilirler.

Benim sorum - Böyle bir test durumunda daralma yaygın mı? Özellikle JavaScript ortamında bilmediğim daha iyi yaklaşımlar var mı?


Başka bir örnek:

Tamam, neler olduğunu açıklamaya yardımcı olacak daha somut bir örnek:

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'ımı üç bölümden oluşturuyorum:

  • Haritalar key
  • manifestHarita gerektirdiği detaylandırma bütün varlıklar (tilesheets ve spritesheets)
  • mapDefinitionTilemap'in yapısını ve katmanlarını tanımlayan A.

İlk olarak, Phaser içinde Tilemap'i inşa etmek için super'i aramalıyım. Bu, yalnızca içinde tanımlanan anahtarları değil, gerçek varlıkları aramaya çalışırken önbelleğe yapılan tüm çağrıları çağıran bölümdür manifest.

İkinci olarak, döşeme sayfalarını ve döşeme katmanlarını Tilemap ile ilişkilendiriyorum. Artık haritayı oluşturabilir.

Üçüncüsü, ben yinelerler benim katmanları içinden ve ben haritadan extrude istediğiniz herhangi bir özel nesneleri bulmak: Creatures, Items, Interactablesve benzeri. Bu nesneleri daha sonra kullanmak üzere oluşturur ve saklarım.

Şu anda hala bu varlıkları bulmamı, kaldırmamızı, güncellememi sağlayan nispeten basit bir API'm var:

    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 });
    }

Kontrol etmek istediğim bu işlevsellik. Döşeme Katmanlarını veya Döşeme Kümelerini eklemezsem, harita görüntülenmez, ancak bunu test edebilirim. Bununla birlikte, super (...) çağrısı bile, testlerimde izole edemediğim bağlama özgü veya durum bilgisi olan mantığı çağırır.


2
Kafam karıştı. Phaser'ın tilemap yükleme işini yaptığını test etmeye mi çalışıyorsunuz yoksa tilemap'in içeriğini test etmeye mi çalışıyorsunuz? Eğer eskisi ise, genellikle bağımlılıklarınızın işini yaptığını test etmezsiniz; bu kütüphane görevlisinin işi. İkincisi ise, oyun mantığınız çerçeveye çok sıkı bir şekilde bağlıdır. Performansın izin verdiği ölçüde, oyununuzun iç işleyişini saf tutmak ve bu tür karışıklığı önlemek için yan etkileri programın en üst katmanlarına bırakmak istersiniz.
Doval

Hayır, kendi işlevlerimi test ediyorum. Testler böyle görünmüyorsa özür dilerim, ama kapakların altında biraz var. Aslında, tilemap üzerinden bakıyorum ve Öğeler, Yaratıklar ve benzeri gibi oyun varlıklarına dönüştürdüğüm özel karoları keşfediyorum. Bu mantık tamamen benimdir ve kesinlikle test edilmelidir.
IAE

1
Phaser'ın buna tam olarak nasıl dahil olduğunu açıklayabilir misiniz? Phaser'ın nereden ve neden çağrıldığı açık değil. Harita nereden geliyor?
Doval

Karışıklık için özür dilerim! Test etmeye çalıştığım işlevsellik birimine örnek olarak Tilemap kodumu ekledim. Tilemap, kullanmak istediğim ekstra işlevsellik ile tilemap'ı oluşturmama izin veren bir uzantı (veya isteğe bağlı olarak -a) has Phaser.Tilemap. Son paragraf neden ayrı ayrı test edemediğimi vurgulamaktadır. Bir bileşen olarak bile, sadece new Tilemap(...)Phaser'ın önbelleğinde kazmaya başladığım an . Bunu ertelemeliydim, ama bu Tilemap'ımın iki durumda olduğu, biri düzgün şekilde işleyemediği ve tamamen inşa edilmiş olduğu anlamına geliyor.
IAE

Bana öyle geliyor ki, ilk yorumumda söylediğim gibi, oyun mantığınız çerçeveye çok bağlı. Oyun mantığını, çerçeveye hiç girmeden çalıştırabilmelisin. Döşeme haritasını ekrana çizmek için kullanılan varlıklarla eşleştirilir.
Doval

Yanıtlar:


2

Phaser veya Typescipt'i bilmiyorum, yine de size bir cevap vermeye çalışıyorum, çünkü karşılaştığınız sorunlar diğer birçok çerçevede de görülebilen problemlerdir. Sorun şu ki, bileşenler sıkı bir şekilde birleşmelidir (her şey Tanrı nesnesine işaret eder ve Tanrı nesnesi her şeye sahiptir ...). Bu, çerçevenin yaratıcıları birim testlerini kendileri oluşturduğunda gerçekleşmesi olası olmayan bir şeydir.

Temel olarak dört seçeneğiniz vardır:

  1. Birim sınamasını durdurun.
    Diğer tüm seçenekler başarısız olmadıkça bu seçenekler seçilmemelidir.
  2. Başka bir çerçeve seçin veya kendi çerçevenizi yazın.
    Birim testi kullanan ve kuplajı kaybetmiş başka bir çerçeve seçmek hayatı çok daha kolaylaştıracaktır. Ama belki de sevdiğiniz hiçbir şey yoktur ve bu nedenle şu anda sahip olduğunuz çerçeveye sıkışmışsınızdır. Kendiniz yazmak çok zaman alabilir.
  3. Çerçeveye katkıda bulunun ve test dostu olmasını sağlayın.
    Muhtemelen en kolayı, ancak gerçekten ne kadar zamanınız olduğuna ve çerçevenin yaratıcılarının çekme isteklerini kabul etmeye ne kadar istekli olduğuna bağlıdır.
  4. Çerçeveyi sarın.
    Bu seçenek büyük olasılıkla birim testine başlamak için en iyi seçenektir. Ünite testlerinde gerçekten ihtiyacınız olan bazı nesneleri sarın ve geri kalanı için sahte nesneler oluşturun.

2

David gibi, Phaser veya Typescript'e aşina değilim, ancak endişelerinizi çerçeveler ve kütüphanelerle birim testi için ortak olarak kabul ediyorum.

Kısa cevap evet, şimşek bunu birim testi ile ele almanın doğru ve yaygın yoludur . Bence kopukluk izole ünite testi ile fonksiyonel test arasındaki farkı anlamaktır.

Birim testi , kodunuzun küçük bölümlerinin doğru sonuçlar verdiğini kanıtlar. Birim testinin amacı, üçüncü taraf kodunun test edilmesini içermez. Varsayım, kodun 3. tarafın beklediği şekilde çalıştığı test edilmiştir. Bir çerçeveye dayanan kod için bir birim testi yazarken, koda belirli bir duruma benzeyen şeyleri hazırlamak veya çerçeveyi / kütüphaneyi tamamen şimşek etmek için belirli bağımlılıkları takmak yaygındır. Basit bir örnek bir web sitesi için oturum yönetimidir: belki şim depolamadan okumak yerine her zaman geçerli, tutarlı bir durum döndürür. Başka bir yaygın örnek, verileri belleğe aktarmak ve veritabanını sorgulayacak herhangi bir kütüphaneyi atlamaktır, çünkü amaç, ona bağlanmak için kullandığınız veritabanını veya kütüphaneyi test etmek değil, sadece kodunuzun verileri doğru bir şekilde işlediği.

Ama iyi birim testleri gelmez son kullanıcı beklediğiniz tam olarak ne göreceğiz anlamına gelir. İşlevsel test , bir özelliğin, çerçevelerin ve hepsinin çalıştığı yüksek düzeyde bir görünümden daha fazlasını alır. Basit bir web sitesi örneğine geri dönersek, işlevsel bir test kodunuza bir web isteği yapabilir ve yanıtı geçerli sonuçlar için kontrol edebilir. Sonuç üretmek için gereken tüm kodlara yayılmıştır. Test, belirli kod doğruluğundan daha fazla işlevsellik içindir.

Bu yüzden birim testlerle doğru yolda olduğunuzu düşünüyorum. Tüm sistemin işlevsel testlerini eklemek için Phaser çalışma zamanını çağıran ve sonuçları kontrol eden ayrı testler oluşturacağım.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.