Question

Nous avons une tâche asynchrone qui effectue un calcul potentiellement en cours d'exécution à long pour un objet. Le résultat est ensuite mis en cache sur l'objet. Pour éviter des tâches multiples de répéter le même travail, nous avons ajouté une mise à jour de verrouillage SQL atomique:

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

Le verrouillage est uniquement pour la tâche asynchrone. L'objet lui-même peut encore être mis à jour par l'utilisateur. Si cela se produit, une tâche inachevée pour une ancienne version de l'objet doit jeter ses résultats car ils sont probablement hors de ce jour. Ceci est également assez facile à faire avec une mise à jour de SQL atomique:

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

Si l'objet a été mis à jour, sa version ne correspond pas à et donc les résultats seront mis au rebut.

Ces deux mises à jour atomiques doivent gérer toutes les conditions de course possibles. La question est de savoir comment vérifier que les tests unitaires.

La première est sémaphores facile à tester, car il est tout simplement une question de la mise en place de deux tests différents avec les deux scénarios possibles: (1) où l'objet est verrouillé et (2) où l'objet est pas verrouillé. (Nous ne avons pas besoin de tester l'atomicité de la requête SQL comme cela devrait être la responsabilité du fournisseur de base de données.)

Comment peut-on tester la deuxième sémaphores? L'objet doit être modifié par un tiers un certain temps après la première sémaphores, mais avant la seconde. Cela nécessiterait une pause dans l'exécution afin que la mise à jour peut être effectuée de manière fiable et cohérente, mais je ne connais pas de support pour l'injection avec des points d'arrêt RSpec. Y a-t-il un moyen de faire cela? Ou est-il une autre technique que je suis avec vue pour simuler les conditions de course?

Était-ce utile?

La solution

Vous pouvez emprunter une idée de la fabrication de produits électroniques et de mettre des crochets de test directement dans le code de production. Tout comme une carte de circuit peut être fabriqué avec des endroits spéciaux pour les équipements de test pour contrôler et sonder le circuit, nous pouvons faire la même chose avec le code.

Supposons que nous avons un code d'insérer une ligne dans la base de données:

class TestSubject

  def insert_unless_exists
    if !row_exists?
      insert_row
    end
  end

end

Mais ce code est en cours d'exécution sur plusieurs ordinateurs. Il y a une condition de course, puis, depuis un autre processus peuvent insérer la ligne entre notre test et notre insert, ce qui provoque une exception DuplicateKey. Nous voulons vérifier que notre code gère l'exception qui résulte de cette condition de course. Pour ce faire, notre test doit insérer la ligne après l'appel à row_exists? mais avant l'appel à insert_row. Donc, nous allons ajouter un crochet de test 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

Lors de l'exécution dans la nature, le crochet ne fait rien à part manger un petit peu de temps CPU. Mais lorsque le code est mis à l'essai pour la condition de la race, le singe-patches de test before_insert_row_hook:

class TestSubject
  def before_insert_row_hook
    insert_row
  end
end

est-ce pas rusé? Comme une larve de guêpe parasite qui a pris en otage le corps d'une chenille sans méfiance, le test piraté le code en cours de test afin qu'il crée la condition exacte nous avons besoin testé.

Cette idée est aussi simple que le curseur XOR, donc je suppose que beaucoup de programmeurs ont indépendamment inventé. Je l'ai trouvé à être généralement utile pour tester le code avec des conditions de course. J'espère que ça aide.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top