Вопрос

У нас есть асинхронная задача, которая выполняет потенциально длительные вычисления для объекта.Результат затем кэшируется на объекте.Чтобы предотвратить повторение одной и той же работы несколькими задачами, мы добавили блокировку с помощью атомарного обновления 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.Есть ли способ сделать это?Или есть какой-то другой метод, который я упускаю из виду для моделирования таких условий гонки?

Это было полезно?

Решение

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

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

class TestSubject

  def insert_unless_exists
    if !row_exists?
      insert_row
    end
  end

end

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

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

class TestSubject
  def before_insert_row_hook
    insert_row
  end
end

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top