Полагаю, поскольку вы упомянули TDD, рассматриваемого кода на самом деле не существует. Если это так, то вы делаете не настоящий TDD, а TAD (Test-After Development), что, естественно, приводит к таким вопросам, как этот. В TDD мы начнем с теста. Похоже, что вы создаете какой-то тип меню или системы команд, поэтому я буду использовать это в качестве примера.
describe GameMenu do
it "Allows you to navigate to character creation" do
# Assuming character creation would require capturing additional
# information it violates SRP (Single Responsibility Principle)
# and belongs in a separate class so we'll mock it out.
character_creation = mock("character creation")
character_creation.should_receive(:execute)
# Using constructor injection to tell the code about the mock
menu = GameMenu.new(character_creation)
menu.execute("c")
end
end
Этот тест привел бы к некоторому коду, подобному следующему (помните, достаточно кода, чтобы пройти тест, не более)
class GameMenu
def initialize(character_creation_command)
@character_creation_command = character_creation_command
end
def execute(command)
@character_creation_command.execute
end
end
Теперь мы добавим следующий тест.
it "Allows you to display character inventory" do
inventory_command = mock("inventory")
inventory_command.should_receive(:execute)
menu = GameMenu.new(nil, inventory_command)
menu.execute("i")
end
Выполнение этого теста приведет нас к реализации, такой как:
class GameMenu
def initialize(character_creation_command, inventory_command)
@inventory_command = inventory_command
end
def execute(command)
if command == "i"
@inventory_command.execute
else
@character_creation_command.execute
end
end
end
Эта реализация приводит нас к вопросу о нашем коде. Что должен делать наш код при вводе неверной команды? Как только мы решим ответ на этот вопрос, мы сможем провести еще один тест.
it "Raises an error when an invalid command is entered" do
menu = GameMenu.new(nil, nil)
lambda { menu.execute("invalid command") }.should raise_error(ArgumentError)
end
Это приводит к быстрому переходу на execute
метод
def execute(command)
unless ["c", "i"].include? command
raise ArgumentError("Invalid command '#{command}'")
end
if command == "i"
@inventory_command.execute
else
@character_creation_command.execute
end
end
Теперь, когда у нас есть прохождение тестов, мы можем использовать Извлечение метода рефакторинг для извлечения проверки команды в Метод раскрытия намерений .
def execute(command)
raise ArgumentError("Invalid command '#{command}'") if invalid? command
if command == "i"
@inventory_command.execute
else
@character_creation_command.execute
end
end
def invalid?(command)
!["c", "i"].include? command
end
Теперь мы наконец дошли до того, что можем ответить на ваш вопрос. Поскольку метод invalid?
был исключен путем рефакторинга существующего тестируемого кода, нет необходимости писать для него модульный тест, он уже покрыт и не является самостоятельным. Поскольку команды инвентаризации и символов не проверяются в нашем существующем тесте, их нужно будет тестировать независимо.
Обратите внимание, что наш код может быть еще лучше, поэтому, пока тесты проходят, давайте немного его очистим. Условные операторы являются показателем того, что мы нарушаем OCP (открытый-закрытый принцип) , мы можем использовать рефакторинг Replace Conditional With Polymorphism для удаления условной логики.
# Refactored to comply to the OCP.
class GameMenu
def initialize(character_creation_command, inventory_command)
@commands = {
"c" => character_creation_command,
"i" => inventory_command
}
end
def execute(command)
raise ArgumentError("Invalid command '#{command}'") if invalid? command
@commands[command].execute
end
def invalid?(command)
!@commands.has_key? command
end
end
Теперь мы реорганизовали класс таким образом, что дополнительная команда просто требует, чтобы мы добавили дополнительную запись в хэш команд, а не изменяли нашу условную логику и метод invalid?
.
Все тесты должны пройти, и мы почти завершили нашу работу. После того, как мы протестируем отдельные команды, вы можете вернуться к методу инициализации и добавить некоторые значения по умолчанию для таких команд:
def initialize(character_creation_command = CharacterCreation.new,
inventory_command = Inventory.new)
@commands = {
"c" => character_creation_command,
"i" => inventory_command
}
end
Финальный тест:
describe GameMenu do
it "Allows you to navigate to character creation" do
character_creation = mock("character creation")
character_creation.should_receive(:execute)
menu = GameMenu.new(character_creation)
menu.execute("c")
end
it "Allows you to display character inventory" do
inventory_command = mock("inventory")
inventory_command.should_receive(:execute)
menu = GameMenu.new(nil, inventory_command)
menu.execute("i")
end
it "Raises an error when an invalid command is entered" do
menu = GameMenu.new(nil, nil)
lambda { menu.execute("invalid command") }.should raise_error(ArgumentError)
end
end
И финал GameMenu
выглядит так:
class GameMenu
def initialize(character_creation_command = CharacterCreation.new,
inventory_command = Inventory.new)
@commands = {
"c" => character_creation_command,
"i" => inventory_command
}
end
def execute(command)
raise ArgumentError("Invalid command '#{command}'") if invalid? command
@commands[command].execute
end
def invalid?(command)
!@commands.has_key? command
end
end
Надеюсь, это поможет!
Brandon