在所有测试用例之前,在Rspec中定义的变量调用方法?

时间:2015-05-29 04:42:01

标签: ruby testing rspec

我正在研究国际象棋程序,并试图为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文件中,我无法弄清楚如何在运行单个测试之前在boardempty_board上分别调用这些方法,而不必在每个描述中执行此操作块。有办法解决这个问题吗?

2 个答案:

答案 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 } }

在Ruby 2.x中使用命名参数

传递位置布尔标志的另一种方法是传递命名的布尔标志。由于参数已命名,因此很清楚调用者打算做什么:

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班级似乎可能是两个独立的班级:EmptyBoardBoard。测试它们也会更容易。您可以在特定类的initializer方法内为每个“策略”设置“初始化”代码。