web-dev-qa-db-fra.com

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

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 se 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?

22
Chris Cooper

À 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 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 rapide tests unitaires.

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.

42
Andres F.

Je ne suis pas d'accord avec "ne vous moquez pas d'objets que vous ne possédez pas" lorsque vous faites 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.

6
Venu b

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 devrez 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 . En outre, 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

1
Richard

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

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

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

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

1
Daenyth

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 modèles de simulation sont incorrects), et être invoqués par l'application réelle comme s'ils étaient réellement utilisés. 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 tout cela terminé, 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.

0