#ruby #rspec #bdd
#ruby #rspec #bdd
Вопрос:
Я просто вхожу в 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(amp;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(amp;block)
block.call self
children.each { |c| c.visit amp;block }
end
end
Я достаточно доволен результирующей реализацией (являющейся ознакомительным примером и всем прочим), но есть ли идиома RSpec, которая может сделать спецификацию более продуманной и легко читаемой?
Редактировать: Чтобы уточнить, мне интересно, есть ли хорошие способы справиться с этим с помощью помощников RSpec / mocks и т.д.
Комментарии:
1. Вы имеете в виду
block.call self
в первой версииTree#visit
?
Ответ №1:
Я бы сказал, что лучше, если тесты также будут выглядеть правильно. Реальный вопрос в том, зачем вообще использовать 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 amp;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 amp;block
end
end
Комментарии:
1. Итак, как бы вы изменили пример в вопросе, чтобы с пользой использовать mocks?
Ответ №2:
Это выглядит более или менее правильно. Тесты, как правило, выглядят более запутанными, чем код приложения, поскольку на самом деле это просто одноразовые примеры того, как должен работать ваш код. Поэтому я бы не стал слишком зацикливаться на украшении вашего кода.
Тем не менее, вы могли бы немного почистить примеры, поместив object setup в блок 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
Комментарии:
1. Спасибо, это определенно полезно. Я попытался добавить некоторые пояснения в вопрос, чтобы показать, что я ищу новые методы для изучения в RSpec. Имея неудачный опыт работы с неуправляемым тестовым кодом и, возможно, наивное представление о том, что большую часть кода можно сделать легко понятной, я еще не готов отказаться от того, чтобы сделать его более понятным.