Pergunta

Nós temos uma tarefa assíncrona que executa uma potencialmente longa de cálculo para um objeto. O resultado é então armazenada em cache no objeto. Para evitar que várias tarefas de repetir o mesmo trabalho, nós adicionamos bloqueio com uma atualização SQL atômica:

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

O bloqueio é apenas para a tarefa assíncrona. O objeto em si pode ainda ser atualizados pelo usuário. Se isso acontecer, qualquer tarefa inacabada para uma versão antiga do objeto deve descartar seus resultados como eles estão propensos out-of-date. Esta é também muito fácil de fazer com um atualizar SQL atômica:

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

Se o objeto foi atualizado, sua versão não irá corresponder e assim os resultados serão descartados.

Estas duas atualizações atômicas deve lidar com quaisquer condições de corrida possíveis. A questão é como verificar que em testes de unidade.

O primeiro semáforo é fácil de teste, como é simplesmente uma questão de criação de dois testes diferentes, com os dois cenários possíveis: (1) onde o objeto está bloqueado e (2) em que o objeto não está bloqueado. (Nós não precisamos de testar a atomicidade da consulta SQL como que deve ser da responsabilidade do fornecedor do banco de dados.)

Como é que um teste do segundo semáforo? O objeto precisa ser mudado por um terceiro algum tempo após o primeiro semáforo, mas antes do segundo. Isso exigiria uma pausa em execução para que a atualização pode ser confiável e consistente realizada, mas eu sei de nenhum apoio para injetar breakpoints com RSpec. Existe uma maneira de fazer isso? Ou há alguma outra técnica que eu estou com vista para simular essas condições de corrida?

Foi útil?

Solução

Você pode pedir uma ideia de Electronics Manufacturing e ganchos de teste put diretamente no código de produção. Assim como uma placa de circuito pode ser fabricado com lugares especiais para equipamentos de teste para controle e sondar o circuito, podemos fazer a mesma coisa com o código.

Suponha que tenhamos algum código de inserir uma linha no banco de dados:

class TestSubject

  def insert_unless_exists
    if !row_exists?
      insert_row
    end
  end

end

Mas este código está sendo executado em vários computadores. Há uma condição de corrida, então, uma vez mais processos podem inserir a linha entre o nosso teste e nossa inserção, causando uma exceção DuplicateKey. Queremos teste que nosso código lida com a ressalva de que os resultados de que condição de corrida. A fim de fazer isso, o nosso teste precisa inserir a linha após a chamada para row_exists? mas antes da chamada para insert_row. Então, vamos adicionar um gancho de teste ali:

class TestSubject

  def insert_unless_exists
    if !row_exists?
      before_insert_row_hook
      insert_row
    end
  end

  def before_insert_row_hook
  end

end

Quando executado no estado selvagem, o gancho não faz nada exceto comer um pouquinho de tempo de CPU. Mas quando o código está sendo testado para a condição de corrida, o teste de macaco-patches before_insert_row_hook:

class TestSubject
  def before_insert_row_hook
    insert_row
  end
end

Não é dissimulado? Como uma larva de vespa parasita que seqüestrou o corpo de uma lagarta desavisado, o teste sequestrado o código sob teste para que ele irá criar a condição exata que precisamos testado.

Esta ideia é tão simples quanto o cursor XOR, então eu suspeito que muitos programadores ter inventado de forma independente. Eu tê-lo encontrado para ser geralmente útil para testar código com condições de corrida. Espero que ajude.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top