Как вы четко показываете намерение при тестировании функций, принимающих блоки в RSpec? - PullRequest
0 голосов
/ 28 апреля 2011

Я только вхожу в RSpec и немного поэкспериментирую с некоторыми простыми примерами и реализую древовидную структуру узлов с посещаемыми узлами.

Первый тест, который я использовал для очистки кода с помощью bdd, был:

describe "Tree" do
  it "is visitable" do
    t = Tree.new
    visited = nil
    t.visit { |n| visited = n }
    visited.should == t
  end
end

Это дает мне следующую реализацию:

class Tree
  def visit(&block)
    block.call self
  end
end

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

it "has visitable children" do
  c1, c2 = Tree.new, Tree.new
  t = Tree.new([c1, c2])
  visited = Set.new

  t.visit { |n| visited.add(n) }

  visited.should == Set.new([t, c1, c2])
end

Это дает мне полную реализацию:

class Tree
  attr_accessor :children
  def initialize(children=[])
    @children = children
  end

  def visit(&block)
    block.call self

    children.each { |c| c.visit &block }
  end
end

Я достаточно доволен итоговой реализацией (будучи пробным примером и всем прочим), но есть ли идиома RSpec, которая может сделать спецификацию более преднамеренной и легко читаемой?

Редактировать: Чтобы уточнить, мне интересно, есть ли хорошие способы справиться с этим с помощью помощников / насмешек RSpec и т. Д.

Ответы [ 2 ]

1 голос
/ 29 апреля 2011

Я бы сказал, что лучше, если тесты тоже выглядят правильно.Реальный вопрос - зачем вообще TDD?Чтобы определить ваши прецеденты (вообще говоря) перед написанием кода и определить API, чтобы он соответствовал вашим прецедентам.По крайней мере, таков мой опыт: используя TDD с RSpec или что-то еще, я определяю лучшие интерфейсы для своего кода.Это связано с тем, что для того, чтобы протестировать как можно больше функциональных возможностей, необходимо обрезать API таким образом, чтобы можно было легко получать доступ к отдельным частям (и при необходимости имитировать).

Так что на самом деле я ожидаю, что тестовые примеры будут выглядетькак производственный код: потому что они будут содержать точно такой же API, что и производственный код.Если это закончится беспорядочно, то это может означать, что вы еще не готовы к использованию своего API?

Использование mocks также помогает, потому что оно отражает ваши ожидания, как в

@target.should_receive(:print).exactly(1).times

Отвечая на комментарий: Вот примеры насмешек над блоком в вашем тестовом примере с явным указанием ожиданий от блока:

describe "Tree" do

  it "is visitable" do
    t = Tree.new
    block = lambda { |n| n }
    block.should_receive(:call).with(t).exactly(1).times
    t.visit &block
  end

  it "has visitable children" do
    c1, c2 = Tree.new, Tree.new
    t = Tree.new([c1, c2])

    block = lambda { |n| n }
    block.should_receive(:call).with(kind_of(Tree)).exactly(3).times { |n|
      [t, c1, c2].include?(n)
    }

    t.visit &block
  end
end
1 голос
/ 28 апреля 2011

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

Тем не менее, вы могли бы немного очистить примеры, поместив настройку объекта в блок before (: each) и / или разделив ваши примеры на контексты дляодноузловые деревья и деревья с детьми.Как то так:

    context "singleton trees" do
      before(:each) do
        @tree1 = Tree.new
        @tree2 = Tree.new
      end

      ...

      context "trees with children" do

        before(:each) do
          @tree_with_children = Tree.new([@tree1, @tree2)
        end     

       ...

      end
    end
...