2014-06-09 1 views
1

Je n'ai pas fait de tordre depuis quelques années et j'ai commencé à utiliser le nouveau style d'agent des appels http du client. L'utilisation de l'agent a été OK, mais les tests me déroutent (c'est tordu après tout).Tests unitaires twisted.web.client.Agent sans le réseau

J'ai passé en revue les docs https://twistedmatrix.com/documents/current/core/howto/trial.html et les API sur les outils d'évaluation et l'agent lui-même. Aussi de nombreuses recherches.

Je suis parti avec l'agent, car je n'ai pas besoin de le tester. Mais à cause des étapes pour traiter le traitement et la réponse d'une requête d'Agent, mon code de test est méchant, implémentant les couches imbriquées de l'Agent, du protocole, etc. Où dois-je tracer la ligne et y at-il des utilitaires? pas trouvé?

Voici un exemple minimal (de mise en œuvre naïve du SUT):

from twisted.web.client import Agent, readBody 
from twisted.internet import reactor 
import json 

class SystemUnderTest(object): 

    def __init__(self, url): 
     self.url = url 

    def action(self): 
     d = self._makeAgent().request("GET", self.url) 
     d.addCallback(self._cbSuccess) 
     return d 

    def _makeAgent(self): 
     ''' It's own method so can be overridden in tests ''' 
     return Agent(reactor) 

    def _cbSuccess(self, response): 
     d = readBody(response) 
     d.addCallback(self._cbParse) 
     return d 

    def _cbParse(self, data): 
     self.result = json.loads(data) 
     print self.result 

avec le module de test:

from twisted.trial import unittest 
from sut import SystemUnderTest 
from twisted.internet import defer 
from twisted.test import proto_helpers 

class Test(unittest.TestCase): 

    def test1(self): 
     s_u_t = ExtendedSystemUnderTest(None) 
     d = s_u_t.action() 
     d.addCallback(self._checks, s_u_t) 
     return d 

    def _checks(self, result, s_u_t): 
     print result 
     self.assertEqual({'one':1}, s_u_t.result) 


class ExtendedSystemUnderTest(SystemUnderTest): 

    def _makeAgent(self): 
     return FakeSuccessfulAgent("{'one':1}") 

## Getting ridiculous below here... 

class FakeReason(object): 
    def check(self, _): 
     return False 
    def __str__(self): 
     return "It's my reason" 

class FakeResponse(object): 
    ''' Implementation of IResponse ''' 
    def __init__(self, content): 
     self.content = content 
     self.prot = proto_helpers.StringTransport() 
     self.code = 200 
     self.phrase = '' 

    def deliverBody(self, prot): 
     prot.makeConnection(self.prot) 
     prot.dataReceived(self.content) 
#  reason = FakeReason() 
#  prot.connectionLost(reason) 

class FakeSuccessfulAgent(object): 
    ''' Implementation of IAgent ''' 
    def __init__(self, response): 
     self.response = response 

    def request(self, method, url): 
     return defer.succeed(FakeResponse(self.response)) 

Répondre

2

mais les tests me confusion (il est tordu après tout).

Hilarant.

class ExtendedSystemUnderTest(SystemUnderTest): 
    def _makeAgent(self): 
     return FakeSuccessfulAgent("{'one':1}") 

Je suggère que vous fassiez l'agent pour utiliser un paramètre normal. C'est plus pratique qu'une méthode privée comme _makeAgent. La composition est géniale. L'héritage est meh.

class FakeReason(object): 
    ... 

Il n'y a aucune raison d'en faire un faux. Utilisez simplement twisted.python.failure.Failure. Vous n'avez avoir pour simuler chaque objet dans le test. Juste ceux qui vous gênent si vous ne les trafiquez pas.

class FakeResponse(object): 
    ... 

Ceci est probablement bon et nécessaire.

class FakeSuccessfulAgent(object): 
    ... 

Ceci est probablement nécessaire aussi bien. Vous devriez le rendre plus comme une implémentation IAgent cependant - déclarez qu'il implémente l'interface, utilisez zope.interface.verify.verify{Class,Object} pour vous assurer que vous avez l'écriture de l'implémentation, etc (par exemple, request a la mauvaise signature maintenant).

Il existe en fait un ticket permettant d'ajouter tous ces outils de test à Twisted lui-même - https://twistedmatrix.com/trac/ticket/4024. Donc je ne pense pas que vous soyez réellement confus, vous êtes fondamentalement sur la même voie que le projet lui-même. Tu souffres juste du fait que Twisted n'a pas déjà fait tout ce travail pour toi.

Aussi, notez qu'au lieu de:

class Test(unittest.TestCase): 

    def test1(self): 
     s_u_t = ExtendedSystemUnderTest(None) 
     d = s_u_t.action() 
     d.addCallback(self._checks, s_u_t) 
     return d 

Vous pouvez écrire quelque chose comme ça à la place (et il est préférable):

class Test(unittest.TestCase): 

    def test1(self): 
     s_u_t = ExtendedSystemUnderTest(None) 
     d = s_u_t.action() 
     self._checks(s_u_t, self.successResultOf(d)) 

Ceci est parce que votre implémentation factice de IAgent est synchrone. Vous savez que c'est synchrone.Au moment request renvoie, le Deferred il renvoie déjà un résultat. Écrire le test de cette façon signifie que vous pouvez simplifier un peu votre code (ie, vous pouvez ignorer l'asynchronisme dans une certaine mesure - parce qu'il ne l'est pas) et cela évite d'exécuter le global réacteur qui est ce qui retourne un Deferred depuis un méthode d'essai dans le procès fait.

+1

Un grand merci Jean-Paul. C'est bon à entendre, il y a un ticket pour ça parce que ça a vraiment l'air de manquer. Notez que si cela est exécuté, il se bloque. On dirait que le protocole attend quelque chose après le dataReceived() (ce qui explique pourquoi j'ai déconnecté commenté ici). Des indices quant à ce qu'il manque pour terminer? Merci –

+0

Il se bloque même si l'appel 'connectionLost' n'est pas commenté? –

+0

Non dans ce cas, il erreurs avec ma raison. Comment puis-je le faire réussir? PS merci pour les conseils de style, j'ai fait ces changements –