2009-03-11 13 views
6

Поскольку я начал использовать rspec, у меня была проблема с понятием светильников. Мои основные проблемы заключаются в следующем:Является ли плохой практикой случайное генерирование тестовых данных?

  1. Я использую тестирование, чтобы выявить удивительное поведение. Я не всегда достаточно сообразителен, чтобы перечислять все возможные примеры кросс-тестов для примеров, которые я тестирую. Использование жестко закодированных светильников кажется ограниченным, потому что он проверяет только мой код с очень конкретными случаями, которые я себе представлял. (По общему признанию, мое воображение также ограничивает, в каких случаях я тестирую.)

  2. Я использую тестирование как форму документации для кода. Если у меня есть жестко закодированные значения прибора, трудно показать, что пытается продемонстрировать конкретный тест. Например:

    describe Item do 
        describe '#most_expensive' do 
        it 'should return the most expensive item' do 
         Item.most_expensive.price.should == 100 
         # OR 
         #Item.most_expensive.price.should == Item.find(:expensive).price 
         # OR 
         #Item.most_expensive.id.should == Item.find(:expensive).id 
        end 
        end 
    end 
    

    Используя первый метод не дает читателю никаких указаний на то, что самый дорогой пункт является только то, что его цена составляет 100. Все три метода попросить читателя принять на веру, что прибор :expensive является самый дорогой, указанный в fixtures/items.yml. Небрежный программист мог нарушить тесты, создав Item в before(:all) или вставив другое приспособление в fixtures/items.yml. Если это большой файл, может потребоваться много времени, чтобы выяснить, в чем проблема.

Одна вещь, которую я начал делать это добавить метод #generate_random для всех моих моделей. Этот метод доступен только тогда, когда я запускаю свои спецификации. Например:

class Item 
    def self.generate_random(params={}) 
    Item.create(
     :name => params[:name] || String.generate_random, 
     :price => params[:price] || rand(100) 
    ) 
    end 
end 

(. Конкретные детали того, как я делаю это на самом деле немного чище у меня есть класс, который обрабатывает генерацию и очистку всех моделей, но этот код достаточно ясно для моего примера.) Поэтому в приведенном выше примере я могу проверить следующее. Предупреждение для финтого сердца: мой код в значительной степени зависит от использования before(:all):

describe Item do 
    describe '#most_expensive' do 
    before(:all) do 
     @items = [] 
     3.times { @items << Item.generate_random } 
     @items << Item.generate_random({:price => 50}) 
    end 

    it 'should return the most expensive item' do 
     sorted = @items.sort { |a, b| b.price <=> a.price } 
     expensive = Item.most_expensive 
     expensive.should be(sorted[0]) 
     expensive.price.should >= 50  
    end 
    end 
end 

Таким образом, мои тесты лучше выявить удивительное поведение. Когда я генерирую данные таким образом, я иногда натыкаюсь на краевой случай, когда мой код не ведет себя так, как ожидалось, но который я бы не поймал, если бы использовал только приборы. Например, в случае с #most_expensive, если я забыл обработать специальный случай, когда несколько предметов имеют самую дорогую цену, мой тест будет иногда терпеть неудачу на первых should. Видя, что не-детерминированные сбои в AutoSpec подскажут мне, что что-то не так. Если бы я использовал только приборы, может потребоваться гораздо больше времени, чтобы обнаружить такую ​​ошибку.

Мои тесты также дают немного лучшую работу, демонстрируя в коде, каково ожидаемое поведение. В моем тесте ясно, что отсортированный массив отсортирован по убыванию в порядке убывания. Поскольку я ожидаю, что #most_expensive будет равен первому элементу этого массива, еще более очевидно, что ожидаемое поведение most_expensive.

Итак, это плохая практика? Является ли мой страх перед светильниками иррациональным? Является ли метод generate_random для каждой модели слишком большой работой? Или это работает?

+0

Линия «3.times {@items 50})« не выглядит правильной. –

+1

И теперь, всего через 58 месяцев, я отвечаю ... Это выглядит не так, потому что у него есть «< <» в нем ... но не удалось сбежать. – bobocopy

ответ

5

Это ответ на ваш второй пункт:

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

Согласен. Идеальные примеры должны быть понятны сами по себе. Использование приборов проблематично, поскольку оно разбивает предварительные условия примера на ожидаемые результаты.

Из-за этого многие пользователи RSpec полностью прекратили использование светильников. Вместо этого создайте необходимые объекты в самом примере spec.

describe Item, "#most_expensive" do 
    it 'should return the most expensive item' do 
    items = [ 
     Item.create!(:price => 100), 
     Item.create!(:price => 50) 
    ] 

    Item.most_expensive.price.should == 100 
    end 
end 

Если в конечном итоге с большим количеством шаблонного кода для создания объекта, вы должны взглянуть на некоторые из многих заводских библиотек испытаний объектов, таких как factory_girl, Machinist или FixtureReplacement.

+0

Является ли ссылка FixtureReplacement нарушенной? –

+0

Множество отличных ответов, но этот режет на преследование - есть лучший способ сделать то, что я хочу сделать, и мои тестовые данные больше не должны быть «случайными». – bobocopy

+0

bobocopy: Кажется, так. Странно, я думаю, что он работал вчера. Теперь это исправлено. –

0

Одна проблемы случайно сгенерированные тестовые случаи является то, что проверка ответа должно быть вычислена с помощью кода, и вы не можете быть уверены, что он не имеет ошибок :)

+0

Тесты и код проверяют друг друга. Если у вашего теста есть ошибки, вы быстро узнаете. :) –

4

Мы много думали о недавнем моем проекте. В итоге мы остановились на двух пунктах:

  • Повторяемость тестовых примеров имеет первостепенное значение. Если вы должны написать случайный тест, будьте готовы его документально документировать, потому что если/когда он терпит неудачу, вам нужно будет точно знать, почему.
  • Использование случайности в качестве костыля для покрытия кода означает, что у вас либо нет хорошего покрытия, либо вы недостаточно понимаете домен, чтобы знать, что представляет собой типичные тестовые случаи. Выясните, что верно и исправить.

В целом, случайность часто может быть больше проблем, чем того стоит. Внимательно изучите, правильно ли вы используете его, прежде чем нажимать на курок. В конечном итоге мы решили, что случайные тестовые случаи были плохими идеями в целом и использовались экономно, если вообще.

+0

Я использую случайные данные теста широко. У меня никогда не было ни одной ситуации, когда было бы больше неприятностей, чем было бы. Мои случайные тесты достаточно просты, и я всегда могу точно сказать, почему они терпят неудачу. Я * имел * случайное тестирование, обнаружил ошибочные предположения в моем коде. Случайные тестовые примеры - гораздо лучшая идея, чем жестко закодированные, и должны использоваться везде, где это возможно. Никогда не производите жесткие коды тестовых данных, если вы можете избежать этого - это обман в пасьянсе. –

+0

Кроме того, вам не нужен генератор случайных чисел. Дамп значения в неудавшемся тестовом случае работает так же хорошо. –

13

Я удивлен, что никто в этой теме или в одном Jason Baker linked to не упоминается Monte Carlo Testing. Это единственный раз, когда я широко использовал рандомизированные тестовые входы. Однако было очень важно сделать тест воспроизводимым, имея постоянное семя для генератора случайных чисел для каждого тестового примера.

+0

+1 для воспроизводимого комментария. Управление начальным состоянием случайного генератора очень важно. Если вы обнаружите странное поведение, вам захочется попробовать еще раз. –

+0

другой +1 для воспроизводимого. – peterchen

+0

Сторонник. Когда я использовал рандомизированное тестирование, я всегда добавляю способ сообщения и установки семени. Хотя в целом я стараюсь не полагаться на рандомизацию ... – Jason

2

Много хорошей информации уже опубликованы, но вот смотрите также: Fuzz Testing. Слово на улице - это то, что Microsoft использует этот подход во многих своих проектах.

+0

Я рад, что кто-то поднял это. Fuzz Testing чрезвычайно полезно, но обратите внимание, что случайное тестирование должно быть * дополнительно * к повторяемым тестам. – vasi

+0

@vasi Если «случайный» включает в себя псевдослучайное, то он не противоречит повторяемости. Как насчет регистрации семени? –

1

Мой опыт тестирования - это, в основном, простые программы, написанные на C/Python/Java, поэтому я не уверен, что это полностью применимо, но всякий раз, когда у меня есть программа, которая может принимать любой пользовательский ввод, я всегда включать тест со случайными входными данными или, по крайней мере, входные данные, созданные компьютером непредсказуемым образом, потому что вы никогда не можете делать предположения о том, что пользователи будут вводить. Или, ну, вы, , можете, но если вы это сделаете, то какой-то хакер, который не делает этого предположения, вполне может найти ошибку, которую вы полностью игнорируете. Машиногенерируемый вход - лучший (только?) Способ, который я знаю, чтобы полностью исключить человеческий уклон из процедур тестирования. Конечно, чтобы воспроизвести неудачный тест, вы должны сделать что-то вроде сохранения тестового ввода в файл или его распечатки (если это текст) перед запуском теста.

1

Случайное тестирование - это плохая практика, так как у вас нет решения проблемы оракула, то есть определения того, что является ожидаемым результатом вашего программного обеспечения с учетом его ввода.

Если вы решили проблему оракула, вы можете сделать еще один шаг вперед, чем простое случайное генерирование ввода. Вы можете выбрать распределение входных данных, чтобы определенные части вашего программного обеспечения выполнялись больше, чем с помощью простого случайного.

Затем вы переходите от случайного тестирования к статистическому тестированию.

if (a > 0) 
    // Do Foo 
else (if b < 0) 
    // Do Bar 
else 
    // Do Foobar 

При выборе a и b случайно в int диапазоне, вы тренируетесь Foo 50% времени, Bar 25% времени и Foobar 25% времени. Вероятно, вы найдете больше ошибок в Foo, чем в Bar или Foobar.

Если вы выберете a так, чтобы он был отрицательным 66.66% времени, Bar и Foobar получите упражнение больше, чем с первого распространения.Действительно, три филиала получают упражнение каждые 33,33% времени.

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

+0

Для этого вам не требуется статистическое тестирование - просто измеримое соотношение между вашим входом и вашим выходом. –

1

я предложил бы иметь взглянуть на Машинисте:

http://github.com/notahat/machinist/tree/master

МАШИНИСТ будет генерировать данные для вас, но это повторяется, поэтому каждый тест-прогон имеет те же случайные данные ,

Вы можете сделать что-то подобное, последовательно поместив генератор случайных чисел.

+0

Вам нужно иметь ActiveRecord/Rails для использования машиниста? –

+0

Я считаю, что это зависит от ActiveRecord, но вы можете использовать его вне Rails. –

0

Эффективность такого тестирования во многом зависит от качества генератора случайных чисел, который вы используете, и от того, насколько правильным является код, который преобразует вывод RNG в тестовые данные.

Если RNG никогда не создает значения, приводящие к тому, что ваш код попадает в какое-либо условие кромки, у вас не будет этого случая. Если ваш код, который преобразует вывод RNG в ввод кода, который вы испытываете, является дефектным, может случиться так, что даже с хорошим генератором вы все равно не будете ударять по всем краям.

Как вы это испытаете?

0

Проблема со случайностью в тестовых случаях заключается в том, что выход является случайным.

Идея проведения тестов (особенно регрессионных тестов) заключается в том, чтобы проверить, что ничего не сломано.

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

Другими словами, если у вас есть тест, который использует случайные данные, сгенерированные на лету, я думаю, что это плохая идея. Если, однако, вы используете набор случайных данных, КОТОРЫЕ ВЫ ДОЛЖНЫ БЫТЬ МАГАЗИНЫМИ И ИСПОЛЬЗУЮТСЯ, это может быть хорошей идеей. Это может иметь форму набора семян для генератора случайных чисел.

Это сохранение сгенерированных данных позволяет найти «правильный» ответ на эти данные.

Таким образом, я бы рекомендовал использовать случайные данные, чтобы изучить систему, но использовать определенные данные в тестах (которые, возможно, первоначально были случайным образом сгенерированных данных)

0

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

Я настоятельно рекомендую использовать Factory Girl и ffaker для этого. (Никогда не используйте приспособления Rails для чего-либо при любых обстоятельствах.)

 Смежные вопросы

  • Нет связанных вопросов^_^