gpt4 book ai didi

ruby - 调用其他方法的 TDD 方法的正确方法

转载 作者:数据小太阳 更新时间:2023-10-29 07:13:07 25 4
gpt4 key购买 nike

我需要一些关于 TDD 概念的帮助。假设我有以下代码

def execute(command)
case command
when "c"
create_new_character
when "i"
display_inventory
end
end

def create_new_character
# do stuff to create new character
end

def display_inventory
# do stuff to display inventory
end

现在我不确定要为什么编写单元测试。如果我为 execute 方法编写单元测试,那不是几乎涵盖了我对 create_new_characterdisplay_inventory 的测试吗?还是我当时测试了错误的东西?我对 execute 方法的测试是否应该只测试执行是否传递给正确的方法并就此停止?那么我是否应该编写更多的单元测试来专门测试 create_new_characterdisplay_inventory

最佳答案

自从您提到 TDD 以来,我假设所讨论的代码实际上并不存在。如果是这样,那么您不是在做真正的 TDD,而是在做 TAD(开发后测试),这自然会导致诸如此类的问题。在 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

现在我们已经通过了测试,我们可以使用提取方法重构将命令的验证提取到Intent Revealing Method中。

  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(开闭原则),我们可以使用用多态替换条件重构来删除条件逻辑。

# 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

希望对您有所帮助!

布兰登

关于ruby - 调用其他方法的 TDD 方法的正确方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6160788/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com