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

В игре игроку предлагается выбрать оружие из камня, бумаги или ножниц, чтобы сыграть против Злого Компьютера (см. Выше). Я создал приложение Sinatra, которое случайным образом генерировало выбор оружия для компьютера, а затем сообщало игроку результат игры. Самой сложной частью создания приложения было тестирование того, кто станет победителем игры, учитывая, что компьютер всегда выдавал случайный результат.

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

Я понял, что извлечение класса этого метода позволит мне изолировать его и проверить логику. Это позволило бы мне быть уверенным, что он ведет себя так, как я ожидал, без осложнений, связанных с насмешливым поведением в моих тестах. Я передвинул свою победу? и рисовать? методов в новый класс VictoryCalculator.

Извлечение этого дало мне возможность запускать столько тестов, сколько я хотел, не превышая спецификацию моего класса Games, где раньше были эти методы. Я «затянул петлю» и сделал класс и его результаты видимыми, изолировав его.

Это дало понять, что его поведение на самом деле отличается от поведения класса Game. Единственная обязанность этого калькулятора состояла в том, чтобы рассчитать, была ли истинна победа или ничья, путем сравнения массива оружия игрока и выбора оружия компьютера с заранее определенными постоянными комбинациями ВЫИГРЫША и НИЧЬИ. Такой подход позволил мне затем добавить экземпляр VictoryCalculator в мой класс Game в качестве атрибута.

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

Удвоить случайность метода Компьютерное оружие_выбор было намного проще после того, как он был извлечен. Я мог бы компьютер настроить его ответы на два метода, которые он вызывал в методе результата, и проверить, действительно ли функциональность метода дает мне ожидаемые результаты.

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