Допустим, в разработке изменились приоритеты. Теперь вам нужно написать ещё и журнал игры.
В TDD с внезапными изменениями требований проблем нет. Цикл разработки короткий, поэтому больших изменений не происходит. Из‑за этого всегда есть рабочая версия проекта, которую можно дорабатывать в какую угодно сторону.
В нашем примере мы переключаемся на историю ходов. Начнём с метода получения истории getMoveHistory.
test/game.jstest('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.jsgetMoveHistory() { + 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.jstest('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.jsconstructor() { + 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.jstest('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) })