Извините за такой длинный ответ, но я думал, что мой мыслительный процесс будет более последовательным, если я пройду через все это.
Поскольку этот вопрос помечен как TDD, я предполагаю, что вы пишете стиль метода TDD. Если это так, вы можете начать с:
describe "my_square_function" do
it "Squares a positive number" do
my_square_function(1).should == 1
end
end
Имея провальный тест, вы можете реализовать my_square_function
следующим образом:
def my_square_function(number)
1
end
Теперь, когда тест пройден, вы хотите реорганизовать дублирование. В этом случае дублирование происходит между кодом и тестом, то есть литералом 1. Поскольку аргумент содержит значение теста, мы можем удалить дублирование, используя вместо этого аргумент.
def my_square_function(number)
number
end
Теперь, когда дублирование удалено и тесты все еще проходят, мы можем перейти к следующему тесту:
describe "my_square_function" do
it "Squares a positive number" do
my_square_function(1).should == 1
end
it "Squares a negative number" do
my_square_function(-1).should == 1
end
end
Запустив тесты, вы снова встретились с провальным тестом, поэтому мы сдаем его:
def my_square_function(number)
number.abs # of course I probably wouldn't really do this but
# hey, it's an example. :-)
end
Теперь этот тест пройден, и пришло время перейти к другому тесту:
describe "my_square_function" do
it "Squares a positive number" do
my_square_function(1).should == 1
end
it "Squares a negative number" do
my_square_function(-1).should == 1
end
it "Squares other positive numbers" do
my_square_function(2).should == 4
end
end
На этом этапе ваш новый тест больше не будет проходить, поэтому теперь для его прохождения:
def my_square_function(number)
number.abs * number
end
К сожалению. Это не совсем сработало, это вызвало сбой теста с отрицательным числом. К счастью, неудача подтолкнула нас к точному тесту, который не сработал, мы знаем, что он провалился из-за «негативного» теста. Вернуться к коду:
def my_square_function(number)
number.abs * number.abs
end
Это лучше, все наши тесты пройдены. Пришло время рефакторинга снова. Здесь мы видим другой ненужный код в этих вызовах abs
. Мы можем избавиться от них:
def my_square_function(number)
number * number
end
Тесты все еще проходят, и мы видим еще одно дублирование с этим противным аргументом. Посмотрим, сможем ли мы от этого избавиться:
def my_square_function(number)
number ** 2
end
Тест пройден, и у нас больше нет этого дублирования. Теперь, когда у нас есть чистая реализация, давайте разберем случай nil
:
describe "my_square_function" do
it "Squares a positive number" do
my_square_function(1).should == 1
end
it "Squares a negative number" do
my_square_function(-1).should == 1
end
it "Squares other positive numbers" do
my_square_function(2).should == 4
end
it "Doesn't try to process 'nil' arguments" do
my_square_function(nil).should == nil
end
end
Хорошо, мы снова вернулись к неудаче, и мы можем продолжить и реализовать проверку nil
:
def my_square_function(number)
number ** 2 unless number == nil
end
Этот тест пройден, и он довольно чистый, поэтому мы оставим все как есть. Теперь вернемся к спецификации и посмотрим, что у нас есть, и убедимся, что нам нравится то, что мы видим:
describe "my_square_function" do
it "Squares a positive number" do
my_square_function(1).should == 1
end
it "Squares a negative number" do
my_square_function(-1).should == 1
end
it "Squares other positive numbers" do
my_square_function(2).should == 4
end
it "Doesn't try to process 'nil' arguments" do
my_square_function(nil).should == nil
end
end
Мое первое желание состоит в том, что мы действительно описываем поведение «возведения в квадрат» числа, а не самой функции, поэтому мы изменим это:
describe "How to square a number" do
it "Squares a positive number" do
my_square_function(1).should == 1
end
it "Squares a negative number" do
my_square_function(-1).should == 1
end
it "Squares other positive numbers" do
my_square_function(2).should == 4
end
it "Doesn't try to process 'nil' arguments" do
my_square_function(nil).should == nil
end
end
Теперь, три примера названия немного мягкие, если их поместить в этот контекст. Я собираюсь начать с первого примера. Кажется, это немного глупо для квадрата 1. Это выбор, который я собираюсь сделать, чтобы уменьшить количество примеров в коде. Я действительно хочу, чтобы примеры были интересными, иначе я не буду их тестировать. Разница между квадратом 1 и 2 неинтересна, поэтому я удалю первый пример. Сначала это было полезно, но не дольше. Это оставляет нас с:
describe "How to square a number" do
it "Squares a negative number" do
my_square_function(-1).should == 1
end
it "Squares other positive numbers" do
my_square_function(2).should == 4
end
it "Doesn't try to process 'nil' arguments" do
my_square_function(nil).should == nil
end
end
Следующая вещь, которую я собираюсь рассмотреть, - это негативный пример, связанный с контекстом в блоке description. Я собираюсь дать ему и остальным примерам новые описания:
describe "How to square a number" do
it "Squaring a number is simply the number multiplied by itself" do
my_square_function(2).should == 4
end
it "The square of a negative number is positive" do
my_square_function(-1).should == 1
end
it "It is not possible to square a 'nil' value" do
my_square_function(nil).should == nil
end
end
Теперь, когда мы ограничили количество тестовых случаев самыми интересными, у нас не так уж много проблем. Как мы видели выше, было приятно знать, на какой именно линии произошли сбои, если это был еще один тестовый случай, который мы не ожидали провала. Составляя список сценариев для прохождения, мы теряем эту функцию, усложняя отладку сбоев. Теперь мы можем заменить примеры динамически генерируемыми блоками it
, как было упомянуто в другом решении, однако мы начинаем терять поведение, которое пытаемся описать.
Итак, в итоге, ограничивая ваши протестированные сценарии только теми, которые описывают интересные характеристики системы, ваша потребность в слишком большом количестве сценариев будет уменьшена. В более сложной системе, имеющей такое количество сценариев, вероятно, подчеркивается, что объектной модели, вероятно, требуется другой вид.
Надеюсь, это поможет!
Brandon