2010-01-07 3 views
25

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

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

ответ

26

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

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

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 

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

class TestSubject 
    def before_insert_row_hook 
    insert_row 
    end 
end 

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

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

+1

A-ha. Это сделало бы это.Хотя вместо того, чтобы добавлять явный крючок, я могу просто использовать 'alias_method_chain' для расширения функциональности метода, который _has_ будет вызываться между двумя семафорами в любом случае - долговременной задачей. – Ian

+0

Ian, Это сделало бы это. –

+4

+1 для использования паразитарной личинки осы в вашем сравнении. – aronchick

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