Test d'un client REST sur un serveur REST. Comment faire des luminaires?

10

Lors de l'écriture de tests unitaires, il est courant d'utiliser des appareils: peu de données testables, nous pouvons donc dire: 1. Obtenez tous les clients doivent inclure Willy Wonka. 2. Supprimez le client 3, et maintenant les clients ne devraient plus inclure Willy Wonka.

C'est très bien pour les tests unitaires. Utilisez la configuration / le démontage pour recharger les appareils ou annuler la transaction. Les tests de création, de mise à jour et de suppression sont donc effectués dans une transaction . Les nouvelles données temporaires ne durent que pendant la durée de ce test, puis sont réinitialisées.

Mais qu'en est-il lorsque nous avons séparé le serveur REST du client REST?

Nous voulons nous assurer que notre client REST ne lit pas seulement correctement, mais crée, met à jour et supprime correctement.

Je n'ai pas pu trouver d'exemples ou de suggestions sur la façon de procéder avec un serveur REST de test distant.

En supposant que j'ai un serveur REST de test ne servant que des appareils. La nature sans état de HTTP signifie qu'il serait difficile d'envoyer un type de message "BEGIN TRANSACTION" et "ROLLBACK TRANSACTION" ou "RELOAD FIXTURES", non?

Je ne peux pas être le premier à vouloir faire ça, donc j'ai le sentiment que j'ai besoin d'une façon différente de penser à ce sujet.

Aucune suggestion?

sivers
la source
Peut-être, puisqu'il s'agit d'un serveur de test, vous pouvez avoir un point de terminaison qui rechargera les appareils?
David Radcliffe
Si votre problème principal est de ramener votre serveur de test dans un état prédéfini, pourquoi n'ajoutez-vous pas une sorte de fonctions de test spéciales comme "RELOAD TESTDATA" à votre API de repos pour faire ce que vous voulez? Bien sûr, vous devez vous assurer que ce type d'appels d'API n'est pas disponible en production.
Doc Brown

Réponses:

7

Les systèmes logiciels ont idéalement des limites de système bien définies et des interfaces entre eux. Les services REST en sont de bons exemples.

À cette fin, je recommanderais de ne pas faire ce que vous essayez de faire.

Plus précisément:

Nous voulons nous assurer que notre client REST ne lit pas seulement correctement, mais crée, met à jour et supprime correctement.

Ce que je suggérerais plutôt, c'est:

  • Création de tests pour votre client REST, pour vous assurer qu'il se comporte correctement, compte tenu d'une entrée et d'une sortie spécifiques. Tenez compte des valeurs bonnes (attendues) et mauvaises (inattendues).

  • Création de tests pour votre service REST (si vous le contrôlez, c'est-à-dire), pour se comporter conformément à sa fonction prévue

  • Gardez les tests à proximité de leur domaine de problème, afin qu'ils puissent aider à guider la conception et le développement de ce qui est important dans ce contexte

briandoll
la source
3
Vous rejetez l'idée de tests d'intégration ici de manière assez décontractée. Je ne pense pas que cette approche soit informée de la pratique.
febeling
Merci à toutes les suggestions utiles. Aussi par Twitter, j'ai reçu d'excellentes suggestions pour essayer Ruby gem "webmock" et similaire pour simuler la réponse du serveur REST API. Je suis également d'accord avec "febeling" que ce que je décris semble être davantage un test d'intégration, donc j'examinerai cela séparément. Merci encore à tous. - Derek
sivers
se moquer d'une API est un excellent moyen de résoudre le problème. Mais comment vous assurez-vous que l'API simulée == vraie API?
FrEaKmAn
4

Deux angles à retenir ici:

  • Testez-vous votre code ou la plomberie? En supposant que vous utilisez un service bien connu et une pile de clients, vous pouvez probablement présumer en toute sécurité leurs testeurs et des milliers d'utilisateurs garantiront généralement qu'il n'y a pas de bogue fondamental dans les fondements.
  • Pourquoi vos tests ne sont-ils pas idempotents? Créez un moyen d'écrire des données hors production ou d'écrire sur un autre point de terminaison. Choisissez un modèle de dénomination prévisible. Pré-chargez la base de données du serveur de repos avant les tests. Et il y a probablement quelques autres moyens pour y arriver - la méthode est vraiment tactique et devrait dépendre de la nature de l'application.
Wyatt Barnett
la source
2

Je pense que truquer les réponses du serveur REST est le meilleur moyen de tester le client.

Pour Ruby, il y a la gemme FakeWeb que vous pouvez utiliser pour émettre de fausses réponses - https://github.com/chrisk/fakeweb .

De plus, en JavaScript, vous pouvez utiliser quelque chose comme Sinon.JS, qui vous donne un faux serveur - http://sinonjs.org/docs/#fakeServer .

laktek
la source
1

Comme d'autres l'ont dit, si vous testez un client, vous n'avez pas besoin d'aller jusqu'à créer, supprimer, etc. sur le serveur. La plupart du temps, vous n'avez même pas besoin de vous moquer d'un serveur. Vous n'avez vraiment besoin que de vous assurer que vous faites les bonnes demandes et que vous gérez correctement les réponses, que ce soit écrit en Ruby, Python, PHP ou autre, à un moment donné, votre client va probablement utiliser une méthode d'une bibliothèque HTTP pour faire une requête et il suffit de se moquer de cette méthode, de vérifier comment elle est appelée et de renvoyer un résultat de test.

Prenez un hypothétique client Python qui l'utilise urllib2pour faire des requêtes. Vous avez probablement une méthode dans le client, appelons-la get(), qui contient un appel urllib2.Request(). Il vous suffit vraiment de vous moquer de l'appel de votre classe get().

@patch('your.Client.get')
def test_with_mock(self, your_mock):
    your_mock.return_value({'some': 'json'})
    test_obj = your.Client.get_object(5)
    your_mock.assert_called_with('/the/correct/endpoint/5')

Cet exemple très simplifié utilise la bibliothèque Mock de Python pour tester une your.Clientclasse hypothétique avec une get_object()méthode qui génère l'URL correcte pour obtenir quelque chose d'une API. Pour effectuer la demande, le client appelle sa get()méthode avec cette URL. Ici, cette méthode est simulée ( your.Client.getest "corrigée" de sorte qu'elle est sous le contrôle de your_mock), et le test vérifie si le bon point de terminaison a été demandé.

La méthode simulée renvoie la réponse JSON configurée ( your_mock.return_value) que le client doit gérer et vous feriez d'autres affirmations pour tester qu'il a traité les données attendues de la manière attendue.

Sean Redmond
la source
Mais comment êtes-vous sûr que lorsque vous faites la "bonne" demande, c'est une vraie demande (en production)? Parce que si je comprends votre suggestion, si l'API change ou casse, vos tests fonctionneront toujours. Pendant la production, c'est une histoire totalement différente.
FrEaKmAn
1

Ce que vous décrivez est un scénario de test d'intégration. Ceux-ci sont généralement un peu difficiles à installer et à démonter. Cela les rend lents à courir et assez souvent cassants.

L'approche avec les appareils est tout aussi maladroite et maladroite, mais c'est la façon par défaut dont certains frameworks s'y prennent, par exemple Rails, et elle est déjà prise en charge. Ils ont besoin du cas de test abstrait ou quelque chose de similaire pour préparer la base de données avec des appareils. (Méfiez-vous des dénominations inhabituelles des catégories de tests dans Rails, les tests unitaires avec les appareils DB sont à proprement parler également des tests d'intégration.)

La façon dont je procéderais dans votre scénario est d'accepter d'avoir un contrôle spécifique au test sur l'état de l'application API ou sa base de données. Vous pouvez soit avoir des points de terminaison supplémentaires pour la configuration et le démontage, qui ne sont présents que dans l'environnement de test. Ou bien, vous parlez à la base de données (ou tout ce que vous utilisez) à l'arrière de votre application / API.

Si vous pensez que cela représente un effort (au sens excessif) excessif, alors considérez que l'approche avec des appareils pour les bases de données fait exactement cela: utiliser des moyens supplémentaires spécifiques au test pour manipuler l'état de la base de données ou de l'application.

Je ne pense pas que cette discussion ait à voir avec la nature sans état de HTTP, cependant. HTTP est sans état, mais l'application ne l'est certainement pas, dans la majorité des cas. Cela vous ressemble un peu à la recherche de rigueur REST. Vous pourriez tout aussi bien faire en sorte que toutes les ressources soient entièrement créables, lisibles et supprimables. Dans ce cas, vous pouvez simplement effectuer toutes les opérations de configuration et de démontage via les moyens API standard. Cependant, cela n'est souvent pas fait dans la pratique, car vous ne voulez pas inclure certaines opérations à partir d'une compréhension métier de votre application, du moins pas en dehors de l'environnement de test.

febeling
la source
1

Patch de singe

Dans mon travail, nous effectuons ATDD en utilisant un cadre xUnit sortant et des appels réseau de correction de singe entre le client et le serveur. Dans le même espace de processus que nous chargeons le client, monkey corrige l'appel réseau en haut du code de pile du serveur REST. Tous les appels sont ensuite émis par le client comme ils le seraient normalement, et le code du serveur reçoit les demandes exactement telles qu'elles apparaissent normalement.

Avantages

  • pas besoin de se synchroniser avec le démarrage du serveur (car il n'y a pas de serveur)
  • tirer parti de la configuration classique de l'unité et de la méthode de démontage pour gérer des choses comme les luminaires
  • pouvoir utiliser des mock / stubs et autres patchs de singe pour un contrôle plus fin du test
  • peut être écrit en utilisant un cadre xUnit

Compromis

  • n'expose pas les interactions / problèmes multi-processus (verrouillage, manque de ressources, etc.)
  • n'expose pas les problèmes multi-serveurs (sérialisation des données, style de clustering)
  • n'expose pas les problèmes de réseau car cela est simulé (accès, erreurs de timeout, etc.)
Dietbuddha
la source