我正在研究国际象棋程序,并试图为Board课程编写测试。 spec文件的顶部包含以下代码:
describe Board do
let(:board) { Board.new }
let(:empty_board) { Board.new(empty=true) }
...
end
但是,我读到有方法的布尔标志是代码气味,因为它表示该方法负责多个事情。因此,我将initialize方法中的逻辑重构为电路板类中的两个方法:create_default_board
,它将电路板的内容初始化为默认配置,并create_empty_board
。
然而,在spec文件中,我无法弄清楚如何在运行单个测试之前在board
和empty_board
上分别调用这些方法,而不必在每个描述中执行此操作块。有办法解决这个问题吗?
答案 0 :(得分:3)
根据您的描述,这听起来像您的规格为您提供有关设计的反馈。根据您的描述:
我将initialize方法中的逻辑重构为两种方法 在董事会班:
create_default_board
初始化 电路板的内容为默认配置,和create_empty_board
。
对我来说,这听起来像是在制造工厂方法,而且会让#34;你写道:
let(:default_board) { Board.create_default_board }
let(:empty_board) { Board.create_empty_board }
Object#tap
tap
方法将对象生成块,然后从块中返回该对象。
let(:default_board) { Board.new.tap { |b| b.create_default_board } }
let(:empty_board) { Board.new.tap { |b| b.create_empty_board } }
有些人不喜欢tap
模式的外观。您可以通过在初始化程序中屈服来以更干净的方式实现相同的模式:
class Board
def initialize
# ... your configuration ...
yield self
end
end
这允许你写:
let(:default_board) { Board.new { |b| b.create_default_board } }
let(:empty_board) { Board.new { |b| b.create_empty_board } }
传递位置布尔标志的另一种方法是传递命名的布尔标志。由于参数已命名,因此很清楚调用者打算做什么:
let(:default_board) { Board.new }
let(:empty_board) { Board.new(empty: true) }
完全基于您的描述,目前尚不清楚区分"默认"来自和"空的"板。这是板子的尺寸吗?这是1-D,2-D,3-D,4-D还是N-D板?它是板上的物体吗?什么是默认对象?他们在哪里?这两个都是吗?
通过回答这些以及可能的其他问题,您可以清除董事会的哪些部分定义设置。为了这个例子和简单起见,让我们说这个板是一维的,并由一个大小和一个占用指数列表来定义:
class Board
def initialize(size, occupied_locations)
end
end
你可以写下:
let(:default_board) { Board.new(4, [0]) }
let(:empty_board) { Board.new(4, []) }
# or perhaps an empty board has no size
let(:empty_board) { Board.new(0, []) }
如果有明确的"默认"您可以为参数提供默认值:
class Board
def initialize(size = 4, occupied_locations = [1])
end
end
将let
转换为:
let(:default_board) { Board.new }
let(:empty_board) { Board.new(4, []) }
当然,除了希望限制API范围或添加第一类BoardConfiguration
对象之外,没有什么能阻止你写作:
class Board
def self.create_default_board(&builder)
new(size: 4, occupied_locations: [1], &builder)
end
def self.create_empty_board(&builder)
new(size: 1, occupied_locations: [], &builder)
end
def initialize(size:, occupied_locations:)
# ... your configuration ...
yield self
end
end
这实际上允许您编写任何先前的let
语句。
哪种方法最适合您的应用程序和规格,这取决于您。一切都是权衡的平衡。
答案 1 :(得分:0)
您可以在正在测试的每个方法的before
块之外写一个describe
块。
describe Board do
let(:board) { Board.new }
let(:empty_board) { Board.new } # empty=true
before(:each) do
board.create_default_board
empty_board.create_empty_board
end
describe '#a_board_method' do
subject(:a_board_method) { board.a_board_method }
before do
# Some other code here. It will be executed after the
# first `before` block
end
# Finally the test, which is executed after all `before` blocks
it { is_expected.to eq(42) }
end
end
第一个before
块将在您拥有的每个断言块右before
处执行。如果在单个方法的before
块中有更多describe
块,那么这些块将在块之前的“main”之后执行。
您可以查看有关here的更多信息。 RSpec 3.2 here有一个版本。
关于班级职责,您的Board
班级似乎可能是两个独立的班级:EmptyBoard
和Board
。测试它们也会更容易。您可以在特定类的initializer
方法内为每个“策略”设置“初始化”代码。