Domanda

Abbiamo un'attività asincrona che esegue un calcolo potenzialmente esecuzione prolungata per un oggetto. Il risultato viene poi memorizzato nella cache per l'oggetto. Per impedire più attività di ripetere lo stesso lavoro, abbiamo aggiunto bloccaggio con un aggiornamento SQL atomico:

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

Il bloccaggio è solo per l'attività asincrona. L'oggetto stesso può ancora essere aggiornato dall'utente. Se ciò accade, qualsiasi attività non finito per una vecchia versione dell'oggetto dovrebbe scartare i risultati come sono probabilmente out-of-date. Questo è anche abbastanza facile da fare con un aggiornamento di SQL atomica:

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

Se l'oggetto è stato aggiornato, la sua versione non corrisponderà e quindi i risultati saranno scartati.

Queste due aggiornamenti atomiche dovrebbero gestire eventuali condizioni di gara. La domanda è: come verificare che nel test di unità.

Il primo semaforo è facile verificare, in quanto è semplicemente una questione di impostare due diversi test con i due casi: (1) se l'oggetto è bloccato e (2) se l'oggetto non è bloccato. (Non abbiamo bisogno di testare l'atomicità della query SQL come che dovrebbe essere la responsabilità del fornitore del database.)

Come si fa a testare secondo semaforo? L'oggetto deve essere cambiato da una terza parte qualche tempo dopo il primo semaforo, ma prima del secondo. Ciò richiederebbe una pausa in esecuzione in modo che l'aggiornamento può essere eseguita in modo affidabile e costante, ma so di alcun supporto per iniettare breakpoint con RSpec. C'è un modo per fare questo? O c'è qualche altra tecnica che sto affaccia per la simulazione di tali condizioni di gara?

È stato utile?

Soluzione

È possibile prendere in prestito l'idea dalla produzione di elettronica e mettere i ganci di test direttamente nel codice di produzione. Proprio come una scheda di circuito può essere prodotto con luoghi speciali per apparecchiature di test per il controllo e sondare il circuito, siamo in grado di fare la stessa cosa con il codice.

Supponiamo di avere un codice di inserire una riga nella banca dati:

class TestSubject

  def insert_unless_exists
    if !row_exists?
      insert_row
    end
  end

end

Ma questo codice viene eseguito su più computer. C'è una condizione di gara, poi, dal momento che altri processi possono inserire la riga tra il nostro test e il nostro inserto, causando un'eccezione DuplicateKey. Vogliamo testare che il nostro codice gestisce l'eccezione che deriva da quella condizione di competizione. Per fare questo, il nostro test ha bisogno di inserire la riga dopo la chiamata a row_exists? ma prima della chiamata a insert_row. Quindi aggiungiamo un gancio di prova proprio lì:

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 viene eseguito in natura, il gancio non fa nulla tranne che mangiare un po 'di tempo di CPU. Ma quando il codice viene testato per la condizione di competizione, i test di scimmia-patches before_insert_row_hook:

class TestSubject
  def before_insert_row_hook
    insert_row
  end
end

Non è furbo? Come una larva di vespa parassita che ha dirottato il corpo di un trattore a cingoli ignaro, il test dirottato il codice in prova in modo che possa creare la condizione esatta abbiamo bisogno testato.

Questa idea è così semplice come il cursore XOR, così ho il sospetto molti programmatori hanno inventato in modo indipendente esso. Ho trovato ad essere generalmente utile per testare codice con condizioni di gara. Spero che aiuta.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top