vendredi 31 janvier 2014

Code de tests des fonctions de nombre et numéro de secondes dans les périodes

Je l'avais dit: «il faut tester impérativement le code des fonctions des bibliothèques avant de les utiliser dans des projets.» et nous le faisons dès à présent.

Factorisation des messages par défaut avec deux directives #define:


La première fonction que nous avons réalisé pour le type datetime contient des messages par défaut, il faut les mutualiser pour ne pas les répéter dans le code. Mais nous ne pouvons pas employer de constantes, car le compilateur les refuse pour une déclaration d'argument de fonction. Nous allons alors utiliser deux directives préprocesseur #define. Nous ajoutons donc ces deux lignes dans la section define du fichier UtilitairesTests.mqh:

#define MESSAGE_ERREUR " a produit une erreur de résultat avec le cas "
#define MESSAGE_SUCCES " a passé le test sans erreurs "

Il faut ensuite mettre ces étiquettes aux endroits où il faut que ces chaines de caractères se trouvent. Donc dans la liste des arguments de la fonction analyserResultatDatetimeDuTest() du même fichier nous remplaçons les chaines de caractères et leurs guillemets par les étiquettes MESSAGE_ERREUR et MESSAGE_SUCCES. Il faut bien comprendre que cela ne change rien pour le compilateur qui lui verra le code comme si on n'avait fait aucune modification. En effet le préprocesseur passe avant le compilateur et remplace l'étiquette par les caractères qu'on a mis après l'étiquette sur la ligne du #define. Donc le compilateur verra à la place des étiquettes dans le code les chaines que nous avons voulu mutualiser. Et les lignes de directives #define ne seront plus là. Voici le code de la fonction:

int analyserResultatDatetimeDuTest(
         datetime resultatTest,
         datetime resultatAttendu,
         string nomFonction,
         string parametresCas,
         string msgErreur=MESSAGE_ERREUR,
         string msgSucces=MESSAGE_SUCCES) {
   int nbErreurs = 0;
   if(resultatTest == resultatAttendu) {
      Print(nomFonction, msgSucces, resultatTest);
   } else {
      Print(nomFonction, msgErreur, parametresCas, resultatTest);
      nbErreurs++;
   }
   return(nbErreurs);
}


Une fonction d'analyse de résultat de test pour le type int:


Maintenant occupons-nous de l'analyse du résultat de test, s'il est du type entier (int). En effet, nous avons une fonction qui analyse le résultat d'un test seulement si c'est un horodatage (datetime) et ne peut en analyser de types différents car on doit passer ce résultat en argument de la fonction, et que cet argument doit avoir un type bien précis. On va donc créer la même fonction mais pour le type entier où datetime est remplacé par int y compris dans le nom et avec une majuscule pour la lisibilité (sinon il y aurait deux fonctions du même nom ce que le compilateur refuse et on ne pourrait pas les distinguer). Voici le code de cette fonction:

int analyserResultatIntDuTest(
         int resultatTest,
         int resultatAttendu,
         string nomFonction,
         string parametresCas,
         string msgErreur=MESSAGE_ERREUR,
         string msgSucces=MESSAGE_SUCCES) {
   int nbErreurs = 0;
   if(resultatTest == resultatAttendu) {
      Print(nomFonction, msgSucces, resultatTest);
   } else {
      Print(nomFonction, msgErreur, parametresCas, resultatTest);
      nbErreurs++;
   }
   return(nbErreurs);
}

Et pourquoi attendre ? Dans la foulée, on écrit (ou plutôt copie et colle, avec trois modifications pour chacune) les mêmes fonctions pour les types bool, double, string et color. Je vous donne le code complet du fichier à la fin du post. (Ces codes complets sont juste là pour vous donner une référence, un moyen de contrôle sur vos écritures, il faudrait pour bien apprendre que vous fassiez l'effort d'écrire vous même le code.)

Le test de ObtenirNbSecsDsPeriode():


On peut donc maintenant écrire du code pour tester notre deuxième fonction du fichier include, celle qui donne le nombre de secondes dans une période. Il y a peu de cas à tester, ce sera assez rapide, car cette fonction prend un entier positif qu'on lui fournit et le multiplie par 60. Si on ne lui donne pas, elle le récupère sur la période courante. On va donc tester deux cas positifs, un cas négatif, un cas nul et un cas sans argument. Ce sera largement suffisant. Normalement, aucun nombre négatif n'est sensé être passé à cette fonction, mais ça ne l'empêchera pas de donner un résultat. Nous n'avons pas fait de code pour lever une erreur si l'argument venait à être négatif, ou bien fournir une valeur par défaut si l'argument n'est pas bon. C'est à réfléchir. Nous verrons ça plus après. Il n'y a qu'un seul argument à la fonction à tester, donc un code d'exécution binaire simple: avec ou sans l'argument. On va donc coder nos deux fonctions: une pour l'exécution avec ou sans argument et l'autre pour faire tous les cas de test et faire analyser les résultat. On n'oublie pas non plus d'ajouter un #define pour le nombre de cas de test et la constante qui contient le nom de la fonction. On change évidemment les types des résultats et des arguments. Et on ajoute une ligne exécutant la fonction de test dans la fonction init(), sinon, pas de test ! Et bien sûr, on change les valeurs des arguments et résultats pour les différents cas. Voici les cas traités:
  1. Sans argument, le résultat attendu est le nombre de secondes de la période courante, soit  60 * Period() .
  2. Argument 5 (timeframe M5), le résultat attendu est 5*60=300.
  3. Argument 240 (timeframe H4), le résultat attendu est 240*60=14 400.
  4. Argument -2 (ne doit normalement pas arriver, sauf bug), résultat attendu -2*60=-120.
  5. Argument 0 (no doit normalement pas arriver, sauf bug), résultat attendu 0*60=0.
Voici les lignes de codes à ajouter à l'expert de test de l'include (testArithmetiqueTemporelle.mq4):

#define NB_CAS_NB_SECS_IN_PERIOD 5

string nomObtenirNbSecsDsPeriode = "obtenirNbSecsDsPeriode()"

int execObtenirNbSecsDsPeriode(int argument) {
   int resultat;
   if(argument == ARG_INT_EMPTY) { resultat = obtenirNbSecsDsPeriode(); }
   else { resultat = obtenirNbSecsDsPeriode(argument); }
   return(resultat);
}

int testObtenirNbSecsDsPeriode() {
   int noCas, nbErreurs = 0;
   int resultat;
   int arguments[NB_CAS_NB_SECS_IN_PERIOD] = {ARG_INT_EMPTY, 5, 240, -2, 0};
   string parametresCas[NB_CAS_NB_SECS_IN_PERIOD] = {" sans paramètres => ",
                                                     " 5 => ",
                                                     " 240 => ",
                                                     " -2 => ",
                                                     " 0 => "};
   int resultatsAttendus[NB_CAS_NB_SECS_IN_PERIOD];
   resultatsAttendus[0] = 60 * Period();
   resultatsAttendus[1] = 300;
   resultatsAttendus[2] = 14400;
   resultatsAttendus[3] = -120;
   resultatsAttendus[4] = 0;
   for(noCas=0 ; noCas<NB_CAS_NB_SECS_IN_PERIOD ; noCas++) {
      resultat = execObtenirNbSecsDsPeriode(arguments[noCas]);
      nbErreurs += analyserResultatIntDuTest(resultat,
                                             resultatsAttendus[noCas],
                                             nomObtenirNbSecsDsPeriode,
                                             parametresCas[noCas]);
   }
   return(nbErreurs);
}

Et ne pas oublier la ligne à ajouter dans la fonction init():

   nbErreurs += testObtenirNbSecsDsPeriode();


Le test de obtenirNoSecDsPeriode():


Même chose pour la fonction qui calcule le numéro de seconde à l'intérieur d'une période. La différence est qu'elle reçoit deux arguments au lieu d'un seul, et que l'un des deux est optionnel. Nous prévoyons donc cinq cas de tests aussi, deux sans l'argument optionnel et trois avec le deuxième argument:
  1. Sans l'argument optionnel, et avec un petit horodatage: 127.
    Résultat attendu:  127 % (60 * Period()).
  2. Sans l'argument optionnel, et avec un horodatage plus conséquent: 123456789.
    Résultat attendu:  123456789 % (60 * Period()).
  3. Avec un nombre de secondes par période de 240 et un horodatage de 123456789.
    Résultat attendu: 69.
  4. Avec un nombre de secondes par période de 900 et un horodatage de 123456789.
    Résultat attendu: 189.
  5. Avec un nombre de secondes par période de 60 et un horodatage de 127.
    Résultat attendu: 7.

Pour le code, on ajoute la directive #define pour déclarer le nombre de cas de test, la constante string pour définir le nom de la fonction pour les affichages, on écrit la fonction qui exécute la fonction à tester selon le cas avec ou sans argument optionnel. On écrit aussi la fonction de test qui définit les valeurs des cas et fait les tests dans une boucle. Pour les valeurs des cas de test, il faut ajouter un autre tableau d'arguments puisqu'elle en demande deux. Et on n'oublie pas d'ajouter la ligne d'exécution du test de cette fonction dans la fonction init(). Voici le code à ajouter à l'expert advisor:

#define NB_CAS_NO_SEC_IN_PERIOD 5

string nomObtenirNoSecDsPeriode = "obtenirNoSecDsPeriode()"

int execObtenirNoSecDsPeriode(datetime argumentHorodatage, int argumentNbSecsDsPeriode) {
   int resultat;
   if(argumentNbSecsDsPeriode == ARG_INT_EMPTY) {
      resultat = obtenirNoSecDsPeriode(argumentHorodatage); }
   else { resultat = obtenirNoSecDsPeriode(argumentHorodatage, argumentNbSecsDsPeriode); }
   return(resultat);
}

int testObtenirNoSecDsPeriode() {
   int noCas, nbErreurs = 0;
   int resultat;
   datetime argumentsHorodatages[NB_CAS_NO_SEC_IN_PERIOD] = {127,
                                                        123456789,
                                                        123456789,
                                                        123456789,
                                                        127};
   int argumentsNbSecsDsPeriode[NB_CAS_NO_SEC_IN_PERIOD] = {ARG_INT_EMPTY,
                                                                 ARG_INT_EMPTY,
                                                                 240,
                                                                 900,
                                                                 60};
   string parametresCas[NB_CAS_NO_SEC_IN_PERIOD] = {" 127, EMPTY => ",
                                                     " 123456789, EMPTY => ",
                                                     " 123456789, 240 => ",
                                                     " 123456789, 900 => ",
                                                     " 127, 60 => "};
   int resultatsAttendus[NB_CAS_NO_SEC_IN_PERIOD];
   resultatsAttendus[0] = 127 % (60 * Period());
   resultatsAttendus[1] = 123456789 % (60 * Period());
   resultatsAttendus[2] = 69;
   resultatsAttendus[3] = 189;
   resultatsAttendus[4] = 7;
   for(noCas=0 ; noCas<NO_CAS_NB_SEC_IN_PERIOD ; noCas++) {
      resultat = execObtenirNbSecsDsPeriode(argumentsHorodatages[noCas],
                                            argumentsNbSecsDsPeriode[noCas]);
      nbErreurs += analyserResultatIntDuTest(resultat,
                                             resultatsAttendus[noCas],
                                             nomObtenirNoSecDsPeriode,
                                             parametresCas[noCas]);
   }
   return(nbErreurs);
}

Le code est un peu décousu car des lignes sont tronquées. Visionnez la vidéo et vous aurez une meilleure présentation. Et dans la fonction init():

   nbErreurs += testObtenirNoSecDsPeriode();


Le tutoriel vidéo et les deux codes complets:




//+------------------------------------------------------------------+
//|                                             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"

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

#define  ARG_STRING_EMPTY "-#-EMPTY-#-"
#define  ARG_INT_EMPTY 2147483647
#define  ARG_BOOL_EMPTY -1
#define  ARG_DOUBLE_EMPTY 123456789.123456789
#define  ARG_DATETIME_EMPTY 4294967295
#define  ARG_COLOR_EMPTY 4294967295

#define MESSAGE_ERREUR " a produit une erreur de résultat avec le cas "
#define MESSAGE_SUCCES " a passé le test sans erreurs "

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

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

int analyserResultatDatetimeDuTest(
         datetime resultatTest, datetime resultatAttendu,
         string nomFonction, string parametresCas,
         string msgErreur = MESSAGE_ERREUR, string msgSucces = MESSAGE_SUCCES) {
   int nbErreurs = 0;
   if(resultatTest == resultatAttendu) {
      Print(nomFonction, msgSucces, resultatTest);
   } else {
      Print(nomFonction, msgErreur, parametresCas, resultatTest);
      nbErreurs++;
   }
   return(nbErreurs);
}

int analyserResultatIntDuTest(
         int resultatTest, int resultatAttendu,
         string nomFonction, string parametresCas,
         string msgErreur = MESSAGE_ERREUR, string msgSucces = MESSAGE_SUCCES) {
   int nbErreurs = 0;
   if(resultatTest == resultatAttendu) {
      Print(nomFonction, msgSucces, resultatTest);
   } else {
      Print(nomFonction, msgErreur, parametresCas, resultatTest);
      nbErreurs++;
   }
   return(nbErreurs);
}

int analyserResultatBoolDuTest(
         bool resultatTest, bool resultatAttendu,
         string nomFonction, string parametresCas,
         string msgErreur = MESSAGE_ERREUR, string msgSucces = MESSAGE_SUCCES) {
   int nbErreurs = 0;
   if(resultatTest == resultatAttendu) {
      Print(nomFonction, msgSucces, resultatTest);
   } else {
      Print(nomFonction, msgErreur, parametresCas, resultatTest);
      nbErreurs++;
   }
   return(nbErreurs);
}

int analyserResultatDoubleDuTest(
         double resultatTest, double resultatAttendu,
         string nomFonction, string parametresCas,
         string msgErreur = MESSAGE_ERREUR, string msgSucces = MESSAGE_SUCCES) {
   int nbErreurs = 0;
   if(resultatTest == resultatAttendu) {
      Print(nomFonction, msgSucces, resultatTest);
   } else {
      Print(nomFonction, msgErreur, parametresCas, resultatTest);
      nbErreurs++;
   }
   return(nbErreurs);
}

int analyserResultatStringDuTest(
         string resultatTest, string resultatAttendu,
         string nomFonction, string parametresCas,
         string msgErreur = MESSAGE_ERREUR, string msgSucces = MESSAGE_SUCCES) {
   int nbErreurs = 0;
   if(resultatTest == resultatAttendu) {
      Print(nomFonction, msgSucces, resultatTest);
   } else {
      Print(nomFonction, msgErreur, parametresCas, resultatTest);
      nbErreurs++;
   }
   return(nbErreurs);
}

int analyserResultatColorDuTest(
         color resultatTest, color resultatAttendu,
         string nomFonction, string parametresCas,
         string msgErreur = MESSAGE_ERREUR, string msgSucces = MESSAGE_SUCCES) {
   int nbErreurs = 0;
   if(resultatTest == resultatAttendu) {
      Print(nomFonction, msgSucces, resultatTest);
   } else {
      Print(nomFonction, msgErreur, parametresCas, resultatTest);
      nbErreurs++;
   }
   return(nbErreurs);
}

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



//+------------------------------------------------------------------+
//|                                  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
#define NB_CAS_NB_SECS_IN_PERIOD 5
#define NB_CAS_NO_SEC_IN_PERIOD 5

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

string nomObtenirNoDerniereSecConnue = "obtenirNoDerniereSecConnue()";
string nomObtenirNbSecsDsPeriode = "obtenirNbSecsDsPeriode()"
string nomObtenirNoSecDsPeriode = "obtenirNoSecDsPeriode()"

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

datetime execObtenirNoDerniereSecConnue(string argument) {
   datetime resultat;
   if(argument == ARG_STRING_EMPTY) { resultat = obtenirNoDerniereSecConnue(); }
   else { resultat = obtenirNoDerniereSecConnue(argument); }
   return(resultat);
}

int testObtenirNoDerniereSecConnue() {
   int noCas, nbErreurs = 0;
   datetime resultat;
   string arguments[NB_CAS_LAST_KNOWN_SEC_NO] = {ARG_STRING_EMPTY,
                                                 "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 = execObtenirNoDerniereSecConnue(arguments[noCas]);
      nbErreurs += analyserResultatDatetimeDuTest(resultat,
                                                   resultatsAttendus[noCas],
                                                   nomObtenirNoDerniereSecConnue,
                                                   parametresCas[noCas]);
   }
   return(nbErreurs);
}

int execObtenirNbSecsDsPeriode(int argument) {
   int resultat;
   if(argument == ARG_INT_EMPTY) { resultat = obtenirNbSecsDsPeriode(); }
   else { resultat = obtenirNbSecsDsPeriode(argument); }
   return(resultat);
}

int testObtenirNbSecsDsPeriode() {
   int noCas, nbErreurs = 0;
   int resultat;
   int arguments[NB_CAS_NB_SECS_IN_PERIOD] = {ARG_INT_EMPTY, 5, 240, -2, 0};
   string parametresCas[NB_CAS_NB_SECS_IN_PERIOD] = {" sans paramètres => ",
                                                     " 5 => ",
                                                     " 240 => ",
                                                     " -2 => ",
                                                     " 0 => "};
   int resultatsAttendus[NB_CAS_NB_SECS_IN_PERIOD];
   resultatsAttendus[0] = 60 * Period();
   resultatsAttendus[1] = 300;
   resultatsAttendus[2] = 14400;
   resultatsAttendus[3] = -120;
   resultatsAttendus[4] = 0;
   for(noCas=0 ; noCas<NB_CAS_NB_SECS_IN_PERIOD ; noCas++) {
      resultat = execObtenirNbSecsDsPeriode(arguments[noCas]);
      nbErreurs += analyserResultatIntDuTest(resultat,
                                             resultatsAttendus[noCas],
                                             nomObtenirNbSecsDsPeriode,
                                             parametresCas[noCas]);
   }
   return(nbErreurs);
}

int execObtenirNoSecDsPeriode(datetime argumentHorodatage, int argumentNbSecsDsPeriode) {
   int resultat;
   if(argumentNbSecsDsPeriode == ARG_INT_EMPTY) {
      resultat = obtenirNoSecDsPeriode(argumentHorodatage); }
   else { resultat = obtenirNoSecDsPeriode(argumentHorodatage, argumentNbSecsDsPeriode); }
   return(resultat);
}

int testObtenirNoSecDsPeriode() {
   int noCas, nbErreurs = 0;
   int resultat;
   datetime argumentsHorodatages[NB_CAS_NO_SEC_IN_PERIOD] = {127,
                                                        123456789,
                                                        123456789,
                                                        123456789,
                                                        127};
   int argumentsNbSecsDsPeriode[NB_CAS_NO_SEC_IN_PERIOD] = {ARG_INT_EMPTY,
                                                                 ARG_INT_EMPTY,
                                                                 240,
                                                                 900,
                                                                 60};
   string parametresCas[NB_CAS_NO_SEC_IN_PERIOD] = {" 127, EMPTY => ",
                                                     " 123456789, EMPTY => ",
                                                     " 123456789, 240 => ",
                                                     " 123456789, 900 => ",
                                                     " 127, 60 => "};
   int resultatsAttendus[NB_CAS_NO_SEC_IN_PERIOD];
   resultatsAttendus[0] = 127 % (60 * Period());
   resultatsAttendus[1] = 123456789 % (60 * Period());
   resultatsAttendus[2] = 69;
   resultatsAttendus[3] = 189;
   resultatsAttendus[4] = 7;
   for(noCas=0 ; noCas<NO_CAS_NB_SEC_IN_PERIOD ; noCas++) {
      resultat = execObtenirNoSecsDsPeriode(argumentsHorodatages[noCas],
                                            argumentsNbSecsDsPeriode[noCas]);
      nbErreurs += analyserResultatIntDuTest(resultat,
                                             resultatsAttendus[noCas],
                                             nomObtenirNoSecDsPeriode,
                                             parametresCas[noCas]);
   }
   return(nbErreurs);
}

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

int init()
  {
   int nbErreurs = 0;
   nbErreurs += testObtenirNoDerniereSecConnue();
   nbErreurs += testObtenirNbSecsDsPeriode();
   nbErreurs += testObtenirNoSecDsPeriode();
   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);
  }

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


Ouf =D Ça commence à en faire du code ! C'est assez répétitif, à cause des limitations de MQL4: on fait ce qu'on peut ! Si quelqu'un voit une amélioration, qu'il la propose je suis preneur. Faites attention aux modifications à apporter à chaque copier coller de fonction. Il ne faut pas en oublier car le compilateur ne vous dira pas forcément qu'il y a un problème. Vous pouvez laissez par exemple un datetime sans le changer en int, et ça passe, le compilateur ne vous dit rien, mais vous aurez des bugs potentiels car il y a des valeurs qui changent en faisant les conversions d'un int à un datetime et inversement.

Aucun commentaire:

Enregistrer un commentaire