GameDev TDD ou Rabbit Hell

TDD est rarement utilisé dans le développement de jeux. Il est généralement plus facile d'embaucher un testeur que de mettre de côté un développeur pour écrire des tests - cela économise à la fois des ressources et du temps. Par conséquent, chaque utilisation réussie de TDD devient plus intéressante. Sous la coupe, la traduction du matériau, où cette technique de développement a été utilisée pour créer le mouvement des personnages dans le jeu ElemenTerra.



Le développement piloté par les tests ou TDD (développement par le biais de tests) est une technique de développement logiciel dans laquelle l'ensemble du processus est divisé en plusieurs petits cycles. Des tests unitaires sont écrits, puis du code est écrit qui réussit ces tests, puis une refactorisation est effectuée. Et l'algorithme se répète.

TDD Basics


Supposons que nous écrivions une fonction qui ajoute deux nombres. Dans un flux de travail normal, nous l'écrivions simplement. Mais pour utiliser TDD, vous devez commencer par créer une fonction d'espace réservé et des tests unitaires:

// Placeholder-,    : int add(int a, int b){ return -1; } // Unit-,   ,  add    : void runTests(){ if (add(1, 1) is not equal to 2) throw error; if (add(2, 2) is not equal to 4) throw error; } 

Au début, nos tests unitaires ne fonctionneront pas, car la fonction d'espace réservé renvoie -1 pour chaque entrée. Nous pouvons maintenant exécuter add correctement pour renvoyer a + b . Les tests seront passés. Cela peut sembler une solution de contournement, mais il existe plusieurs avantages:

Si nous écrivons par erreur add comme a - b , nos tests ne fonctionneront pas et nous apprendrons immédiatement comment corriger la fonction. Sans tests, nous ne pouvons pas attraper cette erreur et voir une réaction non standard qui prendra du temps à déboguer.
Nous pouvons continuer les tests et les exécuter à tout moment pendant l'écriture du code. Cela signifie que si un autre programmeur modifie accidentellement l' ajout , il reconnaîtra immédiatement l'erreur - les tests échoueront à nouveau.

TDD dans le jeu dev


Il y a deux problèmes avec TDD dans le développement de jeu. Premièrement, de nombreuses fonctions de jeu ont des objectifs subjectifs qui ne peuvent pas être mesurés. Et deuxièmement, il est difficile d’écrire des tests couvrant toutes les possibilités de l’espace des mondes qui regorgent d’objets complexes en interaction. Les développeurs qui veulent que les mouvements de leurs personnages «paraissent bien» ou que les simulations physiques «ne paraissent pas saccadées» auront du mal à exprimer ces métriques comme des conditions déterministes «réussies / non réussies».

Cependant, la technique TDD est applicable à des caractéristiques complexes et subjectives - par exemple, le mouvement des personnages. Et dans le jeu ElemenTerra, nous l'avons fait.

Tests unitaires contre les niveaux de débogage


Avant de commencer à pratiquer, je veux faire la distinction entre un test unitaire automatique et un «niveau de débogage» traditionnel. La création d'emplacements cachés avec des conditions artificielles est une chose courante dans gamedev. Cela permet aux programmeurs et aux QA de surveiller les événements individuels.


Niveau de débogage secret dans The Legend of Zelda: The Wind Waker

ElemenTerra a plusieurs de ces niveaux: un niveau plein de géométrie problématique pour le personnage d'un joueur, des niveaux avec des interfaces utilisateur spéciales qui déclenchent certains états de jeu et d'autres.

Comme les tests unitaires, ces niveaux de débogage peuvent être utilisés pour reproduire et diagnostiquer les erreurs. Mais à certains égards, ils diffèrent:

Les tests unitaires divisent les systèmes en parties et les évaluent individuellement, tandis que les niveaux de débogage effectuent les tests de manière plus globale. Après avoir trouvé l'erreur au niveau du débogage, les développeurs peuvent encore avoir besoin de rechercher manuellement le point d'erreur.
Les tests unitaires sont automatisés et doivent à chaque fois donner des résultats déterministes, tandis que de nombreux niveaux de débogage sont «contrôlés» par le joueur. Cela fait une différence dans les sessions.

Mais cela ne signifie pas que les tests unitaires sont meilleurs que les niveaux de débogage. Ces derniers sont souvent plus pratiques. Cependant, les tests unitaires peuvent même être utilisés sur des systèmes où ils n'étaient pas traditionnellement présents.

Bienvenue à Rabbit Hell


À ElemenTerra, les joueurs utilisent les forces mystiques de la nature pour sauver les créatures affectées par une tempête spatiale. L'une de ces forces est la capacité d'ouvrir la voie qui mène les créatures à la nourriture et à l'abri. Étant donné que ces chemins sont des grilles dynamiques créées par les joueurs, le mouvement de la créature doit traiter des cas géométriques inhabituels et un terrain arbitrairement complexe.

Le mouvement des personnages est l'un de ces systèmes complexes où «tout affecte tout le reste». Si vous avez déjà fait cela, vous savez que lors de l'écriture de nouveau code, il est très facile de casser les fonctionnalités existantes. Avez-vous besoin de lapins pour escalader de petits rebords? D'accord, mais maintenant ils se contractent, grimpant les pentes. Voulez-vous que les chemins du lézard ne se croisent pas? Cela a fonctionné, mais maintenant leur comportement typique est ruiné.

En tant que responsable de l'IA et de la majeure partie du code de gameplay, je savais que je n'avais pas le temps d'erreurs surprises. Je voulais immédiatement remarquer la régression, donc le développement à l'aide de TDD m'a semblé être une bonne option.

L'étape suivante a été la création d'un système dans lequel je pouvais facilement identifier chaque cas de mouvement sous la forme d'un test de réussite / échec simulé:



Cet "enfer de lapin" se compose de 18 couloirs isolés. Chacun avec une créature et son propre itinéraire, conçu pour se déplacer uniquement si une certaine fonction de mouvement fonctionne. Les tests sont considérés comme réussis si le lapin est capable de se déplacer pendant une durée infiniment longue sans se coincer. Sinon, échec. Notez que nous testons uniquement le corps des créatures (pion en termes irréels), pas l'intelligence artificielle. Dans ElemenTerra, les créatures peuvent manger, dormir et réagir au monde, mais dans "l'enfer des lapins", leur seule instruction est de courir entre deux points.

Voici quelques exemples de tels tests:


1, 2, 3: Libre circulation, obstacles statiques et obstacles dynamiques


8 et 9: Pentes uniformes et terrain accidenté


10: Plancher en voie de disparition


13: Reproduction d'un bug dans lequel les créatures tournaient sans cesse autour des cibles proches


14 et 15: Capacité de naviguer sur des rebords plats et complexes

Parlons des similitudes et des différences entre mon implémentation et le TDD «propre».

Mon système était similaire à TDD en ceci:

  • J'ai commencé à travailler sur des fonctions en créant des tests, puis j'ai écrit le code nécessaire pour les exécuter.
  • J'ai continué d'exécuter d'anciens tests, en ajoutant de nouvelles fonctionnalités.
  • Chaque test a mesuré exactement une partie du système, ce qui m'a permis de trouver rapidement des problèmes.
  • Les tests étaient automatisés et ne nécessitaient pas d'entrée du joueur.

Et différait en cela:

  • Lors de l'évaluation des tests, il y avait un élément de subjectivité. Alors que les vraies erreurs de déplacement (le personnage ne passait pas de A à B) pouvaient être détectées par programme. C'est-à-dire, par exemple, une position asymétrique, des problèmes de synchronisation de l'animation et des mouvements de contraction nécessitaient une évaluation humaine.
  • Les tests n'étaient pas complètement déterministes. Des facteurs aléatoires, tels que les fluctuations de la fréquence d'images, ont provoqué de petits écarts. Mais en général, les créatures suivent généralement les mêmes chemins et ont le même succès / échec entre les sessions.

Limitations


Utiliser TDD pour déplacer une créature ElemenTerra était un énorme avantage, mais mon approche avait plusieurs limites:

  • Les tests unitaires ont évalué chaque caractéristique du mouvement individuellement, donc les erreurs avec des combinaisons de plusieurs caractéristiques n'ont pas été prises en compte. Parfois, il était nécessaire de compléter les tests unitaires par des niveaux de débogage traditionnels.
  • ElemenTerra a quatre types de créatures, mais les tests ne contiennent que des lapins. C'est une caractéristique de notre calendrier de production (les trois autres types ont été ajoutés beaucoup plus tard dans le développement). Heureusement, tous les quatre ont la même mobilité, mais le grand corps de Mossmork a causé plusieurs problèmes. La prochaine fois, j'aurais les tests engendrer dynamiquement les espèces sélectionnées au lieu d'utiliser des lapins pré-placés.


Ce Mossmork nécessite un peu plus d'espace qu'un lapin.

TDD est votre choix?


Les développeurs peuvent consacrer trop d'efforts aux niveaux de tests unitaires que le joueur n'appréciera jamais. Je ne le nie pas, j'ai moi-même eu beaucoup de plaisir à créer "l'enfer du lapin". Ces fonctions internes peuvent prendre du temps et compromettre des étapes plus importantes. Pour éviter que cela ne se produise, étudiez soigneusement où et quand utiliser les tests unitaires. Ci-dessous, j'ai mis en évidence plusieurs critères qui justifient TDD pour le mouvement d'une créature ElemenTerra.

1. Cela prendra-t-il beaucoup de temps pour terminer manuellement les tâches de test?

Avant de passer du temps sur des tests automatisés, vous devez vérifier si nous pouvons évaluer la fonction à l'aide des commandes de jeu conventionnelles. Si vous voulez vous assurer que vos clés déverrouillent les portes, générez la clé et ouvrez la porte pour elles. La création de tests unitaires pour cette fonction serait une perte de temps - les tests manuels ne prennent que quelques secondes.

2. Est-il difficile de créer des cas de test manuellement?

Les tests unitaires automatisés sont justifiés lorsqu'il existe des cas connus et difficiles à reproduire. Le test n ° 7 de «l'enfer des lapins» vérifie comment ils marchent le long des rebords - quelque chose que l'IA s'efforce généralement d'éviter. Une telle situation peut être difficile ou impossible à reproduire à l'aide des commandes de jeu, et les tests sont faciles.

3. Savez-vous que les résultats souhaités ne changeront pas?

La conception du jeu est entièrement basée sur des itérations, de sorte que les objectifs des fonctionnalités peuvent changer à mesure que votre jeu change. Même de petits changements peuvent invalider les métriques par lesquelles vous évaluez vos fonctionnalités, et donc tous les tests unitaires. Si le comportement des créatures pendant la nourriture, le sommeil et l'interaction avec le joueur changeait plusieurs fois, la transition du point A au point B restait inchangée. Par conséquent, le code de mouvement et ses tests unitaires sont restés pertinents tout au long du développement.

4. Les régressions sont-elles susceptibles de passer inaperçues?

Avez-vous rencontré une situation lorsque vous avez terminé l'une des dernières tâches avant d'envoyer le jeu, et soudain, vous trouvez une erreur qui enfreint les règles? Et dans la fonction que vous avez terminée il y a de nombreuses années. Les jeux sont de gigantesques systèmes interconnectés, et il est donc naturel que l'ajout d'une nouvelle fonction B puisse entraîner l'échec de l'ancienne fonction A.

Ce n'est pas si mal quand une fonction cassée est utilisée partout (par exemple, un saut) - vous devriez immédiatement remarquer une panne de la mécanique. Les erreurs découvertes dans un développement ultérieur peuvent perturber le calendrier et, après le lancement, peuvent nuire au gameplay.

5. Le pire qui puisse arriver lors de l'utilisation de tests et sans eux?

La création de tests est une forme de gestion des risques. Imaginez que vous décidiez d'acheter une assurance automobile. Vous devez répondre à trois questions:

  • Combien coûtent les primes d'assurance mensuelles?
  • Quelle est la probabilité que la voiture soit endommagée?
  • Quel serait le pire scénario si vous n'étiez pas assuré?

Pour TDD, nous pouvons imaginer des contributions mensuelles sous forme de coûts de production pour l'entretien de nos tests unitaires, la probabilité de dommages à la voiture comme la probabilité d'avoir un bug, et le coût d'un remplacement complet de voiture comme le pire des cas pour une erreur de régression.

S'il faut beaucoup de temps pour créer un test de fonctionnalité, il est simple et peu susceptible d'être modifié (ou il peut être traité s'il se casse dans le développement ultérieur), alors les tests unitaires peuvent causer plus de problèmes que de bien. Si les tests sont faciles à faire, la fonction est instable et interconnectée (ou ses erreurs prendront beaucoup de temps), alors les tests aideront.

Limites d'automatisation


Les tests unitaires peuvent être un excellent ajout à la recherche et à l'élimination des erreurs, mais ils ne remplacent pas la nécessité d'un contrôle de qualité professionnel dans les jeux à grande échelle. L'AQ est un art qui nécessite de la créativité, un jugement subjectif et une excellente communication technique.

Source: https://habr.com/ru/post/fr455076/


All Articles