Railstutorial.org Проверка уникальной электронной почты - PullRequest
4 голосов
/ 04 января 2012

В разделе 6.2.4 Учебное пособие по Ruby on Rails 3 Майкл Хартл описывает предостережение о проверке уникальности адресов электронной почты: если два идентичных запроса совпадают по времени, запрос A может пройти проверку, затем B Пройдите валидацию, затем A сохраните, затем B сохраните, и вы получите две записи с одинаковым значением. Каждый был действителен на момент проверки.

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

Будучи совершенно новым для rspec, мой наивный подход заключался в том, чтобы просто написать сценарий:

it 'should reject duplicate email addresses with caveat' do
  A = User.new( @attr ) 
  A.should be_valid          # always valid

  B = User.new( @attr )
  B.should be_valid          # always valid, as expected

  A.save.should == true      # save always works fine

  B.save.should == false     # this is the problem case
  # B.should_not be_valid    # ...same results as "save.should"
end

но этот тест проходит / не проходит в тех же случаях, что и обычный тест на уникальность; B.save.should == false проходит, когда мой код написан так, что обычный тест на уникальность проходит и не проходит, если обычный тест не проходит.

Итак, мой вопрос: «Как мне написать тест rspec, который проверит, что я решаю эту проблему?» Если ответ окажется «это сложно», есть ли другая платформа тестирования Rails, на которую я должен обратить внимание?

1 Ответ

4 голосов
/ 04 января 2012

Это сложно.Гоночные условия настолько неприятны именно потому, что их так сложно воспроизвести.Внутренне, save выглядит примерно так:

  1. Проверка.
  2. Запись в базу данных.

Итак, чтобы воспроизвести проблему синхронизации, вы 'Мне нужно расположить два вызова save так, чтобы они перекрывались (псевдорельсы):

a.validate    # first half of a.save
b.validate    # first half of b.save
a.write_to_db # second half of a.save
b.write_to_db # second half of b.save

, но вы не можете так просто открыть метод save и возиться с его внутренностями.

Но (и это большое но), вы можете полностью пропустить проверки :

Обратите внимание, что save также имеет возможность пропускать проверкиесли передано :validate => false в качестве аргумента.Эту технику следует использовать с осторожностью.

Поэтому, если вы используете

b.save(:validate => false)

, вы должны получить только половину "записи в базу данных" из b saveи отправьте ваши данные в базу данных без проверки.Это должно вызвать нарушение ограничения в базе данных, и я уверен, что это вызовет исключение ActiveRecord :: StatementInvalid , поэтому я думаю, что вам нужно искать исключение, а не просто ложное возвращение из save:

b.save(:validate => false).should raise_exception(ActiveRecord::StatementInvalid)

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

...