Вдруг! 🔗

Допустим, в разработке изменились приоритеты. Теперь вам нужно написать ещё и журнал игры.

В TDD с внезапными изменениями требований проблем нет. Цикл разработки короткий, поэтому больших изменений не происходит. Из‑за этого всегда есть рабочая версия проекта, которую можно дорабатывать в какую угодно сторону.

В нашем примере мы переключаемся на историю ходов. Начнём с метода получения истории getMoveHistory.

test/game.js
test('Game saves user\'s move in history', () => { const x = 1, y = 1 game.acceptUserMove(x, y) const history = game.getMoveHistory() expect(history).toEqual([{turn: 'user', x, y}]) })
src/Game.js
+ getMoveHistory() { + + }

Метод для получения истории 🔗

Чтобы пройти тест с минимальными усилиями, нам достаточно вернуть то, что тест требует. Это покроет не все возможные случаи, а один. Однако мы определимся со структурой данных и API.

src/Game.js
getMoveHistory() { + return [{turn: 'user', x: 1, y: 1}] }

Рефакторим тесты и метод 🔗

Имя пользователя нам понадобится в следующих тестах, поэтому вынесем его в константу.

Историю же вынесем в конструктор класса.

test/game.js
+ const userName = 'user' const userMoveSymbol = '×' // ... - expect(history).to.deep.equal([{turn: 'user', x, y}]) + expect(history).to.deep.equal([{turn: userName, x, y}])
src/Game.js
+ this._history = [{turn: 'user', x: 1, y: 1}] this._board = [ // ... getMoveHistory() { - return [{turn: 'user', x: 1, y: 1}] + return this._history }

Сохраняем ход компьютера в истории 🔗

Теперь будем сохранять ход компьютера в истории.

Тест не будет проходить, так как история всегда возвращает одинаковое значение.

test/game.js
test('Game saves computers\'s move in history', () => { game.createComputerMove() const history = game.getMoveHistory() expect(history).toEqual([{turn: 'computer', x: 0, y: 0}]) })

Доработаем логику игры так, чтобы при вызове хода игрока или компьютера ход автоматически записывался в историю.

Сделаем историю изначально пустой. Сейчас падают два теста: последний и предыдущий. Это верно, так как теперь ход игрока тоже не записывается.

Допишем обновление истории в метод acceptUserMove, чтобы записывать ходы игрока, и в метод createComputerMove, чтобы записывать ходы компьютера.

Теперь все тесты зелёные. Можно приступать к рефакторингу.

src/Game.js
- this._history = [{turn: 'user', x: 1, y: 1}] + this._history = [] //... + this._history.push({turn: 'user', x, y}) this._updateBoard(x, y) } // ... createComputerMove() { + this._history.push({turn: 'computer', x: 0, y: 0}) this._updateBoard(0, 0, {

В тестах вынесем имя компьютера в переменную.

Обновление истории через push слишком прямолинейно. История может перестать быть массивом, тогда придётся бегать по всему классу и менять её обновление.

Вынесем его в метод _updateHistory и заменим все вызовы на новый метод.

Имена пользователя и компьютера так же вынесем в константы.

Убеждаемся, что ничего не сломалось.

test/game.js
+ const computerName = 'computer' // ... - expect(history).toEqual([{turn: 'computer', x: 0, y: 0}]) + expect(history).toEqual([{turn: computerName, x: 0, y: 0}])
src/Game.js
constructor() { + this._userName = 'user' + this._computerName = 'computer' this._userMoveSymbol = '×' // ... - this._history.push({turn: 'user', x, y}) + this._updateHistory(this._userName, x, y) } createComputerMove() { - this._history.push({turn: 'computer', x: 0, y: 0}) + this._updateHistory(this._computerName, 0, 0) this._updateBoard(0, 0, { // ... + _updateHistory(turn, x, y) { + this._history.push({turn, x, y}) + }

Проверяем, как история записывает несколько ходов 🔗

Теперь пробуем «проиграть ситуацию», когда и пользователь, и компьютер сходили по одному разу. В истории должно оказаться две записи.

Первая запись описывает ход пользователя, вторая — компьютера. Определить это мы можем через поле turn в объекте записи.

Запустив тесты, мы увидим, что новый тест тоже проходит. Но это ещё не значит, мы написали всё правильно.

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

test/game.js
test('Game saves 1 user\'s move and 1 computer\'s move in history', () => { const x = 1, y = 1 game.acceptUserMove(x, y) game.createComputerMove() const history = game.getMoveHistory() expect(history.length).toBe(2) expect(history[0].turn).toEqual(userName) expect(history[1].turn).toEqual(computerName) })