Site logo

Triceraprog
La programmation depuis le Crétacé

La Maison dans la colline, partie 7 ()

Dans ce septième article de la série sur le jeu « La maison dans la colline », il va être question de tests anti-regression.

Regressions

Mais qu'est-ce qu'une regression ? C'est un fonctionnement qui donnait toute satisfaction et qui, suite à un changement dans le système, se met à ne plus fonctionner comme attendu. Autrement dit, une apparition de bug !

Les bugs n'arrivent jamais de nulle part, il y a toujours une raison. Mais plus un programme est grand, plus il se complexifie et plus le risque de programmer des morceaux qui entrent en conflit apparaît. C'est à peu près inéluctable et le développement d'un logiciel est, normalement, accompagné d'un certain nombre de règles pour éviter au mieux et surtout repérer au plus vite les défauts qui apparaissent.

La vitesse de détection est importante, car il une regression peut ne pas être immédiatement flagrante. Il est possible que quelque chose casse sur une partie « éloignée » de ce sur quoi on travaille sur le moment. Il est aussi possible que le défaut soit subtile ; présent, mais pas évident. Tout à l'air de bien fonctionner en apparence, mais pas dans les détails. Et si on continue à développer avec ce défaut présent, il se peut très bien que l'on amplifie le problème, ajoutant du bug à du bug.

La vitesse est aussi importante pour une question de contexte. Lorsque l'on a en tête une partie en train d'être travaillée, il est plus simple de corriger ce qui vient d'être modifié que lorsque l'on s'en rend compte plus tard, lorsqu'on est passé à autre chose.

Dans le contexte de « La maison dans la colline », je suis tout seul et, comme je l'ai déjà dit auparavant, j'essaie de maximiser mon temps libre passé sur le projet. Ainsi, une session à essayer de trouver et corriger la raison d'un bug introduit deux sessions avant ne m'enchante pas du tout.

J'ai donc mis en place deux systèmes pour m'aider à détecter rapidement les bugs

Tests unitaires

Le premier système est un système simpliste de tests unitaires. Le principe est de mettre le système dans un certain état, de faire une opération, puis de vérifier que le système est dans l'état attendu.

J'ai mis en place ce système après avoir débuté le projet et lorsque celui-ci commençait à devenir un peu complexe. Malheureusement ou heureusement, j'avais pris quelques raccourcis qui rendaient certains tests un compliqués. Cela m'a pris un peu de temps pour nettoyer ça ; au passage, j'en suis sorti avec quelques nettoyages bienvenue dans le code.

Je n'ai pas non plus beaucoup de tests. Majoritairement, je fais des tests sur la gestion de l'inventaire, qui m'a causé quelques soucis et qui a été ce qui m'a décidé à mettre des tests unitaires. Je fais aussi quelques tests sur mon micro-allocateur de mémoire dynamique (celui de z88dk ne me convenant pas).

Lorsque je lance le programme avec une option de compilation, les tests sont exécutés dans une page spéciale, au démarrage du jeu. Je peux ainsi m'assurer que les tests passent et que donc le fonctionnement de mon système d'inventaire et d'allocation sont toujours d'aplomb.

Voilà à quoi ressemble le test des objets, une fois que j'ai mis en place un environnement de tests avec des objets dans des pièces :

    CHECK(object_count_in_room(ROOM(1)) == 2);
    CHECK(object_count_in_room(ROOM(254)) == 1);
    CHECK(object_count_in_room(ROOM(255)) == 0);

Et voilà une capture d'écran lors d'une regression :

Liste terminée des fonctions du jeu.

Mode automatique

L'autre système que j'ai mis en place est un mode de jeu automatique. Au bout d'un moment, et alors que le nombre d'actions et de pièce commençait à grandir, il me fallait régulièrement jouer toute une première partie du jeu. Je vérifiais les mouvements, les portes et changements de pièce, la prise d'objet dans l'inventaire, l'utilisation d'un objet.

Ça a rapidement été long, pas très amusant et source d'erreurs : est-ce que je fais bien les mêmes étapes ? Est-ce que j'ai encore envie de le faire parce que ça m'ennuie ? Est-ce que quelque chose ne va pas casse pile la fois où j'aurai la flemme de conduire les tests ?

Solution : automatiser le test.

Lorsque je compile le jeu avec le mode automatique inclue, je déroule un petit script qui va exécuter les actions à ma place. C'est assez simpliste, je n'ai pas de retour d'erreur automatisé. Mais au moins, si je vois quelque chose d'étrange, ou bien si le personnage se retrouve bloqué sur une étape, je peux le voir rapidement. Et je peux alors conduire un test manuel pour comprendre dans les détails ce qu'il se passe.

Il n'y a pas beaucoup d'étapes dans ce test automatique, car c'est un peu fastidieux à maintenir. J'ai hésité un moment à rendre le système plus malin, avec une création de script depuis les données du jeu. Par exemple en trouvant le chemin pour aller d'un point A à un point B automatiquement. Mais je ne suis pas allé jusque là, j'ai trouvé un compromis avec un système qui « tente » d'aller vers une position, mais de manière simpliste, que je dois aider manuellement parfois.

Mais que de temps gagné au final !

Voici à quoi ressemble le début du script, qui est stocké dans un tableau de caractères dans le code source :

unsigned char script[] =
        {
                'W', 'P', 4, // Wait page 4 (test page)
                'W', 'T', 20,// Wait frames
                'K', ' ',    // Press ' '
                'W', 'P', 0, //
                'K', ' ',    // Press ' '
                'W', 'P', 1, //
                'K', 'A',    // Press 'A'
                'W', 'T', 5,
                // Go to Kitchen
                'K', KEY_UP,
                'K', KEY_UP,
                'K', KEY_UP,
                'K', KEY_UP,
                'G', 'R', 2,// Go to Room 2

Conclusion

J'ai déjà évoqué mon goût pour les outils et les automatisation dans les articles précédents. L'automatisation des tests est un outil de plus pour se simplifier la vie. Cela demande un peu d'effort en amont, mais force parfois à mieux réfléchir son code pour le rendre flexible, ce qui est bénéfique lors de la mise au point du jeu, et permet un gros gain de temps sur la durée du projet, pour peu que l'on trouve le bon équilibre entre le temps passé à créer les outils et le gain de temps espéré grâce à eux.