Разработка полной игры Wordle очень проста с TDD

Сейчас все играют в Wordle…

А я люблю ТДД.



Итак, двигаемся…

Вкратце: всего за несколько шагов мы можем создать надежный Wordle.

Определение слова

Минимальное количество информации в Wordle — это слово.

Мы можем возразить, что буква меньше, но мы думаем, что весь необходимый буквенный протокол уже определен (мы можем ошибаться).

Слово — это не строка. Это распространенная ошибка и нарушение биекции.



У слова и строки разные обязанности, хотя они могут пересекаться.

Смешивание (случайных) деталей реализации с (существенным) поведением — очень распространенная ошибка.

Итак, нам нужно определить, что такое слово.

Слово в Wordle — это допустимое слово из 5 букв.

Начнем с нашего счастливого пути:

Мы утверждаем, что запрос букв в «valid» возвращает массив с буквами.

Уведомление

  • Класс Word еще не определен.
  • Мы не заботимся о сортировке писем. Это было бы преждевременной оптимизацией и сценарием позолоты.
  • Начнем с простого примера. Нет дубликатов.
  • Мы пока не возимся с проверкой слов (слово может быть XXXXX).
  • Мы можем начать с более простого теста, просто создается слово проверки. Это нарушило бы тестовую структуру, которая всегда требует утверждения.
  • Ожидаемое значение всегда должно быть первым.

Получаем ошибку:

Ошибка: Класс «Wordle\Word» не найден

Это хорошо в TDD, мы изучаем нашу область.

Нам нужно создать Word с помощью конструктора и функции letter().

Уведомление

  • Нам не нужно ничего делать с параметром конструктора
  • Мы жестко кодируем функции букв, так как это самое простое решение на данный момент.
  • Притворяйтесь, пока мы не сделаем это.
  • Классы являются окончательными, чтобы избежать подклассификации.

Мы запускаем все тесты (только 1), и все в порядке.

ОК (1 тест, 1 утверждение)

Напишем еще один тест:

Уведомление

  • Вызов исключения PHPUnit не очень хорош. Мы просто объявляем, что будет возбуждено исключение.

Тест не проходит…

Не удалось подтвердить, что выброшено исключение типа «Wordle\Exception».

Нам нужно изменить нашу реализацию, чтобы сделать test02 пройденным (и также test01)

Уведомление

  • Мы просто проверяем несколько букв. не слишком много, так как у нас еще нет теста покрытия.
  • TDD требует полного охвата. Добавление еще одной проверки без теста является нарушением техники.
  • Мы просто вызываем общее исключение. Создание специальных исключений — это запах кода, загрязняющий пространства имен. (если только не поймаем, но сейчас этого не происходит).


Давайте проверим слишком много

Тест не пройден, как и ожидалось. Давайте исправим.

Не удалось подтвердить, что выброшено исключение типа «Exception».

И все испытания прошли.

ОК (3 теста, 3 утверждения)

Теперь мы можем сделать (необязательный) рефакторинг и изменить функцию, чтобы она утверждала диапазон вместо двух границ.
Мы решили оставить этот вариант, поскольку он более декларативен.

Мы также можем добавить тестовую проверку на отсутствие слов, следуя методологии Zombie.



Давай сделаем это.

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

Давайте теперь проверим, что такое допустимая буква:

… и тест не работает, так как не выдвигается никакое утверждение.

Не удалось подтвердить, что выброшено исключение типа «Exception».

Нам нужно исправить код…

И все тесты проходят, так как мы явно хардкодим.

ОК (5 тестов, 5 утверждений)

Давайте добавим больше недопустимых букв и исправим код.

Уведомление

— Более универсальную функцию мы не писали (пока), так как не можем одновременно править тесты и рефакторить (техника нам запрещает).

Все тесты в порядке.
Можем провести рефакторинг.
Заменяем два последних предложения

Уведомление

  • Утверждение проверяет только заглавные буквы. Так как мы имеем дело с этими примерами до сих пор.
  • Мы максимально откладываем проектные решения.
  • Мы определили регулярное выражение на основе английских букв. Мы почти уверены, что он не примет испанский (ñ), немецкий (ë) и т. д.

В качестве контрольной точки у нас теперь есть только слова из пяти букв.

Давайте утверждаем функцию letter().
Мы оставили его жестко закодированным.
TDD открывает много путей.
Нам нужно отслеживать их все, пока мы не откроем новые.

Нам нужно сравнить слова

И тест проваливается.
Давайте воспользуемся параметром, который мы отправляем им.

Уведомление

- Мы храним буквы и этого достаточно для сравнения объектов (это может зависеть от языка).
- функция Letters() все еще жестко запрограммирована

Тесты в порядке

ОК (8 тестов, 8 утверждений)

Добавляем другое слово для сравнения букв

И тест не проходит.

Не удалось подтвердить, что два массива равны.

Очень важно проверять равенство/неравенство вместо assertTrue(), так как многие IDE открывают сравнение на основе объектов.
Это еще одна причина использовать IDE, а не текстовые редакторы.

Давайте изменим функцию letters().

Наши слова находятся в биекции со словами английского Wordle. или нет?

Этот тест не проходит.
Мы не перехватываем недопустимые 5-буквенные слова английского языка.

Нам нужно принять решение. Согласно нашей биекции, существует внешний словарь, утверждающий допустимые слова.

Мы можем проверить словарь при создании слова. Но мы хотим, чтобы в словаре хранились допустимые слова. Не струны.

Это проблема яйца и курицы.

Мы решили иметь дело с недопустимыми словами в словаре, а не со словом Wordle.

Мы создаем новые тесты в нашем словаре.

Тест не пройден, так как мы не определили наш Словарь.
Делаем это:

Уведомление

- Со словами пока ничего не делаем.
- Количество слов жестко прописываем.
- Опять подделываем.

Мы добавляем еще один случай для счета 1, если в словаре есть одно слово.

Тест не пройден, как и ожидалось

Не удалось подтвердить, что 0 соответствует ожидаемому 1.

Мы исправляем это.

Уведомление

- Словарь неизменен
- Нет сеттеров или геттеров

Начинаем с включения и получаем ошибку.

Ошибка: вызов неопределенного метода Wordle\Dictionary::includesWord()

Так что подделываем.

Мы добавляем положительный случай.
И нам нужно исправить функцию, а не хардкодить ее.

У нас работает словарь.

Давайте создадим игру.

Тест не пройден.
Нам нужно создать класс и функцию.

Реализуем слова пробовали.
И самое простое решение

Попробуем подобрать слова.
Получаем

Ошибка: вызов неопределенного метода Wordle\Game::addtry()

Мы определяем это.

Уведомление

- Мы храним испытания локально и добавляем испытание, а также меняем реальную реализацию wordsTried().

Мы можем реализовать функцию hasLost(), если она пропускает 5 попыток.
Как обычно, с самой простой реализацией.

Как всегда. Я перестаю притворяться и решаю сделать это.

Не удалось утверждать, что ложь является правдой.

Поэтому мы меняем его, как показано ниже.

У нас есть большая часть механики.
Давайте добавим словарь и поиграем в инвалида

Нам нужно передать словарь, чтобы исправить тесты

Зафиксированный.

Теперь мы играем, чтобы выиграть

Нам нужно исправить hasWon().

Уведомление

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

Мы добавилиwinWord.
Нам нужно подтвердить, что это слово есть в словаре.

ОК (8 тестов, 10 утверждений)

У нас есть вся механика.

Давайте добавим позиции букв.
Мы можем сделать это в классе Word.

Тест проходит

OK (10 тестов, 10 утверждений)

Давайте сопоставим

Не удается.

Нам нужно определить это лучше

Мы постоянно проводим все тесты

OK (23 теста, 25 утверждений)

Мы можем добавить тест безопасности, чтобы быть более декларативным

Теперь нам нужны последние шаги. Сопоставление в неправильных позициях.
и всегда самое простое решение…

Более пикантный тестовый пример.
Перейдем к реализации

Удаляем из вхождений точные совпадения.

ОК (26 тестов, 32 утверждения)

Вот и все.
Мы реализовали очень маленькую модель со всеми значимыми правилами.

Будущие шаги

Хорошая модель должна выдерживать изменения требований.

В следующих статьях мы сделаем эти улучшения также с помощью TDD:

  • Управляйте разными языками и персонажами.
  • Импортируйте слова из текстового файла.
  • Добавьте визуальный движок и разместите его.
  • Реализовать Абсурд.
  • Разработайте алгоритм машинного обучения для минимизации перемещений слов.

Репозиторий

Вы можете получить весь код (и делать пулл-реквесты на GitHub.



Заключение

TDD — это итеративная методология. Если вы обнаружите недостающую функциональность, вы можете написать мне в твиттере, и мы добавим ее (конечно, после неудачного теста).



Надеюсь, вам понравилась эта статья, и вы получите удовольствие от игры в Wordle!