Моделирование условий состязания в модульных тестах 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. Существует ли способ сделать это? Или есть ли некоторая другая техника, которую я пропускаю для моделирования таких условий состязания?

27
задан Ian Lesperance 7 January 2010 в 01:20
поделиться

1 ответ

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

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

class TestSubject

  def insert_unless_exists
    if !row_exists?
      insert_row
    end
  end

end

, но этот код работает на нескольких компьютерах. Затем есть состояние гонки, поскольку другие процессы могут вставлять строку между нашим тестом и нашей вставкой, вызывая исключение дубликатов. Мы хотим проверить, что наш код обрабатывает исключение, которое является результатом этого состояния гонки. Для этого наш тест должен вставлять строку после вызова на ROW_EXISS? , но перед вызовом 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

Разве это не так sly? Как паразитарная личинка, которая угонала тело ничего не подозреваемого гусеницы, тест угнал код в тесте, чтобы он создал точное состояние, которое нам нужно проверить.

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

27
ответ дан 28 November 2019 в 05:45
поделиться
Другие вопросы по тегам:

Похожие вопросы: