Tests unitaires pour une bibliothèque de calcul scientifique

15

J'ai eu un peu d'expérience avec les tests unitaires auparavant, dans ce que j'appelle (pas péjorativement) le projet classique d'ingénierie logicielle: un MVC, avec une interface utilisateur graphique, une base de données, une logique métier dans la couche intermédiaire, etc. Je suis en train d'écrire une bibliothèque de calcul scientifique en C # (ouais, je sais que le C # est trop lent, utilisez C, ne réinventez pas la roue, et tout ça, mais nous avons beaucoup de gens qui font du calcul scientifique dans ma faculté en C #, et nous en avons besoin). C'est un petit projet, en termes de développement de logiciels, car je l'écris principalement par moi-même, et de temps en temps avec l'aide de quelques collègues. De plus, je ne suis pas payé pour ça, et le plus important, c'est un projet académique. Je veux dire, je m'attends à ce qu'il ait une qualité professionnelle un jour, parce que je prévois de devenir open source,

Quoi qu'il en soit, le projet prend de l'ampleur (environ 18 000 lignes de code, ce qui, je pense, est important pour un projet d'un homme), et il me dépasse. J'utilise git pour le contrôle de code source, et je pense que je me sens bien, mais je teste comme à l'ancienne, je veux dire, en écrivant des applications de console complètes qui testent une grande partie du système, principalement parce que je ne sais pas comment faire des tests unitaires dans ce scénario, même si je pense que c'est ce que je devrais faire. Le problème est que la bibliothèque contient principalement des algorithmes, par exemple, des algorithmes de graphe, des classificateurs, des solveurs numériques, des distributions aléatoires, etc. Je ne sais tout simplement pas comment spécifier de minuscules cas de test pour chacun de ces algorithmes, et puisque beaucoup d'entre eux sont stochastique Je ne sais pas comment valider l'exactitude. Pour la classification, par exemple, il existe des mesures telles que la précision et le rappel, mais ces métriques sont meilleures pour comparer deux algorithmes que pour juger un seul algorithme. Alors, comment puis-je définir l'exactitude ici?

Enfin, il y a aussi le problème des performances. Je sais que c'est un ensemble de tests complètement différent, mais les performances sont l'une des caractéristiques importantes des outils scientifiques, plutôt que la satisfaction des utilisateurs ou d'autres mesures de génie logiciel.

L'un de mes plus gros problèmes concerne les structures de données. Le seul test que je peux trouver pour un arbre kd est un test de stress: insérez un grand nombre de vecteurs aléatoires, puis effectuez un grand nombre de requêtes aléatoires et comparez avec une recherche linéaire naïve. La même chose pour les performances. Et avec des optimiseurs numériques, j'ai des fonctions de référence que je peux tester, mais là encore, c'est un test de résistance. Je ne pense pas que ces tests puissent être classés comme des tests unitaires et, plus important encore, exécutés en continu, car la plupart d'entre eux sont plutôt lourds. Mais je pense aussi que ces tests doivent être effectués, je ne peux pas simplement insérer deux éléments, éclater la racine, et oui, cela fonctionne pour le cas 0-1-n.

Alors, quelle est, le cas échéant, l'approche de test (unitaire) pour ce type de logiciel? Et comment organiser les tests unitaires et les tests lourds autour du cycle de construction de code-validation-intégration?

Alejandro Piad
la source

Réponses:

19

Je dirais que le calcul scientifique est en fait assez bien adapté aux tests unitaires. Vous avez des entrées et des sorties définies, des conditions préalables et postconditions clairement définies qui ne changeront probablement pas toutes les deux semaines selon le caprice de certains concepteurs, et aucune exigence d'interface utilisateur difficile à tester.

Vous nommez certains éléments qui pourraient causer des problèmes; voici ce qu'il faut faire à leur sujet:

  • algorithmes randomisés: il y a deux possibilités. Si vous voulez réellement tester la randomisation elle-même, planifiez simplement un grand nombre de répétitions et assurez-vous que la proportion attendue de cas répond au critère souhaité, avec des marges d'erreur suffisamment grandes pour que les échecs de test parasites soient assez rares. (Une suite de tests qui signale de manière fiable des bogues fantômes est bien pire qu'une suite qui n'attrape pas tous les défauts imaginables.) Alternativement, utilisez une source aléatoire configurable et remplacez l'horloge système (ou ce que vous utilisez) par une source déterministe via la dépendance injection afin que vos tests deviennent entièrement prévisibles.
  • algorithmes définis uniquement en termes de précision / rappel: rien ne vous empêche de mettre dans tout un ensemble de cas d'entrée et de mesurer la précision et le rappel en les additionnant tous; il s'agit simplement de générer de manière semi-automatique de tels cas de test de manière efficace afin que la fourniture des données de test ne devienne pas le goulot d'étranglement de votre productivité. Alternativement, spécifier certaines paires d'entrée / sortie judicieusement choisies et affirmer que l'algorithme calcule exactement l'entrée souhaitée peut également fonctionner si la routine est suffisamment prévisible.
  • exigences non fonctionnelles: Si la spécification donne vraiment les exigences d' espace / temps explicites, vous essentiellement ont à exécuter ensemble des suites d'entrée / paires de sortie et vérifier que les conforme d'utilisation des ressources environ au modèle d'utilisation nécessaire. L'astuce consiste à calibrer d'abord votre propre classe de test, afin de ne pas mesurer dix problèmes avec des tailles différentes qui finissent par être trop rapides à mesurer ou qui prennent si longtemps que l'exécution de la suite de tests devient impossible. Vous pouvez même écrire un petit générateur de cas d'utilisation qui crée des cas de test de différentes tailles, selon la vitesse à laquelle le PU fonctionne.
  • tests rapides et lents: qu'il s'agisse de tests unitaires ou d'intégration, vous vous retrouvez souvent avec de nombreux tests très rapides et quelques tests très lents. Étant donné que l'exécution régulière de vos tests est très précieuse, je prends généralement la voie pragmatique et sépare tout ce que j'ai en une suite rapide et lente, de sorte que la plus rapide puisse s'exécuter aussi souvent que possible (certainement avant chaque commit), et peu importe si deux tests «sémantiquement» vont de pair ou non.
Kilian Foth
la source
+1. Merci beaucoup, il y a beaucoup de perspicacité dans votre réponse. Juste quelques questions: que diriez-vous des algorithmes d'optimisation comme les méta-heuristiques. J'ai un tas de fonctions de référence, mais tout ce que je peux faire avec elles est de comparer deux algorithmes différents. Dois-je également trouver un algorithme de référence? Que signifie qu'un algorithme génétique est correct? Et comment puis-je tester chacune des stratégies "paramétrables", comme le type de recombinaison et de mutation, etc.?
Alejandro Piad
1
Pour la méta-heuristique, je me contenterais de choisir quelques paires d'E / S caractéristiques, c'est-à-dire les "succès célèbres" de la routine, et je vérifierais que la méthode (ou la meilleure des deux) trouve effectivement cette solution. Les problèmes de "sélection des cerises" qui fonctionnent bien sont bien sûr non-non dans la recherche d'optimisation, mais pour les tests de logiciels ce n'est pas un problème - vous n'affirmez pas la qualité de l'algorithme, juste l'implémentation correcte. C'est la seule «justesse» que vous pouvez prouver. Quant aux routines multipliables paramétrables: oui, je crains que cela nécessite une quantité combinatoire de tests ...
Kilian Foth
Donc, c'est comme concevoir un benchmark trivial que toutes les implémentations correctes devraient résoudre exactement? Existe-t-il un moyen de prouver la qualité de l'algorithme? Je sais que je ne peux pas définir un standard de qualité la plupart du temps, mais au moins je pourrais souhaiter qu'aucun changement ne diminue la qualité obtenue?
Alejandro Piad