Comment puis-je tester une classe qui nécessite un appel de service Web?

21

J'essaie de tester une classe qui appelle certains services Web Hadoop. Le code est à peu près de la forme:

method() {
    ...use Jersey client to create WebResource...
    ...make request...
    ...do something with response...
}

par exemple, il existe une méthode de création de répertoire, une méthode de création de dossier, etc.

Étant donné que le code traite d'un service Web externe sur lequel je n'ai pas de contrôle, comment puis-je le tester à l'unité? Je pourrais essayer de me moquer du client du service Web / des réponses, mais cela rompt la directive que j'ai beaucoup vue récemment: "Ne vous moquez pas d'objets que vous ne possédez pas". Je pourrais mettre en place une implémentation de service Web factice - cela constituerait-il toujours un "test unitaire" ou serait-ce alors un test d'intégration? N'est-il tout simplement pas possible de faire des tests unitaires à un niveau aussi bas - comment un praticien TDD s'y prendrait-il?

Chris Cooper
la source
5
Où avez-vous vu des conseils pour ne pas se moquer de choses qui ne vous appartiennent pas? Cela semble être une énorme raison pour laquelle vous devriez vous moquer des choses ...
Thomas Owens
1
Je l'ai lu dans de nombreux blogs, dont l'un fait référence à amazon.com/Growing-Object-Oriented-Software-Guided-Tests/dp/… que je connais comme un livre réputé ( blog.8thlight. com / eric-smith / 2011/10/27 / thats-not-yours.html , mockobjects.com/2007/04/test-smell-everything-is-mocked.html )
Chris Cooper du
1
@ChrisCooper: Puis-je souligner que le dernier lien est très obsolète (depuis 2007). Beaucoup a changé depuis lors. J'ai l'impression de la publication que la simulation était beaucoup plus difficile à l'époque lorsque vous deviez réellement mettre en œuvre un comportement au lieu d'utiliser simplement un cadre de simulation ou une métaprogrammation pour définir des valeurs de retour ...
c_maker

Réponses:

41

À mon avis, vous devriez vous moquer des appels du service Web s'il s'agit d'un test unitaire, par opposition à un test d'intégration.

Votre test unitaire ne doit pas tester si le service Web externe fonctionne ou si votre intégration avec celui-ci est correcte. Sans devenir trop dogmatique sur le TDD, notez qu'un effet secondaire de la transformation de votre test unitaire en test d'intégration est qu'il est susceptible de s'exécuter plus lentement, et vous voulez des tests unitaires rapides .

De plus, si le service Web est temporairement arrêté ou ne fonctionne pas correctement, cela devrait-il entraîner l'échec de votre test unitaire? Cela ne semble pas correct. Votre test unitaire devrait échouer pour une seule raison: s'il y a un bogue dans le code de cette "unité".

La seule partie de code pertinente ici est ...do something with response.... Se moquer du reste.

Andres F.
la source
2
N'oubliez pas que vous devrez conserver la signature de votre faux objet et renvoyer les valeurs synchronisées avec celles générées par le service Web Hadoop tout au long des inévitables modifications apportées à ce service.
pcurry
Il vaut rarement la peine de tester des composants standard tels que Hadoop. Mais si vous appelez un service Web personnalisé fourni par une autre équipe ou organisation, vous souhaiterez peut-être en faire un test d'autodéfense. De cette façon, lorsque les choses tournent mal, vous pouvez rapidement vérifier si le problème est votre code ou le service Web. Ce n'est pas un test unitaire à exécuter automatiquement; c'est un diagnostic à exécuter selon les besoins.
kevin cline
@kevincline Je suis entièrement d'accord sur la nécessité des tests que vous proposez, et en effet je les écris dans mon travail de jour et je me suis avéré utile. Mais ce ne sont pas par définition des tests unitaires, c'est de cela qu'il s'agissait :) Considérez ceci: s'il s'agit d'un test unitaire et que le code échoue parce que le service Web a été modifié, quelle est l '"unité" que vous testez? Qu'est-ce qui a échoué exactement? Vous ne testez pas isolément, comme requis par les tests unitaires.
Andres F.
1
@AndresF .: Je pense que nous sommes en accord violent: "Ce [diagnostic] n'est PAS un test unitaire ..."
kevin cline
@kevincline C'est vrai! J'ai mal lu votre commentaire, désolé!
Andres F.
5

Je ne suis pas d'accord avec "ne vous moquez pas d'objets que vous ne possédez pas" lorsque vous effectuez des tests unitaires.

Le but de l'existence des moqueries est le fait qu'il y aura des modules, des bibliothèques, des classes que nous ne posséderons pas.

Ma suggestion pour votre scénario est de se moquer de l'appel du service Web.

Configurez la maquette de manière à ce qu'elle renvoie les données à votre module.
Assurez-vous de couvrir tous les scénarios, par exemple lorsque les données renvoyées sont nulles, lorsque les données renvoyées sont valides, etc.

Et pour le code que vous possédez, votre responsabilité en tant que développeur est de vous assurer que le code que vous créez fonctionne comme prévu dans tous les scénarios.

Venu b
la source
1

J'utiliserais quelque chose comme EasyMock pour ce test. Les frameworks de simulation sont un moyen idéal pour supprimer les dépendances extérieures sur une classe et vous donnent un contrôle total sur le résultat des dépendances extérieures pendant les tests. Pour prolonger un peu votre exemple:

class WebClass {

private WebServiceInterface webserviceInterface;

    void method(){
        R result = webServiceInterface.performWebServiceCall();
        ... do something with result
    }

    public void setWebServiceInterface(WebServiceInterface webServiceInterface){
        this.webServiceInterface = webServiceInterface;
    }
}


interface WebServiceInterface {

   R performWebServiceCall();

}


class WebClassTest {

private WebServiceInterface mock;    
private R sampleResult = new R();

    @Before
    public void before(){
        mock = EasyMock.createMock(WebServiceInterface.class);
    }


    @Test
    public void test() {
        WebClass classUnderTest = new WebClass();
        EasyMock.expect(mock.performWebServiceCall()).andReturn(sampleResult);
        classUnderTest.setWebServiceInterface(mock);
        classUnderTest.method();
        EasyMock.verify(mock);
    }
}

La première chose que vous devez faire est d'extraire la logique de votre classe où vous utilisez Jersey pour obtenir une ressource Web et appeler le service Web dans une classe distincte. La création d'une interface pour cette classe vous permettra de créer une maquette à laquelle vous pourrez ensuite dicter le comportement.

Une fois cette interface créée, vous pouvez créer une maquette à l'aide d'EasyMock, qui renverra un objet spécifié selon votre scénario de test. L'exemple ci-dessus est une simplification de la façon de structurer un test simulé de base et du fonctionnement de votre interface.

Pour plus d'informations sur les frameworks de simulation, veuillez consulter cette question . De plus, cet exemple suppose l'utilisation de Java mais les frameworks de simulation sont disponibles dans tous les langages et bien qu'ils soient implémentés différemment, ils fonctionneront généralement de la même manière

Richard
la source
1

Les simulacres sont acceptables dans ce cas, mais vous n'en avez pas besoin. Au lieu de tests unitaires, testez à la method()place uniquement la partie qui gère la réponse.

Extraire une fonction qui prend ResponseData(quel que soit le type approprié) puis exécute l'action.

Au lieu de vous moquer, il vous suffit maintenant de construire un objet ResponseData et de le transmettre.

Vous pouvez laisser l' appel du service à des tests d'intégration complets - qui couvriront method()au total

Daenyth
la source
0

Ce que j'ai fait et ça marche:

  1. Demandez à tous les services Web d'appel de code via un proxy.
  2. Le proxy appelle une classe qui sait statiquement si nous utilisons un proxy ou non et redirige en conséquence. Les mocks ne sont que des HashMaps qui, pour chaque demande, retournent une réponse donnée.
  3. Exécutez les tests plusieurs fois dans cet ordre:

3.1 Tout d'abord, tous les services Web sont testés. De chaque machine, même les machines du développeur. Ce sont les vrais webservices, mais fonctionnant en environnement de développement. Cela signifie que les services Web ne peuvent jamais être interrompus ou répondre à des valeurs erronées, car sinon, chaque développeur se plaint de ne pas pouvoir compiler.

3.2 Ensuite, tous les tests unitaires internes à l'application sont exécutés. Cela signifie que tous les services Web sont simulés et testés en exécutant les mêmes tests que 3.1 (et qu'ils devraient également passer, sinon les simulations sont fausses), et être invoquées par l'application réelle comme si elles étaient réellement utilisées. Si les simulations sont erronées, vous pouvez exécuter le test en 3.1 et enregistrer ces valeurs (demande, réponse) dans un HashMap.

3.3 Ensuite, les mêmes tests que 3.2 sont exécutés, mais cette fois contre les vrais services web fonctionnant dans l'environnement de développement.

Une fois tous ces éléments terminés, pour l'environnement de production réel, il vous suffit de fournir l'adresse réelle de chaque service Web. Espérons que cela ne nécessite pas trop de changements dans la configuration.

Guillermo Schwarz
la source