2010-03-06 23 views
2

J'écris un service en utilisant Thrift et j'ai besoin d'appliquer quelques tests pour m'assurer qu'il fonctionne/répond comme prévu. Pour ce faire, l'approche la plus robuste semble être d'utiliser le module unittest.Tests d'intégration: Démarrer un serveur de blocage pendant `unittest.setUp` avant de le tester?

Je voudrais commencer le service en mode « test » (commence sur un port « test » spécifique, utilise des données « test », etc.) directement à partir de la méthode setUp du test unitaire, mais appelant serve() blocs à cette point en attente de connexions. Quelle serait la meilleure approche pour démarrer le service afin que les tests puissent s'exécuter et que le service puisse être ramené proprement à l'aide de la méthode tearDown?

Répondre

7

Le « isolement complet » qui unittest fournit est excellent pour unité essais (ce qu'il est conçu pour), mais pas nécessairement pour intégration des tests - je vois l'attrait de la réutilisation unittest pour ceux, je le fais moi-même pour profiter des tests spéciaux & c que nous avons autour, mais, en réalisant que c'est quelque chose de force à utiliser unittest pour intégration tests, j'essaie de compenser en codant plutôt différemment que je voudrais coder les tests unitaires. Lorsque je dois lancer un serveur dans un test d'intégration, j'ai tendance à lancer le processus au début du module, dans un processus séparé - via subprocess, ou par d'autres moyens appropriés à votre installation, si vous voulez exécutez-le dans un nœud séparé ou autre - et enregistrez avec atexit le code de terminaison qui enverra à ce serveur la demande de terminaison lorsque mon module de test sera terminé. Ce n'est pas aussi "distinctement séparé" que le test de l'unité, mais je trouve qu'il est suffisant pour les tests d'intégration, et amortit la surcharge de démarrage et d'initialisation du serveur, qui peut être très élevé, à travers plusieurs tests. Même si vous souhaitez utiliser setUp et tearDown, je vous recommande quand même d'utiliser un processus séparé (et un nœud séparé, si vous en avez beaucoup dans une "ferme de construction continue" ou autre). Utiliser un thread différent dans le même processus, comme le suggère la réponse de @ Ned, me semble risqué: il pourrait facilement produire des interactions non désirées entre le serveur et les tests, en cachant un bug ou en provoquant d'autres. Si je comprends bien, vous essayez d'exécuter le serveur non seulement sur le même processus, mais même dans le même fil que les tests, et cela me semble définitivement une mauvaise idée - bien sûr, il va tout bloquer , sauf si le serveur est codé très en effet! -)

+0

Est-ce encore votre pratique en 2016?Il me semble que webtest et mock peuvent être utilisés pour sortir le socket de l'équation mais toujours obtenir l'OP ce qu'ils veulent. – BrianTheLion

+0

@BrianTheLion, le fait de se moquer de vous ne vous permet pas de tester ** l'intégration ** - la distinction clé avec les tests ** unit ** mérite d'être bien prise en compte! –

+0

Mock peut permettre la capture des données de requête sortantes vers le socket sous la forme d'une chaîne. Cette chaîne est ensuite utilisée - dans le même processus - pour construire un objet de requête côté serveur, qui est fourni à webtest.TestApp. Une simulation similaire du côté serveur permet de capturer la réponse et de la renvoyer au client, sans fork() ing. Test d'intégration réalisé. (Notez que cette approche ne fonctionne pas dans les scénarios de streaming.) – BrianTheLion

0

Si serve() blocs, alors votre meilleur pari est de générer un fil dans setUp pour appeler serve(). Ensuite, vous devez comprendre comment arrêter Thrift dans la méthode tearDown.

1

Vous pouvez créer un gestionnaire de contexte pour lancer votre test pendant qu'un serveur fonctionne en arrière-plan.

if __name__ == '__main__': 
    with background_server(): 
     print('Server loaded, launching the tests...') 
     unittest.main(exit=False) 

Le code que j'utilise pour background_server:

@contextlib.contextmanager 
def background_server(): 
    # Launching the server 
    pid_server = os.fork() 
    if not pid_server: # Child code 
     launch_server() # Blocking call (until signal.SIGINT) 
     print('Interuption detected, server closed...') 
     sys.exit() # Closing the process 

    # HACK: Wait for the server to be launched 
    while True: 
     try: 
      requests.get("http://localhost:5000/", timeout=0.5) 
      break 
     except requests.exceptions.ConnectionError: 
      pass 
     time.sleep(0.3) 

    try: 
     yield 
    finally: 
     # Closing the server 
     os.kill(pid_server, signal.SIGINT)