В первой части этой серии я шучу, что если бы это был производственный код, я бы написал тесты. Что ж, тесты - это то, что мне было нужно; в коде было несколько ошибок, которые проскочили. Мы возвращаемся к коду из части 2 и добавляем тесты для предметов в игре.

Для проведения тестов я буду использовать среду тестирования Jest (https://jestjs.io); его легко установить и создать чистую среду узлов. Есть много других фреймворков для тестирования, но я считаю Jest быстрым и простым в использовании.

Первый шаг - разбить код на отдельные файлы.

Во-первых, пункт.

// item.js
const item = function( n ) {
    const _name = n;
    function name() {
        return _name;
    }
    return { name, isItem : true };
}

module.exports = item;

Далее оружие.

// weapon.js
const item = require( "./item");
const weapon = function( n, s ) {
    const _item = item( n );
    const _strength = s || 1;

    function strength() {
        return _strength;
    }
    return { ..._item, strength, isWeapon : true }
}
module.exports = weapon;

Наконец, сумка.

// bag.js
const item = require( "./item");

const bag = function( n, capacity ) {
    const _item = item( n );
    const _size = capacity || 5; // 1 is the minimum size
    const _items = {};
    function stash( item ) {
        if ( size() >=  _size ) {
            return "Your bag is full, you have to drop an item first";
        }
        if ( !item.isItem ) {
            return "A bag can only be filled with items not " + typeof item + "'s";
        }
        _items[item.name()] = item;
        return null;
    }
    function drop( itemName ) {
        return fetch( itemName)
    }
    function fetch( itemName ) {
        let item = _items[itemName]
        if (item == null ) {
            return null;         }
        delete _items[itemName];
        return item;
    }
    function items( ) {
        return Object.keys( _items );
    }
    function size() {
        return Object.keys( _items ).length
    }
   
    return { ..._item, stash, drop, fetch, items, size, isBag : true}
}

module.exports = bag;

Разделение на файлы не является обязательным требованием для тестирования; разделение тестов делает код более удобным для сопровождения в будущем.

Сначала напишем самый простой тест для item.js.

// item.test.js
const item = require( "./item");

test( "Create a weapon and assert behavior", ()=>{
    const myItem = item( "MyItem");
    expect( myItem.name()).toBe("MyItem");
    expect( myItem.isItem).toBe( true );
    expect( myItem.isWeapon).toBeUndefined();
});

Код теста должен быть довольно очевидным; элемент не делает много. Мы могли бы добавить сюда еще несколько защитных тестов; например, что произойдет, если мы не укажем название для элемента? Можно ли добавить случайное имя? Кроме того, нет теста на предмет с другим именем, который будет обнаружен позже.

Далее мы добавляем тест для оружия.

// weapon.test.js
const weapon = require("./weapon");

test( "Create a weapon and assert behavior", ()=>{
    const obj = weapon( "MyWeapon");
    expect( obj.name()).toBe("MyWeapon");
    expect( obj.isWeapon).toBe( true );
    expect( obj.isItem).toBe( true );
    expect( obj.strength()).toBe( 1 );
});

test( "Create a powerful weapon", ()=>{
    const obj = weapon( "LongSword", 5);
    expect( obj.name()).toBe("LongSword");
    expect( obj.isWeapon).toBe( true );
    expect( obj.isItem).toBe( true );
    expect( obj.strength()).toBe( 5 );
});

Опять же, это достаточно ясный тестовый пример. Мы добавляем тест на прочность оружия и на то, что происходит, когда конструктору не предоставляется сила. Если вы сравните код в этой статье с кодом для оружия в последней, вы не заметите проверки на значение по умолчанию в предыдущей версии. Это одна из ценностей написания тестов.

Наконец, мы добавляем тест для сумки. Это намного сложнее.

// bag.test.js
const bag = require("./bag");
const weapon = require("./weapon");
const item = require("./item");

test( "Create a bag and assert behavior", ()=>{
    const obj = bag( "MyBag");
    expect( obj.name()).toBe("MyBag");
    expect( obj.isBag).toBe( true );
    expect( obj.isItem).toBe( true );
    expect( obj.isWeapon).toBeUndefined();
    expect( obj.items()).toStrictEqual( [] );
    expect( obj.size()).toBe( 0 );
});

test( "test stash", ()=>{
    const obj = bag( "Bag of Holding", 2);
    expect( obj.name()).toBe("Bag of Holding");
    const paper = item( "paper");
    expect( obj.size()).toBe( 0 );
    expect( obj.stash( paper )).toBeNull(); // Returns null on success, error message on fail
    expect( obj.size()).toBe( 1 );
    expect( obj.items()).toStrictEqual( ["paper"]);
    expect( typeof obj.stash( "unknown")).toBe( "string" );
    expect( obj.size()).toBe( 1 );
    // Same item just succeeds
    expect( obj.stash( paper )).toBeNull();
    expect( obj.size()).toBe( 1 );
    const rock = item( "rock");
    expect( obj.stash( rock )).toBeNull();
    expect( obj.size()).toBe( 2 );
    expect( obj.items()).toStrictEqual( ["paper", "rock"]);
});

test( "test fetch", ()=>{
    const obj = bag( "Bag of Holding", 2);
    expect( obj.name()).toBe("Bag of Holding");
    const paper = item( "paper");
    expect( obj.size()).toBe( 0 );
    expect( obj.stash( paper )).toBeNull(); // Returns null on success, error message on fail
    expect( obj.size()).toBe( 1 );
    expect( obj.items()).toStrictEqual( ["paper"]);
    expect( obj.fetch( "paper")).toBe( paper );
    expect( obj.size()).toBe( 0 );
    expect( obj.items()).toStrictEqual( []);
    expect( obj.fetch( "item")).toBeNull();
});

test( "capacity", ()=>{
    const obj = bag( "Bag of little Holding", 1 );
    const paper = item( "paper");
    expect( obj.size()).toBe( 0 );
    expect( obj.stash( paper )).toBeNull();
    expect( obj.size()).toBe( 1 );
    const rock = item( "rock");
    expect( typeof obj.stash( paper )).toBe( "string");
    expect( obj.size()).toBe( 1 );
});

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

Это письмо короче, но мы надеемся, что оно поможет показать ценность тестирования и сделает код лучше.





Больше контента на plainenglish.io