Comment concevoir le logiciel d'un jeu de manière à ce qu'il soit facile à tester unitaire?

25

Est-il pratique d'utiliser un framework de test comme JUnit dans une situation de développement de jeu? Quel type de considérations de conception pouvez-vous suivre pour rendre votre jeu plus testable? Quelles parties d'un jeu peuvent / doivent être testées et quelles parties doivent / doivent être laissées aux tests humains?

Par exemple, si la boucle de jeu est encapsulée dans une fonction, il semble que ce serait terriblement difficile à tester. J'aime refactoriser une fonction de "mise à jour" qui prend un delta de temps et fait avancer la logique du jeu; cela permet des astuces intéressantes comme la possibilité de ralentir le jeu en le nourrissant de faux deltas temporels plus lents.

Ricket
la source
6
Pourquoi perdre des heures de travail à écrire des tests unitaires lorsque vous avez une armée pratiquement infinie de travailleurs esclaves pour faire des tests de jeu pour vous? I kid, I kid ...;)
Mike Strobel
1
En fait, vous n'avez pas besoin d'enfant, c'est vraiment un bon point! Je n'y ai pas pensé, mais les jeux sont le type de logiciel le plus simple pour faire tester d'autres personnes. Je suppose que cela compense quelque peu la difficulté des tests de jeu automatisés (unitaires ou autres).
Ricket
3
Les tests unitaires ne consistent pas nécessairement à s'assurer que votre application fonctionne correctement dans son ensemble (c'est-à-dire les tests fonctionnels). Il s'agit davantage de s'assurer que les modifications futures ne cassent pas les fonctionnalités existantes. Bien sûr, un utilisateur peut voir quand le vaisseau spatial est à l'envers, mais lorsque l'algorithme de cheminement est désactivé à 0,001, les effets peuvent ne pas être visibles jusqu'à ce que le jeu soit joué pendant 200 heures. C'est le genre de choses que les tests unitaires peuvent attraper avant que le code ne sorte.
Wade Williams
1
Les jeux sont le logiciel le plus simple pour inciter les utilisateurs à jouer , mais en jouant! = Testing . Obtenir de bons rapports de bogues est assez difficile. Au-delà de cela, la recherche de l'emplacement du bogue dans le code et la vérification que les nouvelles modifications ne cassent pas le code existant bénéficient toutes deux de manière spectaculaire des tests automatisés.
munificent

Réponses:

25

L'un des principes de TDD est que vous laissez TDD dans certains cas influencer votre conception. Vous écrivez un test pour le système, puis écrivez le code pour réussir ce test, gardez les dépendances aussi peu profondes que possible.

Pour moi, il n'y a que deux choses que je ne teste pas dans le cadre des tests unitaires:

Tout d'abord, je ne teste pas les éléments visuels et l'apparence des choses. Je teste cela et l'objet sera au bon endroit après sa mise à jour, qu'une caméra éliminera un objet en dehors de ses limites, que les transformations (au moins celles qui sont effectuées en dehors des shaders) soient effectuées correctement avant d'être remises au moteur graphique , mais une fois qu'il atteint le système graphique, je trace la ligne. Je n'aime pas essayer de se moquer de choses comme DirectX.

Deuxièmement, je ne teste pas vraiment la fonction de boucle de jeu principale. Je teste que chaque système fonctionnera lorsqu'il passera un delta raisonnable et que les systèmes fonctionneront correctement ensemble quand ils en auront besoin. Ensuite, je viens de mettre à jour chaque système avec le delta correct dans la boucle de jeu. Je pourrais avoir un test pour montrer que chaque système a été appelé avec le bon delta, mais dans de nombreux cas, je trouve cela exagéré (à moins que vous ne fassiez une logique complexe pour obtenir votre delta, alors ce n'est pas exagéré).

Jeff
la source
6
Le premier paragraphe est essentiel. Le fait est que TDD vous oblige à concevoir votre code mieux que vous ne l'auriez fait sans lui, simplement pour permettre les tests. Bien sûr, beaucoup pensent qu'ils sont des experts en conception, mais bien sûr, ceux qui sont le plus impressionnés par leurs propres compétences sont généralement ceux qui ont le plus à apprendre ...
dash-tom-bang
2
Je ne suis pas d'accord pour dire que le code est nécessairement mieux conçu en conséquence. J'ai vu de nombreuses abominations où une interface auparavant propre devait être brisée et l'encapsulation cassée afin d'injecter des dépendances testables.
Kylotan
4
Je trouve que cela se produit plus dans les cas où les tests sont écrits pour des classes préexistantes plutôt que l'inverse.
Jeff
@Kylotan, c'est probablement plus un symptôme de mauvaise conception / mauvaise conception de test. Refactorisez et utilisez des simulateurs pour faire des tests propres, ne piratez pas votre code dans un état pire; c'est le contraire de ce qui est censé se produire.
timoxley
2
L'encapsulation est une bonne chose, mais le plus important est de comprendre POURQUOI c'est une bonne chose. Si vous l'utilisez simplement pour cacher les dépendances et une grande interface laide, il crie à peu près la charge que votre conception sous le capot n'est probablement pas très agréable. L'encapsulation ne consiste principalement pas à masquer du code et des dépendances moches, il s'agit principalement de rendre votre code plus facile à comprendre et de laisser au consommateur moins de chemins à suivre lorsqu'il essaie de raisonner sur le code.
Martin Odhelius
19

Noel Llopis a couvert les tests unitaires dans la mesure où je pense que vous recherchez:

En ce qui concerne le test de la boucle de jeu entière, il existe une autre technique pour empêcher les régressions fonctionnelles dans votre code. L'idée est que votre jeu se joue lui-même via un système de relecture (c.-à-d. D'abord enregistrer les entrées du joueur, puis les rejouer sur une autre piste). Pendant la relecture, vous générez un instantané de l'état de chaque objet pour chaque image. Cette collection d'état peut alors être appelée "l'image dorée". Lors de la refactorisation de votre code, vous réexécutez la relecture et comparez l'état de chaque image à l'état de l'image dorée. S'il diffère, le test a échoué.

Bien que les avantages de cette technique soient évidents, vous devez faire attention à certaines choses:

  • Votre jeu doit être déterministe, vous devez donc être prudent avec des choses comme en fonction de votre horloge système, des événements système / réseau, des générateurs de nombres aléatoires qui ne sont pas définis de manière déterministe. etc.
  • les modifications de contenu après la génération de l'image dorée peuvent entraîner des différences légitimes avec votre image dorée. Il n'y a aucun moyen de contourner cela - lorsque vous modifiez votre contenu, vous devez régénérer votre image dorée.
jpaver
la source
+1 Il convient également de noter que la valeur de cette approche est directement liée à la longueur de "l'image dorée": si elle n'est pas assez longue, vous pouvez facilement manquer des bogues; si c'est trop long, vous aurez beaucoup d'attente à faire. Il est important d'essayer de faire fonctionner le jeu peut-être un ordre de grandeur plus rapidement dans ce mode, que vous le feriez dans des circonstances d'exécution normales, pour réduire le temps. Ignorer le rendu peut aider!
Ingénieur
9

Je suis d'accord avec les commentaires de Jeff et de jpaver. Je voulais également ajouter que l'adoption d'un modèle de composant pour votre architecture augmente considérablement sa testabilité. Avec un modèle de composant, chaque composant doit effectuer une seule unité de travail et doit pouvoir être testé isolément (ou avec des objets factices limités).

De même, les autres parties du jeu qui dépendent des entités ne devraient compter que sur un sous-ensemble des composants pour fonctionner. En pratique, cela signifie que vous pouvez généralement simuler quelques faux composants pour faciliter le test de ces zones ou vous pouvez simplement composer des entités partielles à des fins de test. par exemple, vous omettez les composants de rendu et d'entrée car ceux-ci ne sont pas nécessaires pour tester la physique.

Enfin, j'ignorerais les conseils sur les performances que vous obtenez de certaines personnes de la communauté du jeu concernant les interfaces. Écrivez bien le code avec les interfaces et si vous rencontrez des problèmes de performances sur la route, vous pouvez facilement profiler, identifier et refactoriser pour résoudre le problème. Je n'ai pas été convaincu par les préoccupations de Noel concernant l'impact sur les performances des interfaces ou la surcharge de complexité qu'elles ont ajoutée.

Cela dit, n'allez pas trop loin pour essayer de tester chaque petite chose indépendamment. Comme la plupart des choses, les tests et la conception visent à trouver le bon équilibre.

Alex Schearer
la source
Veuillez noter, même si vous ne semblez pas être nouveau dans SX, que les réponses ne sont pas ordonnées et dire quelque chose comme "les deux commentaires ci-dessus" est voué à être déprécié rapidement, car il est actuellement maintenant que vous avez une voix avant une des deux autres réponses en ce moment. :)
Ricket
Merci, je n'y ai pas pensé en commentant. J'ai depuis mis à jour le commentaire pour sélectionner les commentaires individuels.
Alex Schearer
3

J'ai pensé ajouter une deuxième réponse en réponse au commentaire du PO selon lequel l'utilisateur pourrait remplacer les tests unitaires. Je crois que c'est complètement faux car le but des tests unitaires n'est pas d'assurer la qualité. Si vous voulez que des tests soient en place pour garantir la qualité de votre programme, vous devriez probablement enquêter sur des tests de scénarios ou investir dans une excellente surveillance.

(Avec un excellent contrôle et un produit fonctionnant en tant que service, il est en effet possible de pousser certains "tests" sur le client, mais vous devez disposer d'un système sophistiqué qui détecte rapidement les erreurs et annule les modifications responsables. Cela est probablement trop pour une petite équipe de développement.)

Le principal avantage des tests unitaires est qu'ils obligent le développeur à écrire du code mieux conçu. L'acte de test met le développeur au défi de séparer les préoccupations et d'encapsuler les données. Il encourage également le développeur à utiliser des interfaces et autres abstractions afin de ne tester qu'une seule chose.

Alex Schearer
la source
D'accord, sauf que les tests unitaires n'obligent pas les développeurs à écrire du bon code. Il y a une autre option: des tests vraiment mal écrits. Vous devez vous engager à l'idée de tests unitaires de vos codeurs pour les empêcher d'être blasés.
tenpn
2
Bien sûr, mais ce n'est pas une raison pour jeter le bébé avec l'eau du bain. Utilisez les outils de manière responsable, mais utilisez d'abord les outils.
Alex Schearer