next up previous contents index PLPL moodlepserratamodulosperlmonksperldocapuntes LHPgoogleetsiiullpcgull
Sig: Repaso: Pruebas en el Sup: Pruebas para el Analizador Ant: Comprobando el Analizador Léxico Err: Si hallas una errata ...


Práctica: Pruebas en el Análisis Léxico

Extienda su compilador para modularizar el analizador léxico tal y como se explicó en la sección 4.4.
  1. Lea los siguientes documentos

  2. Incluya la estrategia de pruebas de no regresión explicada en las secciones previas. Dado que ahora la estructura del terminal es una estructura de datos mas compleja (token, [value, line_number]) no podrá usar is, ya que este último sólo comprueba la igualdad entre escalares. Use is_deeply para comprobar que la estructura de datos devuelta por el analizador léxico es igual a la esperada. Sigue un ejemplo:

    nereida:~/src/perl/YappWithDefaultAction/t> cat -n  15treeregswith2arrays.t
     1  #!/usr/bin/perl -w
     2  use strict;
     3  #use Test::More qw(no_plan);
     4  use Test::More tests => 3;
     5  use_ok qw(Parse::Eyapp) or exit;
    
    ..  ..... etc., etc.
    
    84  my $expected_tree = bless( {
    85    'children' => [
    86      bless( { 'children' => [
    87          bless( { 'children' => [], 'attr' => 'a', 'token' => 'a' }, 'TERMINAL' )
    88        ]
    89      }, 'A' ),
    90      bless( { 'children' => [
    91          bless( { 'children' => [], 'attr' => 'c', 'token' => 'c' }, 'TERMINAL' )
    92        ]
    93      }, 'C' )
    94    ]
    95  }, 'ABC' );
    96  is_deeply($t, $expected_tree, "deleting node between arrays");
    

  3. Extienda los tests con una prueba en la que la entrada contenga un carácter ilegal. Obsérve que, tal y como esta escrito la rutina scanner, si la entrada tiene un carácter ilegal se ejecutarán las líneas
    26     elsif (/\G\s*(.)/gc) {
    27       Error::fatal "Caracter invalido: $1\n";
    28     }
    
    lo que causa la parada del programa de prueba, al ejecutarse fatal el cuál llama a croak.
      sub fatal {
        my $msg = join " ", @_;
        croak("Error: $msg\n");
      }
    
    El objetivo es lograr que el programa de pruebas continúe ejecutando las subsiguientes pruebas.

    Para ello puede usar Test::Exception o bien eval y la variable especial $@ para controlar que el programa .t no termine prematuramente. Repase la sección [*] [4], el capítulo [*] y mas específicamente la sección [*] del capítulo sobre construcción de módulos.

  4. Pruebe a dar como entrada un fichero vacío
  5. Pruebe a dar como entrada un fichero que no existe
  6. Pruebe a dar como entrada un fichero binario
  7. Si tiene sentido en su caso, llame a las subrutinas con mas argumentos (y también con menos) de los que esperan.
  8. Si tiene sentido en su caso, llame a las subrutinas con argumentos cuyo tipo no es el que se espera.

    No use prototipos para lograrlo. No es una buena idea. Los prototipos en Perl a menudo producen un preprocesado del parámetro. Escriba código que controle que la naturaleza del parámetro es la que se espera. Por ejemplo:

    sub tutu {
      my $refhash = shift;
      croak "Error" unless UNIVERSAL::isa($refhash, 'HASH');
      ...
    }
    
  9. Cambie los argumentos de orden (si es que se aplica a su código)
  10. Comentarios: pruebe con /* * */ a = 4; /* * / */. Tambien con comentarios anidados (debería producirse un error)
  11. Flotantes: compruebe su expresión regular con 0.0 0e0 .0 0 1.e-5 1.0e2 -2.0 . (un punto sólo)
  12. Cadenas. Pruebe con las cadenas
    "" 
    "h\"a\"h" 
    "\""
    
    Pruebe también con una cadena con varias líneas y otra que contenga un carácter de control en su interior.
  13. Convierta los fallos (bugs) que encontró durante el desarrollo en pruebas
  14. Compruebe la documentación usando el módulo Test::Pod de Andy Lester. Instálelo si es necesario.

  15. Utilice el módulo Test::Warn para comprobar que los mensajes de warning (uso de warn and carp) se muestran correctamente.

  16. Una prueba SKIP declara un bloque de pruebas que - bajo ciertas circustancias - puede saltarse. Puede ser que sepamos que ciertas pruebas sólo funcionan en ciertos sistemas operativos o que la prueba requiera que ciertos paquetes están instalados o que la máquina disponga de ciertos recursos (por ejemplo, acceso a internet). En tal caso queremos que los tests se consideren si se dan las circustancias favorables pero que en otro caso se descarten sin protestas. Consulte la documentación de los módulos Test::More y Test::Harness sobre pruebas tipo SKIP. El ejemplo que sigue declara un bloque de pruebas que pueden saltarse. La llamada a skip indica cuantos tests hay, bajo que condición saltarselos.
     1  SKIP: {
     2      eval { require HTML::Lint };
     3 
     4      skip "HTML::Lint not installed", 2 if $@;
     5 
     6      my $lint = new HTML::Lint;
     7      isa_ok( $lint, "HTML::Lint" );
     8 
     9      $lint->parse( $html );
    10      is( $lint->errors, 0, "No errors found in HTML" );
    11  }
    
    Si el usuario no dispone del módulo HTML::Lint el bloque no será ejecutado. El módulo Test::More producirá oks que serán interpretados por Test::Harness como tests skipped pero ok.

    Otra razón para usar una prueba SKIP es disponer de la posibilidad de saltarse ciertos grupos de pruebas. Por ejemplo, aquellas que llevan demasiado tiempo de ejecución y no son tan significativas que no se pueda prescindir de ellas cuando se introducen pequeños cambios en el código. El siguiente código muestra como usando una variable de entorno TEST_FAST podemos controlar que pruebas se ejecutan.

    nereida:~/src/perl/YappWithDefaultAction/t> cat 02Cparser.t | head -n 56 -
    #!/usr/bin/perl -w
    use strict;
    #use Test::More qw(no_plan);
    use Test::More tests => 6;
    
    use_ok qw(Parse::Eyapp) or exit;
    
    SKIP: {
      skip "You decided to skip C grammar test (env var TEST_FAST)", 5 if $ENV{TEST_FAST} ;
      my ($grammar, $parser);
      $grammar=join('',<DATA>);
      $parser=new Parse::Eyapp(input => $grammar, inputfile => 'DATA', firstline => 52);
    
      #is($@, undef, "Grammar module created");
    
      # Does not work. May I have done s.t. wrong?
      #is(keys(%{$parser->{GRAMMAR}{NULLABLE}}), 43, "43 nullable productions");
    
      is(keys(%{$parser->{GRAMMAR}{NTERM}}), 233, "233 syntactic variables");
    
      is(scalar(@{$parser->{GRAMMAR}{UUTERM}}), 3, "3 UUTERM");
    
      is(scalar(keys(%{$parser->{GRAMMAR}{TERM}})), 108, "108 terminals");
    
      is(scalar(@{$parser->{GRAMMAR}{RULES}}), 825, "825 rules");
    
      is(scalar(@{$parser->{STATES}}), 1611, "1611 states");
    }
    
    __DATA__
    /*
       This grammar is a stripped form of the original C++ grammar
       from the GNU CC compiler :
    
       YACC parser for C++ syntax.
       Copyright (C) 1988, 89, 93-98, 1999 Free Software Foundation, Inc.
       Hacked by Michael Tiemann (tiemann@cygnus.com)
    
       The full gcc compiler an the original grammar file are freely
       available under the GPL license at :
    
       ftp://ftp.gnu.org/gnu/gcc/
       ...................... etc. etc.
    */
    nereida:~/src/perl/YappWithDefaultAction> echo $TEST_FAST
    1
    nereida:~/src/perl/YappWithDefaultAction> make test
    PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(0, 'blib/lib', 'blib/arch')" t/*.t
    t/01calc....................................ok
    t/02Cparser.................................ok
            5/6 skipped: various reasons
    t/03newgrammar..............................ok
    t/04foldandzero.............................ok
    t/05treewithvars............................ok
    t/06meta....................................ok
    t/07translationschemetype...................ok
    t/08tschemetypestar.........................ok
    t/09ts_with_defaultaction...................ok
    t/10ts_with_treereg.........................ok
    
    etc., etc...................................ok
    
    t/28unshifttwoitems.........................ok
    t/29foldinglistsofexpressions...............ok
    t/30complextreereg..........................ok
    t/32deletenodewithwarn......................ok
    t/33moveinvariantoutofloop..................ok
    t/34moveinvariantoutofloopcomplexformula....ok
    All tests successful, 5 subtests skipped.
    Files=33, Tests=113,  5 wallclock secs ( 4.52 cusr +  0.30 csys =  4.82 CPU)
    
    Introduzca una prueba SKIP similar a la anterior y otra que si el módulo Test::Pod esta instalado comprueba que la documentación esta bien escrita. Estudie la documentación del módulo Test::Pod.

  17. Introduzca pruebas TODO (que, por tanto, deben fallar) para las funciones que están por escribir (parser, Optimize, code_generator, transform). Repáse [*] [4]. Sigue un ejemplo:

    42 TODO: {
    43   local $TODO = "Randomly generated problem";
    44   can_ok('Algorithm::Knap01DP', 'GenKnap'); # sub GenKnap no ha sido escrita aún
    45 }
    

  18. Cuando compruebe el funcionamiento de su módulo nunca descarte que el error pueda estar en el código de la prueba. En palabras de Schwern

    Code has bugs. Tests are code. Ergo, tests have bugs.

    Michael Schwern

  19. Instale el módulo Devel::Cover. El módulo Devel::Cover ha sido escrito por Paul Johnson y proporciona estadísticas del cubrimiento alcanzado por una ejecución. Para usarlo siga estos pasos:
    pl@nereida:~/src/perl/YappWithDefaultAction$ cover -delete
    Deleting database /home/pl/src/perl/YappWithDefaultAction/cover_db
    pl@nereida:~/src/perl/YappWithDefaultAction$ HARNESS_PERL_SWITCHES=-MDevel::Cover make test
    PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(0, 'blib/lib', 'blib/arch')" t/*.t
    t/01calc....................................ok 
    t/01calc....................................ok
    t/02Cparser.................................ok
            5/6 skipped: various reasons
    t/03newgrammar..............................ok 
    t/03newgrammar..............................ok
    t/04foldandzero.............................ok
    etc., etc. .................................ok
    t/34moveinvariantoutofloopcomplexformula....ok
    All tests successful, 5 subtests skipped.
    Files=33, Tests=113, 181 wallclock secs (177.95 cusr +  2.94 csys = 180.89 CPU)
    
    La ejecución toma ahora mucho mas tiempo: ¡181 segundos frente a los 5 que toma la ejecución sin cover !. Al ejecutar cover de nuevo obtenemos una tabla con las estadísticas de cubrimiento:

    pl@nereida:~/src/perl/YappWithDefaultAction$ cover
    Reading database from /home/pl/src/perl/YappWithDefaultAction/cover_db
    
    ---------------------------- ------ ------ ------ ------ ------ ------ ------
    File                           stmt   bran   cond    sub    pod   time  total
    ---------------------------- ------ ------ ------ ------ ------ ------ ------
    blib/lib/Parse/Eyapp.pm       100.0    n/a    n/a  100.0    n/a    0.2  100.0
    ...lib/Parse/Eyapp/Driver.pm   72.4   63.2   50.0   64.3    0.0   21.3   64.4
    ...ib/Parse/Eyapp/Grammar.pm   90.9   77.8   66.7  100.0    0.0   16.6   84.3
    blib/lib/Parse/Eyapp/Lalr.pm   91.4   72.6   78.6  100.0    0.0   48.3   85.6
    blib/lib/Parse/Eyapp/Node.pm   74.4   58.3   29.2   88.2    0.0    1.6   64.7
    ...ib/Parse/Eyapp/Options.pm   86.4   50.0    n/a  100.0    0.0    2.7   72.8
    ...lib/Parse/Eyapp/Output.pm   82.3   47.4   60.0   70.6    0.0    3.7   70.0
    .../lib/Parse/Eyapp/Parse.pm  100.0    n/a    n/a  100.0    n/a    0.2  100.0
    ...Parse/Eyapp/Treeregexp.pm  100.0    n/a    n/a  100.0    n/a    0.1  100.0
    blib/lib/Parse/Eyapp/YATW.pm   89.4   63.9   66.7   85.7    0.0    4.8   77.6
    ...app/_TreeregexpSupport.pm   73.1   33.3   50.0  100.0    0.0    0.4   60.8
    main.pm                        52.2    0.0    n/a   80.0    0.0    0.0   45.7
    Total                          83.8   64.7   60.0   84.5    0.0  100.0   75.5
    ---------------------------- ------ ------ ------ ------ ------ ------ ------
    
    Writing HTML output to /home/pl/src/perl/YappWithDefaultAction/cover_db/coverage.html ...
    pl@nereida:~/src/perl/YappWithDefaultAction$
    
    El HTML generado nos permite tener una visión mas detallada de los niveles de cubrimiento.

    Para mejorar el cubrimiento de tu código comienza por el informe de cubrimiento de subrutinas. Cualquier subrutina marcada como no probada es un candidato a contener errores o incluso a ser código muerto.

    Para poder hacer el cubrimiento del código usando Devel::Cover, si se usa una csh o tcsh se debe escribir:

    nereida:~/src/perl/YappWithDefaultAction> setenv HARNESS_PERL_SWITCHES -MDevel::Cover
    nereida:~/src/perl/YappWithDefaultAction> make test
    PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness(0, 'blib/lib', 'blib/arch')" t/*.t
    t/01calc....................................ok 
    t/01calc....................................ok
    t/02Cparser.................................ok
            5/6 skipped: various reasons
    t/03newgrammar..............................ok 
    t/03newgrammar..............................ok
    t/04foldandzero.............................ok
    t/05treewithvars............................ok
    t/06meta....................................ok 
    t/06meta....................................ok
    t/07translationschemetype...................ok
    ............................................ok
    t/38tspostfix_resultisarray.................ok
    t/39tspostfix...............................ok 
    All tests successful, 5 subtests skipped.
    Files=38, Tests=135, 210 wallclock secs (206.28 cusr +  3.27 csys = 209.55 CPU)
    nereida:~/src/perl/YappWithDefaultAction>
    
    Aún mas robusto - más independiente de la shell que usemos - es pasar las opciones en HARNESS_PERL_SWITCHES como parámetro a make:
    make HARNESS_PERL_SWITCHES=-MDevel::Cover test
    

    Añade el informe de cubrimiento al MANIFEST para que se incluya en la distribución que subas. Si lo consideras conveniente añade un directorio informes en los que vayan los informes asociados a esta práctica. Incluye en el README o en la documentación una breve descripción de donde están los informes.

  20. Se conoce con el nombre de perfilado o profiling de un programa al estudio de su rendimiento mediante un programa (conocido como profiler) que monitoriza la ejecución del mismo mediante una técnica que interrumpe cada cierto tiempo el programa para comprobar en que punto de la ejecución se encuentra. Las estadísticas acumuladas se vuelcan al final de la ejecución en un fichero que puede ser visualizado mediante la aplicación apropiada.

    En Perl hay dos módulos que permiten realizar profiling. El mas antiguo es Devel::DProf . La aplicación para visualizar los resultados se llama dprofpp . Sigue un ejemplo de uso:

    nereida:~/src/perl/YappWithDefaultAction/t> perl -d:DProf 02Cparser.t
    1..6
    ok 1 - use Parse::Eyapp;
    ok 2 - 233 syntactic variables
    ok 3 - 3 UUTERM
    ok 4 - 108 terminals
    ok 5 - 825 rules
    ok 6 - 1611 states
    nereida:~/src/perl/YappWithDefaultAction/t> dprofpp tmon.out
    Total Elapsed Time = 3.028396 Seconds
      User+System Time = 3.008396 Seconds
    Exclusive Times
    %Time ExclSec CumulS #Calls sec/call Csec/c  Name
     31.4   0.945  1.473   1611   0.0006 0.0009  Parse::Eyapp::Lalr::_Transitions
     17.5   0.528  0.528   1611   0.0003 0.0003  Parse::Eyapp::Lalr::_Closures
     16.1   0.486  0.892      1   0.4861 0.8918  Parse::Eyapp::Lalr::_ComputeFollows
     8.04   0.242  0.391      1   0.2419 0.3906  Parse::Yapp::Driver::_Parse
     8.04   0.242  0.242  11111   0.0000 0.0000  Parse::Eyapp::Lalr::__ANON__
     4.59   0.138  0.138   8104   0.0000 0.0000  Parse::Eyapp::Lalr::_Preds
     2.66   0.080  0.080      1   0.0800 0.0800  Parse::Eyapp::Lalr::_SetDefaults
     2.66   0.080  0.972      1   0.0800 0.9718  Parse::Eyapp::Lalr::_ComputeLA
     2.46   0.074  0.074   3741   0.0000 0.0000  Parse::Eyapp::Parse::_Lexer
     1.89   0.057  0.074   8310   0.0000 0.0000  Parse::Eyapp::Parse::__ANON__
     0.96   0.029  0.028      1   0.0288 0.0276  Parse::Eyapp::Lalr::_SolveConflict
                                                 s
     0.66   0.020  0.050      6   0.0033 0.0083  Parse::Eyapp::Output::BEGIN
     0.60   0.018  1.500      1   0.0176 1.4997  Parse::Eyapp::Lalr::_LR0
     0.53   0.016  0.259      3   0.0054 0.0863  Parse::Eyapp::Lalr::_Digraph
     0.33   0.010  0.010      1   0.0100 0.0100  Parse::Eyapp::Grammar::_SetNullable
    

    Tambien es posible usar el módulo -MDevel::Profiler :

    nereida:~/src/perl/YappWithDefaultAction/examples> perl -MDevel::Profiler eyapp 02Cparser.yp
    Unused terminals:
    
            END_OF_LINE, declared line 128
            ALL, declared line 119
            PRE_PARSED_CLASS_DECL, declared line 120
    
    27 shift/reduce conflicts and 22 reduce/reduce conflicts
    nereida:~/src/perl/YappWithDefaultAction/examples> dprofpp tmon.out
    Total Elapsed Time = 3.914144 Seconds
      User+System Time = 3.917144 Seconds
    Exclusive Times
    %Time ExclSec CumulS #Calls sec/call Csec/c  Name
     22.3   0.877  1.577   1611   0.0005 0.0010  Parse::Eyapp::Lalr::_Transitions
     17.8   0.700  0.700   1611   0.0004 0.0004  Parse::Eyapp::Lalr::_Closures
     15.6   0.614  1.185      1   0.6142 1.1854  Parse::Eyapp::Lalr::_ComputeFollow
                                                 s
     9.60   0.376  0.545      1   0.3758 0.5453  Parse::Yapp::Driver::_Parse
     7.99   0.313  0.313   8104   0.0000 0.0000  Parse::Eyapp::Lalr::_Preds
     5.85   0.229  0.229      3   0.0763 0.0763  Parse::Eyapp::Lalr::_Digraph
     4.06   0.159  0.159   3741   0.0000 0.0000  Parse::Eyapp::Parse::_Lexer
     3.32   0.130  0.130      1   0.1300 0.1300  Parse::Eyapp::Lalr::DfaTable
     2.27   0.089  0.089      1   0.0890 0.0890  Parse::Eyapp::Lalr::_SetDefaults
     2.04   0.080  1.265      1   0.0800 1.2654  Parse::Eyapp::Lalr::_ComputeLA
     1.17   0.046  0.057      1   0.0464 0.0567  Parse::Eyapp::Grammar::Rules
     1.02   0.040  1.617      1   0.0397 1.6169  Parse::Eyapp::Lalr::_LR0
     0.77   0.030  0.030   1185   0.0000 0.0000  Parse::Eyapp::Lalr::_FirstSfx
     0.71   0.028  0.039      1   0.0284 0.0387  Parse::Eyapp::Grammar::RulesTable
     0.54   0.021  0.021   1650   0.0000 0.0000  Parse::Eyapp::Grammar::classname
    

    Presente un informe del perfil de su compilador. Añade el informe del perfil al MANIFEST para que se incluya en la distribución que subas.

  21. El módulo Devel::Size proporciona la posibilidad de conocer cuanto ocupa una estructura de datos. Considere el siguiente ejemplo:
     71 .................................... codigo omitido
     72
     73 use Devel::Size qw(size total_size);
     74 use Perl6::Form;
     75
     76 sub sizes {
     77   my $d = shift;
     78   my ($ps, $ts) = (size($d), total_size($d));
     79   my $ds = $ts-$ps;
     80   return ($ps, $ds, $ts);
     81 }
     82
     83 print form(
     84 ' ==============================================================',
     85 '| VARIABLE | SOLO ESTRUCTURA |     SOLO DATOS |          TOTAL |',
     86 '|----------+-----------------+----------------+----------------|',
     87 '| $parser  | {>>>>>>} bytes  | {>>>>>>} bytes | {>>>>>>} bytes |', sizes($parser),
     88 '| $t       | {>>>>>>} bytes  | {>>>>>>} bytes | {>>>>>>} bytes |', sizes($t),
     89 ' ==============================================================',
     90 );
    
    Al ejecutarlo se obtiene esta salida:

     ....... salida previa omitida
    
     ==============================================================
    | VARIABLE | SOLO ESTRUCTURA |     SOLO DATOS |          TOTAL |
    |----------+-----------------+----------------+----------------|
    | $parser  |      748 bytes  |      991 bytes |     1739 bytes |
    | $t       |       60 bytes  |     1237 bytes |     1297 bytes |
     ==============================================================
    
    Elabore un informe con el consumo de memoria de las variables mas importantes de su programa. Añadelo el informe al MANIFEST para que se incluya en la distribución que subas. Explica en el README o en la documentación el significado de los ficheros de informe.


next up previous contents index PLPL moodlepserratamodulosperlmonksperldocapuntes LHPgoogleetsiiullpcgull
Sig: Repaso: Pruebas en el Sup: Pruebas para el Analizador Ant: Comprobando el Analizador Léxico Err: Si hallas una errata ...
Casiano Rodríguez León
2012-05-22