Моделирование условий гонки в модульных тестах RSpec

У нас есть асинхронная задача, которая выполняет потенциально длительные вычисления для объекта. Затем результат кэшируется в объекте. Чтобы предотвратить повторение одной и той же работы несколькими задачами, мы добавили блокировку с атомарным обновлением SQL:

UPDATE objects SET locked = 1 WHERE id = 1234 AND locked = 0

Блокировка предназначена только для асинхронной задачи. Сам объект все еще может быть обновлен пользователем. Если это произойдет, любая незавершенная задача для старой версии объекта должна отбросить ее результаты, поскольку они, вероятно, устарели. Это также довольно легко сделать с помощью атомарного обновления SQL:

UPDATE objects SET results = '...' WHERE id = 1234 AND version = 1

Если объект был обновлен, его версия не будет совпадать, поэтому результаты будут отброшены.

Эти два атомарных обновления должны обрабатывать любые возможные условия гонки. Вопрос в том, как это проверить в юнит-тестах.

Первый семафор легко протестировать, так как это просто вопрос настройки двух разных тестов с двумя возможными сценариями: (1) когда объект заблокирован и (2) когда объект не заблокирован. (Нам не нужно проверять атомарность SQL-запроса, так как это должно быть обязанностью поставщика базы данных.)

Как проверить второй семафор? Объект должен быть изменен третьей стороной через некоторое время после первого семафора, но до второго. Это потребует паузы в выполнении, чтобы обновление могло выполняться надежно и последовательно, но я не знаю поддержки для внедрения точек останова с помощью RSpec. Есть ли способ сделать это? Или есть какая-то другая техника, которую я упускаю из виду для имитации таких условий гонки?


person Ian Lesperance    schedule 07.01.2010    source источник


Ответы (1)


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

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

class TestSubject

  def insert_unless_exists
    if !row_exists?
      insert_row
    end
  end

end

Но этот код работает на нескольких компьютерах. Таким образом, возникает состояние гонки, поскольку другой процесс может вставить строку между нашим тестом и нашей вставкой, вызывая исключение DuplicateKey. Мы хотим проверить, обрабатывает ли наш код исключение, возникающее в результате этого состояния гонки. Для этого наш тест должен вставить строку после вызова row_exists?, но перед вызовом insert_row. Итак, давайте добавим тестовый хук прямо туда:

class TestSubject

  def insert_unless_exists
    if !row_exists?
      before_insert_row_hook
      insert_row
    end
  end

  def before_insert_row_hook
  end

end

При запуске в дикой природе хук ничего не делает, кроме того, что съедает немного процессорного времени. Но когда код тестируется на состояние гонки, тестовая обезьяна-патч перед_insert_row_hook:

class TestSubject
  def before_insert_row_hook
    insert_row
  end
end

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

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

person Wayne Conrad    schedule 10.01.2010
comment
Ага. Это бы сработало. Хотя вместо того, чтобы добавлять явный хук, я мог бы просто использовать alias_method_chain для расширения функциональности метода, который должен вызываться между двумя семафорами в любом случае — длительная задача. - person Ian Lesperance; 12.01.2010
comment
Кстати, хотелось бы посмотреть, как вы закодировали это с помощью цепочки. Интересная идея. - person aronchick; 13.04.2010
comment
Очень умное решение. Мне это нравится. - person bantic; 16.09.2011
comment
@WayneConrad Я не уверен, злой ли ты гений или гений садового разнообразия, но это..... гений. Спасибо и украл! - person Joe Cairns; 30.07.2018
comment
@JoeCairns Я был бы рад быть любой разновидностью гения. Спасибо за комплимент, и я очень рад, что эта идея помогла вам. - person Wayne Conrad; 30.07.2018