vendredi 24 janvier 2014

Factorisation du code de test avec un fichier include pour les tests

Le code précédent pour tester notre fonction, est tout à fait du «bad code». Il faut impérativement corriger cela, car il ne sert à rien d'essayer de faire du code utile propre et laisser un code de tests dégueulasse. Cela induirait les mêmes effets que si le code utile était mauvais.

Factorisation de l'analyse du résultat du cas:


Je vous propose donc de créer une fonction d'analyse du résultat du test de cas qui sera appelée plusieurs fois pour tester une fonction. Le problème est que nous ne pouvons pas mettre le test de la fonction proprement dit à l'intérieur car avec le langage MQL4 on ne peut pas passer de fonction en paramètre. On est donc obligé de tester le cas puis d'appeler cette fonction d'analyse qui comparera les résultats et fera les affichages et retournera une valeur booléenne pour dire si c'est ok ou bien s'il y a une erreur. Non on va plutôt retourner un entier qui est le nombre d'erreur (0 ou 1) comme ça on pourra l'additionner directement au total des erreurs lors de l'appel de la fonction d'analyse, ça fera un code plus concis. Cette fonction d'analyse du résultat du cas sera placée dans un include, car elle ne sera pas la seule sans doute (pour le moment oui) et qu'elle sera utilisée dans de nombreux experts advisors de tests de code. Cette fonction doit donc recevoir en arguments: le résultat du test, la valeur attendue, le nom de la fonction, le texte représentant les paramètres du cas, le message de succès avec une valeur par défaut et le message d'erreur avec une valeur par défaut. Ça fait beaucoup trop de paramètres, et donc pas «clean code» du tout. Mais hélas, trois fois hélas, avec le MQL4, on est obligé de faire des entorses aux règles du code propre et des compromis. C'est ça ou bien un code très long et très redondant. Autre détail facheux: le type du résultat va varier d'une fonction à tester à l'autre. Or on doit passer ce résultat à la fonction d'analyse, et préciser un type, et il n'y a pas de type général en MQL4 et on ne peut pas se servir du type void pour dire qu'on ne connait pas le type à la compilation et ainsi pouvoir recevoir n'importe quel type de données comme dans le langage C. Ça ne fonctionne pas ! Le compilateur réclame un type précis. Nous allons donc créer une fonction d'analyse par type de valeur de retour, et par «chance» avec MQL4, ça sera en nombre limité. Nous ferons les autres fonctions quand nous en aurons besoin. Je place l'argument du message d'erreur avant celui du succès car le message d'erreur est plus susceptible d'être spécifié que le message de succès, puisqu'il faut placer les valeurs par défaut et non précisées lors de l'appel en dernier. Voici le code complet du fichier include en première version:

//+------------------------------------------------------------------+
//|                                             UtilitairesTests.mqh |
//|                  Copyright 2014, argent-facile-avec-robots-forex |
//|               http://argent-facile-avec-robots-forex.blogspot.fr |
//+------------------------------------------------------------------+
#property copyright "Copyright 2014, argent-facile-avec-robots-forex"
#property link   "http://argent-facile-avec-robots-forex.blogspot.fr"

//+------------------------------------------------------------------+
//| constantes de tests                                              |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| fonctions                                                        |
//+------------------------------------------------------------------+

int analyserResultatDatetimeDuTest(
         datetime resultatTest,
         datetime resultatAttendu,
         string nomFonction,
         string parametresCas,
         string msgErreur = " a produit une erreur de résultat avec le cas ",
         string msgSucces = " a passé le test sans erreurs ") {
   int nbErreurs = 0;
   if(resultatTest == resultatAttendu) {
      Print(nomFonction, msgSucces, resultatTest);
   } else {
      Print(nomFonction, msgErreur, parametresCas, resultatTest);
      nbErreurs++;
   }
   return(nbErreurs);
}


Factorisation des tests des cas:


Nous avons donc maintenant deux lignes par test de cas au lieu de 7: un appel de la fonction à tester avec récupération du résultat et un appel de la fonction qu'on vient de créer pour analyser avec une addition au total des erreurs en même temps. Ces deux étapes sont à faire pour chaque cas. Nous aurons encore du code redondant: ces deux lignes à répéter pour chaque cas. On va factoriser tout ça avec une boucle for, mais dans ce cas il va falloir mettre les données des différents cas dans des tableaux, qu'on mettra en constantes locales à la fonction de test. Nous aurons besoin d'une variable entière noCas (int) pour parcourir la boucle, du nombre d'erreurs (int) comme précédemment, d'une variable résultat (datetime), d'un tableau de string des arguments, un autre tableau de string des paramètres des cas et un tableau de datetime des resultats attendus. Nous pouvons remplir ces tableaux de deux manières:
  1. à la déclaration, avec un égal et la liste des valeurs littérales (nombres et textes dans le code) séparées par des virgules, entre accolades.
  2. en affectant une valeur case par case après la déclaration, et nous y serons obligés bien que ce soit moins pratique pour les valeurs issues de fonctions sur le moment de l'affectation.
Il faut également factoriser ce nombre de cas qui est répété plusieurs fois: dans la déclaration des tableaux et dans la condition de limite de la boucle. Mais lors de déclaration des tableaux on ne peut pas indiquer la taille avec une variable, ni même une constante (c'est la même chose). Par contre il y a un autre moyen qui est d'utiliser les directives #define qui nous permettent de définir des constantes pour le préprocesseur. Celui-ci va remplacer toutes les constantes qu'on lui aura définit par leur valeur associée avant la compilation. Le compilateur ne verra dans le code que le nombre 5 que le préprocesseur aura mis. Pour ne pas faire de noms de constantes #define trop longs je vais donner parfois des noms en anglais quand ils sont plus courts. Voici le tutoriel vidéo et le nouveau code de l'expert advisor de test de l'include ArithmetiqueTemporelle.mqh (certaines lignes entières dans le fichier ont été tronquées pour tenir dans le post, celles qui ont des colonnades de trop nombreux arguments pas «clean code» du tout ! Ahhh ... le MQL4 !):



//+------------------------------------------------------------------+
//|                                  testsArithmetiqueTemporelle.mqh |
//|                  Copyright 2014, argent-facile-avec-robots-forex |
//|               http://argent-facile-avec-robots-forex.blogspot.fr |
//+------------------------------------------------------------------+
#property copyright "Copyright 2014, argent-facile-avec-robots-forex"
#property link   "http://argent-facile-avec-robots-forex.blogspot.fr"

#include <UtilitairesTests.mqh>
#include <ArithmetiqueTemporelle.mqh>

//+------------------------------------------------------------------+
//| defines                                                          |
//+------------------------------------------------------------------+

#define NB_CAS_LAST_KNOWN_SEC_NO 5

//+------------------------------------------------------------------+
//| constantes de tests                                              |
//+------------------------------------------------------------------+

string nomObtenirNoDerniereSecConnue = "obtenirNoDerniereSecConnue()";

//+------------------------------------------------------------------+
//| fonctions de tests                                               |
//+------------------------------------------------------------------+

int testObtenirNoDerniereSecConnue() {
   int noCas, nbErreurs = 0;
   datetime resultat;
   string arguments[NB_CAS_LAST_KNOWN_SEC_NO] = {"NULL",
                                                 "EURUSD",
                                                 "GBPJPY",
                                                 "EUR",
                                                 ""};
   string parametresCas[NB_CAS_LAST_KNOWN_SEC_NO] = {" sans paramètre => ",
                                                     " \"EURUSD\" => ",
                                                     " \"GBPJPY\" => ",
                                                     " \"EUR\" => ",
                                                     " \"\" => "};
   datetime resultatsAttendus[NB_CAS_LAST_KNOWN_SEC_NO];
   resultatsAttendus[0] = TimeCurrent();
   resultatsAttendus[1] = MarketInfo("EURUSD", MODE_TIME);
   resultatsAttendus[2] = MarketInfo("GBPJPY", MODE_TIME);
   resultatsAttendus[3] = 0;
   resultatsAttendus[4] = 0;
   for(noCas=0 ; noCas<NB_CAS_LAST_KNOWN_SEC_NO ; noCas++) {
      resultat = obtenirNoDerniereSecConnue(arguments[noCas]);
      nbErreurs += analyserResultatDatetimeDuTest(resultat,
                                                   resultatsAttendus[noCas],
                                                   nomObtenirNoDerniereSecConnue,
                                                   parametresCas[noCas]);
   }
   return(nbErreurs);
}

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+

int init()
  {
   int nbErreurs = 0;
   nbErreurs += testObtenirNoDerniereSecConnue();
   Print("TESTS DE ArithmetiqueTemporelle.mqh TERMINES AVEC ", nbErreurs, " ERREUR(S).");
   return(0);
  }

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+

int deinit()
  {
   return(0);
  }

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+

int start()
  {
   return(0);
  }

//+------------------------------------------------------------------+

PS: La fonction testée, a un paramètre optionnel, donc avec une valeur par défaut. J'avais commencé par mettre la constante NULL à cette valeur, comme dans le langage C, mais le compilateur MQL4 ne l'accepte pas et j'ai donc mis la chaine de caractères "NULL". Le MQL4 a une constante pour les paramètres vides, c'est la constante EMPTY, et NULL ne concerne que les variables string vides (différent de chaine vide: ""). Mais j'ai laissé la chaine "NULL" car je peux la mettre dans le tableau des arguments et la passer pour le premier cas bien que ce soit un cas sans argument. Cet argument passé est celui par défaut. Ça n'aurait pas pu être le cas avec EMPTY, puisque que le compilateur ne l'accepte pas en tant que valeur dans le tableau.
Je ne peux pas améliorer encore plus le code, je n'ai pas trouvé comment faire pour l'instant, car dans l'idéal il faudrait sortir les déclarations des valeurs des tests et en plus certaines d'entre elles doivent être affectées au moment de l'exécution de la fonction de test (les appels aux fonctions prédéfinies). Nous sommes un peu limités par le langage MQL4. Le code actuel est tout de même bien meilleur que le précédent, y a pas photo ! =D

Aucun commentaire:

Enregistrer un commentaire