web-dev-qa-db-fra.com

Dans la conception de logiciels, une application reste-t-elle agnostique concernant son utilisation avec des données réelles des données ou des données simulées?

Permettez-moi d'essayer de résumer un peu plus avec un exemple simple:

Vous construisez une application importante, un portail utilisateur, par exemple, avec des flux, des nouvelles, une gestion de compte et une gamme complète de caractéristiques de différence.

Au cours du développement, il est décidé que vous devez mettre en œuvre des données moquées, pour une facilité de test, ou peut-être parce que les API que vous communiquez ne sont pas fiables.

Si vous devriez jamais apporter des modifications à votre application principale afin de prendre en compte l'utilisation de données simulées ou si votre application reste pure et agnostique à la manière dont elle est utilisée et que vous fassiez plutôt forcer vos données simulées pour trouver un moyen de trouver un moyen de trouver un moyen de trouver Injectez-vous dans l'application?

Je suis de la mentalité qu'une application ne devrait pas se préoccuper de la manière dont l'extérieur veut l'utiliser ou le tester, et que d'ajouter des énoncés conditionnels dans la vérification de l'application, que ce soit à la dissimulation ou non n'est pas une mauvaise pratique, c'est une pratique terrible. C'est un couplage complet qui ne devrait même jamais être diverti, peu importe la difficulté d'obtenir des données simulées dans l'application.

Sur une mission actuelle, je vois littéralement des centaines de références sur l'ensemble de la base de code qui fera différentes choses en fonction du contexte des données utilisées. Ce genre de choses me donne envie de pleurer, mais peut-être que je viens de tout avoir tort et il y a un avantage légitime dans cela?

Quels sont certains arguments pour pourquoi ce couplage serré pourrait-il être une bonne chose?

Quelles sont vos pensées générales et vos expériences avec une utilisation étroitement en ingraissant des données simulées dans la demande elle-même?

28
Lev

L'idéal est que l'application est une boîte noire. En testant, vous l'alignez une variété d'entrées et observe les résultats.

En réalité, il y a des compromis. Certains tests sont vraiment chers à écrire. Certains tests ont une faible valeur. Aucun test n'est jamais 100%, donc c'est aussi une décision d'entreprise quand elle est "assez bonne". Chaque fois que quelque chose est moqué ou des conditions spéciales sont introduites, la valeur des tests diminue et une tache aveugle est créée.

Si vous avez des chèques tout au long de la place, c'est un signe que la conception de l'application ne convient pas aux tests et doit être refacturé. L'objectif du refacteur est alors d'introduire des limites et des interfaces de module et des objets de transfert de données lorsque les limites sont croisées. Ainsi, la dépendance peut être adressée en une seule place.

41
Martin K

Sur une mission actuelle, je vois littéralement des centaines de références sur l'ensemble de la base de code qui fera différentes choses en fonction du contexte des données utilisées.

C'est probablement un héritage qui a commencé avec un innocent if, mais comme la demande a augmenté de l'état de la condition à plusieurs endroits. Ceci est une dette technique et cette façon de le faire peut facilement conduire à une erreur. Vous devez mettre à jour la condition mais oublier un endroit et tout l'enfer se déchaîner.

Quels sont certains arguments pour pourquoi ce couplage serré pourrait-il être une bonne chose?

C'était rapide et facile à faire.

C'est un couplage complet qui ne devrait même jamais être diverti, peu importe la difficulté d'obtenir des données simulées dans l'application.

La vraie vie est plus compliquée que cela. Parfois rapides et sales apportent plus de valeur que les solutions propres longues et complexes. Cependant, vous avez toujours besoin de peser les avantages et les inconvénients.

Maintenant, si vous souhaitez découpler les données simulées, vous avez probablement un refactoring de faire et de découpler plus que la mise en œuvre et la simulation de données. Vous pouvez avoir des implémentations qui ont trop de responsabilités. Idéalement, vous devriez avoir une entité qui effectue des opérations sur les données de manière indépendante sur la manière d'obtenir ces données.

Une façon d'y parvenir est d'utiliser l'injection de dépendance (DI). L'objectif est de réduire et de contrôler les points d'entrée dans lesquels des données sont fournies.

Disons que vous avez un composant C, fonctionnant sur les données. Devant-le dépendre d'un service de données S qui contient des données (et peut-être les récupérer et les stocker dans des structures appropriées). Un moyen typique d'atteindre la DI est grâce à l'injection de construction:

ComponentC(IDataServiceS service) { ... }

Comme toujours, ne dépendez pas de la mise en œuvre concrète, alors IDataService est une interface et une modification de mise en œuvre en fonction d'un profil.

Dans votre cas, vous pouvez avoir trois profils différents, chacun avec une implémentation de béton différente de IDataService. Quelques exemples:

  • ProdDataService implements IDataService
    // Production implementation (fetch in database, through network...)
    
  • FakeDataService implements IDataService
    // Fake to be used in development. Fetch data from a local file,
    // those data closely match production data and allow to use the
    // application like in production.
    
  • MockDataService implements IDataService
    // Mock to be used in test. Mock Edge case data.
    

Maintenant, comment injecter la mise en œuvre concrète dépend en grande partie de votre pile.

Ce que vous obtenez est un point d'entrée unique (au moins par composant) pour les données. Vous n'avez pas de conditions multiples dans votre composant et la responsabilité d'obtenir/analyser/stocker/stocker des données est laissée à un service; Le composant est propre de toute condition.

14
JayZ

Si vous avez besoin d'apporter des modifications à votre application principale afin de prendre en compte l'utilisation de données simulées ou si votre application reste pure et agnostique sur la manière dont elle est utilisée et si vous forez à la place vos données simulées pour trouver un moyen d'injecter elle-même dans l'application?

"Pure et agnostique" est la bonne réponse. Je ne sais pas si c'est le formulaire correct pour cela, mais votre signification envisage (c'est-à-dire ne pas savoir si les données sont réelles ou non) est correcte.

Le point de test est de tester des conditions réelles. S'il y a un comportement de ramification basé sur des données de test/réel, alors tout test ne sera pas en mesure de vérifier le comportement de la branche "Données réelles", qui défait le but de tester.

Je suis de la mentalité que [..] Pour ajouter des déclarations conditionnelles dans la vérification de l'application, que ce soit à moquer ou non, ce n'est pas seulement une mauvaise pratique, c'est une pratique terrible.

Entièrement d'accord avec votre déclaration. Cela étant dit, il est raisonnable que le code hérité ne soit pas à jour avec les dernières pratiques (donc "héritage"), qui peut rendre la situation compréhensible, mais cela ne signifie pas que cela devrait être considéré. bonnes pratiques.

Sur une mission actuelle, je vois littéralement des centaines de références sur l'ensemble de la base de code qui fera différentes choses en fonction du contexte des données utilisées. Ce genre de choses me donne envie de pleurer, mais peut-être que je viens de tout avoir tort et il y a un avantage légitime dans cela?

Le seul cas d'utilisation réelle est ici si vous avez besoin d'un commutateur de tuer sur un certain comportement. Le SRP dicte qu'un tel comportement ne devrait pas être poivré sur le codebase, mais ignorons que, en ce qui concerne le Code de la Héritage.

Cependant, un interrupteur de jeu n'est pas identique à une simulation de données. L'entrée de données (réelle ou moquée) devrait suivre exactement le même chemin, selon le but de tester le comportement du monde réel de l'application.

Quels sont certains arguments pour pourquoi ce couplage serré pourrait-il être une bonne chose?

Une autre considération est que pour la petite application de vie courte, par exemple. Une demande de console Je frappe rapidement ensemble pour un objectif de courte durée, la codage de bonne pratique n'est pas vraiment nécessaire.

Mais la nécessité de la bonne pratique codant des échelles rapidement à la fois à la taille et à la complexité du codeBase, ainsi que tout recours à long terme attendu sur (et la maintenabilité de) le codebase.

Quelles sont vos pensées générales et vos expériences avec une utilisation étroitement en ingraissant des données simulées dans la demande elle-même?

Ne le fais pas.

8
Flater

Si vous devriez jamais apporter des modifications à votre application principale afin de prendre en compte l'utilisation de données simulées ou si votre application reste pure et agnostique à la manière dont elle est utilisée et que vous fassiez plutôt forcer vos données simulées pour trouver un moyen de trouver un moyen de trouver un moyen de trouver Injectez-vous dans l'application?

Cela semble juste comme un mauvais design pour moi, honnêtement. Ou si ce n'est pas tout à fait mauvais, du moins grandi organiquement dans un gâchis.

Je suis tout à fait d'accord avec votre point de vue qu'une application, à son noyau devrait être totalement data-agnostique. Si vous vous trouvez déjà en train de changer de code de production pour accueillir des tests ou des données simulées, il est temps d'une réorganisation de votre code.

Idéalement, par injection de dépendances via des constructeurs ou des interfaces pour masquer les données, vous devriez être capable d'injecter ce dont vous avez besoin pour vos tests et rien d'autre.

Si vous utilisez Java, vous pouvez envisager l'utilisation des annotations comme @VisibleForTesting Sur des méthodes pour s'assurer qu'ils sont marqués dans leur but, même si seulement, avec parcimonie.

4
Bruno Oliveira

Au cours du développement, il est décidé que vous devez mettre en œuvre des données moquées, pour une facilité de test, ou peut-être parce que les API que vous communiquez ne sont pas fiables.

La moqueur de données elle-même peut apporter plusieurs avantages, comme: (1) Vous pouvez tester vos classes isolément en se moquant de ses dépendances (injectables) (et vous devriez Écrivez ces simulacres sur vos tests unitaires, pas dans votre application principale); (2) Vous pouvez ensuite contrôler la couverture de vos tests, en modifiant les simulacres pour renvoyer des valeurs spécifiques qui permettront à votre test de l'unité de tomber dans des scénarios non découverts précédemment; (3) Vous permet d'automatiser des tests, ce qui les oblige à courir beaucoup plus vite, etc.

Une lecture intéressante à propos de ceci: https://blog.cleancoder.com/uncle-bob/2014/05/10/whenomock.html

... Si vous devriez jamais apporter des modifications à votre application principale afin de s'adapter à l'utilisation de données simulées, ou si votre application doit rester pure et agnostique sur la manière dont elle est utilisée et devriez-vous plutôt forcer vos données simulées à trouver un moyen de s'injecter dans l'application?

Veuillez jeter un coup d'œil à "architecture propre", par oncle Bob: ( https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html ) .

Certaines caractéristiques d'une architecture propre, comme décrit sur le blog Post, sont les suivantes:

  1. Indépendant des cadres. L'architecture ne dépend pas de l'existence d'une bibliothèque de logiciels chargeurs de fonctionnalités. Cela vous permet d'utiliser de tels cadres tels que des outils, plutôt que d'avoir à baisser votre système dans leurs contraintes limitées.
  2. Testable. Les règles commerciales peuvent être testées sans l'interface utilisateur, la base de données, le serveur Web ou tout autre élément externe.
  3. Indépendant de l'interface utilisateur. L'interface utilisateur peut changer facilement, sans changer le reste du système. Une interface utilisateur Web pourrait être remplacée par une interface utilisateur de la console, par exemple sans modifier les règles de l'entreprise.
  4. Indépendant de la base de données. Vous pouvez échanger Oracle ou SQL Server, pour Mongo, Bigtable, CouchDB ou autre chose. Vos règles commerciales ne sont pas liées à la base de données.
  5. Indépendant de toute agence externe. En fait, vos règles commerciales ne savent tout simplement rien du tout sur le monde extérieur.

Conclusion: Sur la base des articles et des concepts expliqués (et sur mon expérience au fil du temps des projets du monde réel) votre L'application doit rester pure et agnostique sur la manière dont elle est utilisée et que des éléments tels que DB devraient être "Pluggable" à votre application principale .

1
Emerson Cardoso

Ma réponse est similaire à quelques autres, mais je veux passer un peu à travers l'argument.

Une application, dans le sens d'un programme qu'un utilisateur interagit directement, constitue généralement une fine wrapper autour de plusieurs API (services, bibliothèques, etc.) qui font tout le travail réel. Ces surfaces de l'API sont là où vous devriez concentrer vos tests. Les tests doivent être basés sur le comportement public documenté de la surface de l'API et non ses détails de mise en œuvre. Si le comportement de l'API publique n'est pas documenté ou si trop de détails sur la mise en œuvre sont exposés, ces problèmes doivent être adressés avant même de commencer à écrire de bons tests.

Cela va dans les deux sens. Une application ne doit pas être couplée aux détails de la mise en œuvre d'une API et une API ne devrait pas être couplée aux détails de la mise en œuvre des données qu'elle fonctionne. Un moyen efficace de résoudre ce problème est le principe selon lequel IN OO Langues qui distinguent les interfaces des types de béton, les paramètres de la méthode et les types de retour doivent généralement être des interfaces. Si les données sont représentées comme un type d'interface, vous peut penser aux données elles-mêmes comme ayant une API clairement définie.

Maintenant, à la partie principale de votre question: Les détails de la mise en œuvre dépendent-ils de l'utilisation? Bien sûr, peut-être. Dans les contraintes du comportement public documenté - c'est-à-dire sans casser aucun test - vous pouvez certainement optimiser les intrants typiques, ou test et branche pour des cas particuliers où la performance peut être améliorée en faisant des choses différentes. Mais tout cela devrait être caché du consommateur de l'API. Une implémentation avec ces optimisations devrait produire exactement les mêmes résultats qu'une implémentation sans eux, même dans des cas de bord (pour lesquels vous devriez écrire des tests.)

Et vous devez peser avec précaution si le coût de la rédaction et la maintenance des optimisations vaut la peine de performance. Trop de développeurs sont obsédés par la performance, au point de passer des jours à écrire un piratage intelligent pour raser quelques millisecondes de quelque chose qu'un programme effectue une fois. De telles optimisations sont à peine mesurables, encore moins visibles. Généralement, les différences de performance doivent non seulement être perceptibles, mais la performance devrait réellement être un problème, avant même que vous n'ayez même pas envisagé de négocier la maintenabilité de la performance.

1
StackOverthrow

Souvent, les utilisateurs d'administrateurs d'un système doivent créer eux-mêmes des données simulées dans le cadre d'apprendre à utiliser le système (ou d'essayer de nouvelles fonctionnalités).

Par conséquent, une catégorie de données doit être créée pour les tests d'utilisateurs uniquement à cet effet.

Je crois qu'il est généralement utile de concevoir un système pour être multi-locataires prêts à partir du début dans tous les cas. Vous pouvez donc avoir une catégorie "test" entièrement isolée des données de production dans le même système (le long des autres catégories séparées de données dans le système).

À mon avis, c'est l'approche la plus pratique.

0
eyal_katz

Version courte: l'application doit absolument être agnostique et si elle nécessite un comportement différent, réalisez que le comportement peut également être des données et la définir de manière appropriée.

Version longue: On dirait que vous utilisez JavaScript et n'avez pas de classe de configuration. Bien que ce ne soit pas la langue/l'environnement le plus facile de configuration, il est certainement possible.

Fondamentalement, vous avez des données et un environnement de test/développement, ou que vous développez dans la production. Donc, le fait que vous obtenez une autre URL est une fonctionnalité. Le problème est comment et où vous décidez que l'URL sera différente. Sans classe de configuration, vous prenez cette décision dans des endroits aléatoires.

Le problème que vous voyez est causé par l'utilisation de code pour déterminer ce que les données doivent être, au lieu de laisser les données vous dire ce qu'elle est. Vous ne devriez pas calculer un point de terminaison lorsque le programme est en cours d'exécution, vous devriez récupérer un point de terminaison.

En ce qui concerne la façon dont il s'agit, il se glisse parce que cela semble être un off, et il est tout simplement plus facile d'écrire une seule ligne de code au lieu de la douzaine ou deux qui seraient nécessaires pour le faire correctement. Cela semble être une valeur et une décision et il ne semble pas que l'effort supplémentaire pour le rendre flexible soit nécessaire ou utile.

Aller un peu philosophique et citer un auteur, j'aime "les gens font le moins d'efforts qu'ils peuvent, pour atteindre leurs objectifs". Vous ne pouvez pas changer cela, ce que vous pouvez faire est de changer leurs objectifs.

0
jmoreno

En général, votre application ne devrait pas se soucier d'où provient les données, à condition que le format correct. L'utilisation de fausses données pour les tests est une excellente idée, car vous pouvez dicter les valeurs pour tester des cas d'angle qui pourraient rarement être touchés par des flux de données réels. Idéalement, vous seriez capable de brancher des données simulées sans que l'application "connaissant". C'est-à-dire que cela ferait partie de la configuration et non un drapeau du code. Par exemple, activez l'URL du flux de données de la source réelle à une instance de test.

Cela nécessite une certaine planification à venir ou change ensuite. Si vous n'avez pas conçu pour cela, il peut être tentant de disposer d'un drapeau qui contrôle le flux dans l'application pour demander de fausses données au lieu de données réelles. Ce n'est pas une idée terrible, tant qu'elle est minimisée. Le boîtier plus spécial que vous effectuez en mode test, moins vos tests testent le code de production.

Parfois, des chèques répétés pour une valeur d'indicateur sont une odeur de code pour le polymorphisme manquant. Si vous pouvez identifier cela et le refacteur, vous pourrez peut-être réduire le nombre de chèques de drapeau que vous faites et avoir une meilleure idée de la diffusion du composant dans le mode de test en mode VS.

0
bmm6o