web-dev-qa-db-fra.com

Pouvons-nous supposer lors d'un test de logiciel qu'un utilisateur ne réaliserait pas de telles actions stupides sur un logiciel?

Par exemple: lors du test fonctionnel d'un formulaire dans une application Web, nous testerons les champs en entrant différents types de valeurs d'entrée aléatoires.

En général, en tant qu'utilisateurs de l'application Web, nous n'entrons pas réellement de valeurs aléatoires dans les champs.

Alors, à quoi sert d'incorporer tous ces cas de test qui peuvent/peuvent ne pas conduire à des bugs, alors que la probabilité d'apparaître ce genre de problèmes en production est bien moindre?

Remarque: l'exemple ci-dessus n'est qu'un exemple de cas; de tels problèmes peuvent survenir dans tout type de fonctionnalité/module.

Je pose cette question uniquement pour savoir si des pratiques standard sont là à suivre ou si cela dépend totalement du produit, du domaine et de tous les autres facteurs.

71
Nagarani Dubbaka

Vous ne pouvez pas entrer des valeurs aléatoires dans les champs d'une application Web, mais il y a certainement des gens qui font exactement cela.

Certaines personnes entrent au hasard par accident et d'autres le font intentionnellement en essayant de casser l'application. Dans les deux cas, vous ne voulez pas que l'application plante ou présente un autre comportement indésirable.
Pour le premier type d'utilisateur, vous ne voulez pas cela car cela leur donne une mauvaise expérience et peut les refuser.
Pour le deuxième type d'utilisateur, ils n'ont généralement pas d'intentions honorables et vous ne voulez pas leur donner accès à des informations auxquelles ils ne devraient pas pouvoir accéder ou leur permettre de refuser l'accès à de véritables utilisateurs. à vos services.

La pratique standard pour les tests consiste à vérifier non seulement que le cas de beau temps fonctionne, mais aussi que les cas Edge inhabituels sont explorés pour trouver des problèmes potentiels et pour avoir la certitude que les attaquants ne peuvent pas facilement accéder à votre système. Si votre application plante déjà avec une entrée aléatoire, vous ne voulez pas savoir ce qu'un attaquant peut faire avec une entrée spécialement conçue.

188

Ne jamais rien supposer

Vous ne pouvez pas supposer qu'aucun utilisateur ne fera quelque chose de "stupide" avec votre logiciel par accident ou exprès. Les utilisateurs peuvent accidentellement appuyer sur le mauvais bouton, le chat peut marcher sur le clavier, le système peut mal fonctionner, leur ordinateur peut être piraté par un logiciel malveillant, etc.

En outre, l'utilisateur lui-même peut être malveillant, recherchant intentionnellement des moyens de casser votre logiciel dans l'espoir qu'il puisse trouver un moyen de l'exploiter à son avantage. Même s'ils trouvent un bogue qu'ils ne peuvent pas exploiter, tout ce qu'ils trouvent peut les inciter à sonder votre système pour quelque chose qu'ils peuvent attaquer, sachant que vos procédures d'assurance qualité font défaut.

En ce qui concerne les tests, il est utile de se prémunir contre les entrées aléatoires, mais le choix d'entrées de test entièrement aléatoires (c'est-à-dire sans aucune considération particulière pour les cas d'utilisation ou les cas Edge) est presque inutile. Le but du test est de valider votre solution par rapport aux exigences et aux attentes de votre employeur/clients/utilisateurs; cela signifie que vous devez vous concentrer sur le ciblage de tous les cas Edge et conditions aux limites, ainsi que de tous les cas "dégénérés" qui ne correspondent pas au flux de travail attendu d'un utilisateur.

Bien sûr, vous pouvez exécuter des tests qui révèlent des bogues que vous jugerez ultérieurement inutiles; cela peut être pour toutes sortes de raisons - le bogue peut être trop cher à corriger par rapport à son impact sur l'utilisateur, ou vous pouvez découvrir des bogues dans des fonctionnalités que personne n'utilise, ou le bogue peut déjà être si bien établi dans le système que certains les utilisateurs le traitent comme une fonctionnalité.

Alternativement, vous pouvez écrire un logiciel sur mesure qui a un public étroitement limité d'utilisateurs `` experts '' où il n'y a aucun avantage commercial à passer du temps à corriger les bogues, car ces utilisateurs sont capables de faire leur travail avec un logiciel de bogue (par exemple, un outil de diagnostic utilisé par l'équipe informatique interne ne génère aucun revenu, donc s'il se bloque occasionnellement, alors personne ne voudra probablement payer le temps nécessaire pour le corriger - ils diront simplement à l'équipe informatique de vivre avec les bugs à la place) .

Cependant, vous ne pouvez prendre ces décisions que si vous connaissez ces bogues. Par exemple, un utilisateur peut entrer une entrée malveillante qui efface toute la base de données - si vous n'avez pas explicitement protégé contre et testé pour ce scénario, alors vous ne pouvez pas être sûr que cela peut se produire ou non. Le risque de laisser des bogues non découverts dans le système signifie que vous vous exposez potentiellement à de vrais problèmes si l'un de ces bogues se révèle dans le monde réel et a un impact majeur sur vos utilisateurs.

Ainsi, alors que la décision de corriger ou non les bogues peut nécessiter une certaine contribution du propriétaire du logiciel (généralement celui qui paie votre salaire), la décision de tester les bogues et les cas à tester est une préoccupation d'ingénierie qui doit être pris en compte dans les estimations et la planification du projet, où l'objectif devrait être d'atteindre quelque chose d'aussi proche que possible de la couverture complète compte tenu des contraintes de temps/d'argent/de ressources.

101
Ben Cottrell

Il y a plusieurs facteurs à prendre en compte. Pour illustrer ces points, je vais utiliser un exemple de champ dans lequel un utilisateur doit entrer un pourcentage dans le contexte d'un quota défini pour une tâche spécifique en termes d'espace disque que la tâche pourrait utiliser. 0% signifie que la tâche ne pourra rien écrire sur le disque; 100% signifie que la tâche pourrait remplir tout l'espace disque. Les valeurs intermédiaires signifient ce qu'elles signifient.

En tant que développeur, vous considérez probablement que les valeurs acceptables sont [0, 1, 2, 3, ⋯ 99, 100], et tout le reste est idiot. Voyons pourquoi les utilisateurs peuvent toujours entrer ces valeurs "idiotes".

Typos

%^

L'utilisateur saisissait la valeur 56, mais a appuyé par erreur Shift en les saisissant (par exemple parce que sur le clavier français, vous devez appuyer sur Shift pour saisir des chiffres, et l'utilisateur bascule constamment entre un clavier français et un clavier QWERTY).

De la même manière, vous pouvez obtenir un nombre, avec quelque chose après ou avant, ou entre:

56q

Ici, l'utilisateur saisissait probablement les chiffres, suivi d'un onglet pour passer au champ suivant. Au lieu d'appuyer sur   ⇆  , l'utilisateur a appuyé sur la touche voisine.

Incompréhensions et interprétations erronées

Une entrée vide est probablement la plus courante. L'utilisateur a imaginé que le champ était facultatif ou ne savait pas quoi mettre dans ce champ.

56.5

L'utilisateur pensait que les valeurs à virgule flottante étaient acceptables. Soit l'utilisateur a tort, et l'application doit expliquer poliment pourquoi seules les valeurs entières sont acceptées, soit les exigences initiales étaient erronées, et il est logique de laisser les utilisateurs entrer des valeurs à virgule flottante.

none

L'utilisateur a mal compris que lorsqu'on lui demandait l'espace que la tâche pouvait prendre, l'application attendait un certain nombre. Cela pourrait indiquer une mauvaise interface utilisateur. Par exemple, demander à l'utilisateur "Combien d'espace disque la tâche devrait-elle prendre?" invite à ce type d'entrée, tandis qu'un champ suivi d'un signe de pourcentage recevrait moins de ce type d'entrée, car "aucun%" n'a pas beaucoup de sens.

150

L'utilisateur a mal compris ce que signifie le pourcentage dans ce cas. Peut-être que l'utilisateur voulait dire que la tâche peut prendre 150% de l'espace actuellement utilisé, donc si sur un disque de 2 To, 100 Go sont utilisés, la tâche pourrait utiliser 150 Go. Encore une fois, une meilleure interface utilisateur pourrait aider. Par exemple, au lieu d'avoir un champ de saisie nu avec un signe de pourcentage ajouté, on pourrait avoir ceci:

[____] % of disk space (2 TB)

Lorsque l'utilisateur commence à taper, cela changerait le texte à la volée pour devenir ceci:

[5___] % of disk space (102.4 GB of 2 TB)

Représentations

Les grands nombres ou les nombres à virgule flottante peuvent être représentés différemment. Par exemple, un nombre 1234,56 pourrait être écrit comme ça: 1,234.56. Selon la culture, la représentation textuelle du même nombre serait différente. En français, le même numéro sera écrit comme ceci: 1 234,56. Vous voyez, une virgule où vous ne vous attendez pas à un, et un espace.

Attendre toujours un format spécifique en utilisant un environnement local spécifique vous causerait des problèmes tôt ou tard, car les utilisateurs de différents pays auraient des habitudes différentes d'écrire des nombres, des dates et des heures, etc.

Humains contre ordinateurs

Twenty-four

Les humains ordinaires ne pensent pas de la même manière que les ordinateurs. "Vingt-quatre" est un nombre réel, indépendamment de ce qu'un PC vous dirait.

Bien que (1) la plupart des systèmes ne gèrent pas du tout ce type d'entrée et (2) presque tous les utilisateurs n'imaginent pas entrer un nombre écrit en lettres entières, cela ne signifie pas qu'une telle entrée est idiote. Dans À propos de Face 3 , Alan Cooper fait remarquer que la non-manipulation de ces entrées est révélatrice de l'incapacité des ordinateurs à s'adapter aux humains, et idéalement, l'interface devrait être capable de gérer ces entrées correctement.

La seule chose que j'ai à ajouter au livre d'Alan Cooper, c'est que dans de nombreux cas, les chiffres sont écrits en chiffres par erreur . Le fait que les ordinateurs s'attendent à ce que leurs utilisateurs fassent des erreurs (et ne tolèrent pas un utilisateur qui écrit correctement) est ennuyeux.

Unicode

5????

Unicode réserve ses propres surprises: les personnages qui pourraient se ressembler ne sont pas les mêmes. Pas convaincu? Copier coller "5????" === "56" aux outils de développement de votre navigateur et appuyez sur Enter.

La raison pour laquelle ces chaînes ne sont pas égales est que le caractère Unicode ???? n'est pas le même que le caractère 6. Cela créerait une situation où un client en colère appellerait pour dire que votre application ne fonctionne pas, fournir une capture d'écran d'une entrée qui semble légitime et votre application affirmant que l'entrée n'est pas valide.

Pourquoi voudrait-on entrer un caractère Unicode qui ressemble à un chiffre, demandez-vous? Bien que je ne m'attendrais pas à ce qu'un utilisateur en saisisse un involontairement, un copier-coller provenant d'une source différente pourrait provoquer cela, et j'ai eu un cas où l'utilisateur a fait un tel copier-coller d'une chaîne qui contenait un caractère Unicode qui ne le ferait pas apparaissent à l'écran.

Conclusion

Ce sont les cas que vous obtenez pour un champ de saisie de nombre élémentaire. Je vous laisse imaginer ce que vous pouvez avoir à gérer pour des formulaires plus complexes, comme une date ou une adresse.

Ma réponse se concentre sur ce que vous avez appelé une entrée "idiote". Les tests ne consistent pas à vérifier les chemins heureux; il s'agit également de vérifier que votre application ne se casse pas lorsqu'un utilisateur malveillant entre intentionnellement des choses étranges, essayant de le casser. Cela signifie que lorsque vous demandez un pourcentage, vous devez également tester ce qui se passe lorsque l'utilisateur répond avec une chaîne contenant 1 000 000 de caractères, ou un nombre négatif, ou un table bobby .

60
Arseni Mourzenko

Il y a beaucoup de bonnes réponses ici qui décrivent pourquoi cela est important, mais pas beaucoup de conseils sur la façon de protéger sensiblement votre application. La "pratique standard" consiste à utiliser une validation d'entrée robuste, sur le client et le serveur. Les contributions non sensées sont faciles à défendre; vous rejetez simplement tout ce qui n'a pas de sens dans ce contexte spécifique. Par exemple, un numéro de sécurité sociale se compose uniquement de tirets et de chiffres; vous pouvez refuser en toute sécurité tout ce que l'utilisateur tape dans un champ de numéro de sécurité sociale.

Il existe deux types de tests qui doivent avoir lieu sur chaque application que vous écrivez, et ils ont chacun des objectifs différents. Le test que vous faites sur votre propre application est test positif; son but est de prouver que le programme fonctionne. Le test de ce testeurs font également sur votre application test négatif; son but est de prouver que votre programme ne fonctionne pas. Pourquoi avez-vous besoin de ça? Parce que vous n'êtes pas la meilleure personne pour tester votre propre logiciel. Après tout, vous avez écrit la chose, donc évidemment ça marche déjà, non?

Lorsque vous écrivez la validation d'entrée, vous utiliserez des tests positifs pour prouver que votre validation fonctionne. Les testeurs utiliseront des entrées aléatoires pour tenter de prouver que cela ne fonctionne pas. Notez que l'espace de problème pour les entrées aléatoires est essentiellement illimité; votre objectif n'est pas de tester toutes les permutations possibles, mais de limiter l'espace du problème en rejetant les entrées non valides.

Notez également que l'utilisateur final n'est pas le seul à fournir une entrée à votre programme. Chaque classe que vous écrivez a sa propre API et ses propres contraintes sur ce qui est considéré comme une entrée valide, donc une validation robuste (c'est-à-dire des "contrats de code") est également importante pour vos classes. L'idée est de durcir votre logiciel afin qu'un comportement inattendu soit rare ou inexistant dans toute la mesure du possible.

Enfin, le workflow est important. J'ai vu des applications tomber, non pas parce que l'utilisateur a entré quelque chose de non sensible, mais parce qu'il a fait des choses dans l'application dans un ordre inattendu. Votre application doit être consciente de cette possibilité et soit gérer les workflows inattendus avec élégance, soit demander à l'utilisateur d'effectuer des opérations dans l'ordre spécifié.

12
Robert Harvey

Habituellement, les valeurs "aléatoires" ne sont pas aléatoires. Vous tentez de capturer des cas Edge, "l'inconnu inconnu".

Dites par exemple que le caractère # plantera votre application. Vous ne le savez pas à l'avance et il serait impossible d'écrire des cas de test pour chaque entrée possible. Mais nous pouvons écrire un test pour "¬!"£$%^&*()_+-=[]{};'#:@~,./<>?|\" et voir s'il se casse

11
Ewan

J'ai écrit une fois un programme, que j'ai testé en direct dans un laboratoire avec 60 étudiants. Je me tenais derrière les 60 écrans d'ordinateur et je les ai vus l'utiliser. La quantité de choses ridicules qu'ils ont faites était époustouflante. J'étais trempé de sueur en regardant leur "créativité". Ils ont fait bien plus que n'importe quel individu peut fantasmer au cours d'une vie. Bien sûr, l'un d'eux l'a cassé.

Après cela, je suis une approche: if "a very specific use case" do, else show error

Si j'ai plusieurs cas d'utilisation, je les définis strictement et les enchaîne.

7
Arthur Tarasov

Ce que vous décrivez est Fuzzing ou Fuzz Testing : lancez une entrée aléatoire et invalide sur un système et voyez ce qui se passe. Vous ne faites pas cela parce que vous attendez d'un utilisateur qu'il le fasse. Vous le faites pour exposer vos propres hypothèses et préjugés pour souligner les bords de votre système pour voir ce qui se passe.

L'entrée de test normale écrite par un humain s'accompagnera d'hypothèses et de biais. Ces biais peuvent être certaines classes de bugs ne sont jamais trouvés via les tests.

Par exemple, si la plupart de vos entrées se trouvent dans la plage Unicode sécurisée ASCII, les hypothèses sur le codage des caractères dans le code ne sont pas appliquées. Ou peut-être qu'il est toujours en dessous d'une certaine taille, donc un champ ou un tampon de taille fixe n'est pas atteint. Ou peut-être qu'il y a des caractères spéciaux qui sont interprétés de manière surprenante, exposant que les entrées utilisateur sont transmises à un shell ou utilisées pour créer des requêtes de manière non sécurisée. Ou peut-être qu'il y a juste trop de tests de "chemin heureux" et pas assez de tentatives pour exercer la gestion des erreurs.

Le fuzzing n'a pas de telles idées préconçues sur l'entrée. Il exercera brutalement votre système avec toute combinaison possible d'entrée "valide". Unicode, ASCII, gros, petit et beaucoup, beaucoup d'erreurs. Votre système doit répondre gracieusement à chacun d'eux. Il ne devrait jamais tomber en panne. L'utilisateur doit toujours recevoir une sorte de message sensible sur ce qui ne va pas et comment y remédier. Ce n'est pas Garbage In/Garbage Out, c'est Garbage In/Error Out.

Alors que l'on pourrait ignorer les explosions qui en résultent car "aucun véritable utilisateur ne le fera", cela manque le point de l'exercice. Le fuzzing est un moyen bon marché d'éliminer vos biais sur les entrées possibles. C'est un moyen bon marché de jeter toutes les choses étranges les utilisateurs essaieront de le faire sur votre système. En tant qu'ingénieur, votre travail consiste à vous assurer que votre système tombe correctement en panne.


De plus, le "input" de fuzz ne concerne pas seulement les utilisateurs. Cela pourrait être le résultat d'une requête API à un service tiers, que se passe-t-il si cela commence à envoyer des résultats erronés? Comment votre système gère-t-il cela? Un système approprié doit alerter un administrateur qu'un composant a mal tourné. Un système incorrect rejettera discrètement la mauvaise requête, ou pire, l'acceptera comme de bonnes données.

Enfin, certains utilisateurs sont malveillants. Si vous ne testez pas fuzz votre système, quelqu'un d'autre le fera. Ils vont sonder les bords de votre système pour les erreurs courantes et essayer de les utiliser comme trous de sécurité. Les tests Fuzz peuvent simuler cela, dans une certaine mesure, et vous pouvez traiter les éventuelles failles de sécurité découvertes avant qu'elles ne deviennent un problème.

6
Schwern

Si votre objectif est de créer un produit de qualité, testez chaque type d'entrée possible qu'un utilisateur sera physiquement en mesure de soumettre. Sinon, vous attendez simplement le jour où quelqu'un soumettra ce type d'entrée que vous ne vous sentiez pas nécessaire de tester.

Lors d'une grande démonstration d'un nouveau logiciel d'enchères électroniques dans une autorité locale où je travaillais, mon manager a décidé (certes avec un peu de malice) qu'il ressentait le besoin de voir ce qui se passerait s'il mettait une enchère avec une valeur négative. À ma grande surprise, le logiciel d'enchères a permis d'arrêter cette offre absurde et l'ensemble du processus d'enchères. Le type d'enchère en cours de démonstration n'aurait jamais dû permettre la soumission de montants négatifs.

Une partie du grand groupe d'agents des achats et des finances réunis était ennuyée par mon directeur pour avoir soumis une valeur absurde. Mais d'autres, dont moi-même, étaient ennuyés par les développeurs de logiciels pour ne pas avoir testé et rejeté un type aussi évident d'entrée invalide. Je ne peux qu'imaginer à quel point le logiciel a dû être faible pour dévier d'autres types d'entrées invalides (tentatives d'injection de code, caractères exotiques non représentables dans la table de base de données, etc.).

Si cela ne tenait qu'à moi, j'aurais renvoyé le logiciel et je l'aurais jugé impropre à l'usage. La différence entre un produit logiciel faible et un produit logiciel puissant est le niveau de test auquel il a été soumis.

4
Arkanon

Par exemple: lors du test fonctionnel d'un formulaire dans une application Web, nous testerons les champs en entrant différents types de valeurs d'entrée aléatoires.

Oui. C'est une sorte de test mais ce n'est pas un test fonctionnel. C'est ce qu'on appelle stress testing. C'est l'acte d'appliquer une pression sur un système pour voir s'il peut le gérer.

Alors, à quoi sert d'incorporer tous ces cas de test qui peuvent/peuvent ne pas conduire à des bugs, alors que la probabilité d'apparaître ce genre de problèmes en production est bien moindre?

Lorsque vous testez un logiciel - stress, vous essayez de découvrir les limites de ses limites.

Les tests sont en quelque sorte exhaustifs par nature. Où vous devez découvrir les limites d'utilisation, les points de rupture, vérifier toutes les branches logiques ou voir comment les défaillances partielles affectent l'ensemble du système.

Vous pouvez faire passer tous vos tests fonctionnels, mais toujours échouer stress testing.

Je pose cette question uniquement pour savoir si des pratiques standard sont à suivre ou si cela dépend totalement du produit, du domaine et de tous les autres facteurs.

Oui, c'est une pratique standard.

Le test de logiciel consiste à poser une question sur le comportement attendu, et lorsque tous les tests réussissent, cela indique que le logiciel fonctionne comme prévu. C'est pourquoi les tests permettent de bonnes conditions préalables au déploiement des mises à jour.

Les tests de résistance ne fournissent pas d'indicateurs clairs de réussite ou d'échec. Les résultats sont plus informatifs. Il vous indique ce que votre système peut gérer et vous prenez des décisions à partir de ces informations.

Vous pouvez définir des objectifs spécifiques pour les tests de résistance qui doivent être passés afin de passer à l'étape suivante du développement. Ceux-ci peuvent être inclus dans le cadre de votre processus d'assurance qualité, mais les changements dans l'environnement peuvent modifier les résultats d'un test de résistance. Donc, les gens exécutent tests de résistance à différents fois pour voir comment le système gère les conditions changeantes.

Ce que je veux dire, c'est que vous ne pouvez pas simplement exécuter des tests de stress à chaque fois que vous déployez une nouvelle version de votre logiciel, et supposer que cela signifie que tout passera des tests de stress plus tard.

1
Reactgular