aprendiendo ( Erlang ).

jueves, 29 de noviembre de 2012

0

Como ya os comenté el pasado día 27 de noviembre se celebro la 5ª Madrid Erlounge. Clara Benac nos comenta que aparte de la charla de Lars-Ake se hablo del curso online que ofrece Erlang Solutions, y que será impartido por Francesco Cesarini y Simon Thompson recientemente. Seguro que os suenan los nombres. Efectivamente, se trata de los coautores del libro Erlang Programming de O'Reilly. Sin lugar a dudas, se trata de un curso interesante.

También se hablo de la próxima Madrid Erlounge, que se celebrará en primavera. Para ella, se baraja la posibilidad de que Tuenti, realice una charla sobre cómo utilizan Erlang en la empresa. Otra charla que se propuso, para mi gusto muy, muy interesante, versará sobre sistemas multiagentes implementados en Erlang.

Os dejo para vuestro disfrute las transparencias de la charla. Que gracias a Lars-Ake y Clara Benac podemos disfrutar de ellas.

domingo, 25 de noviembre de 2012

1


Don Manuel Angel Rubio Jiménez, más conocido por su blog Bosque Viejo, ha anunciado la publicación de su libro, de factura propia, llamado Erlang/OTP: Volumen I, Un mundo concurrente. El libro, como anuncia en su propio blog, saldrá para principios de años.

Sin afán de lucro, el autor, ha decido publicar en dos formatos, el tradicional papel y en PDF. El formato PDF, será totalmente gratuito. En papel, aun no se ha determinado el precio, pero se asegura que será lo más ajustado posible.


Os dejo el índice del libro, para ir abriendo boca:

  • Prólogo de José Luis Gordo Romero
  • Introducción
  • Lo que debes saber sobre Erlang
  • El lenguaje
  • Expresiones, Estructuras y Excepciones
  • Las funciones y módulos
  • Procesos
  • ETS, DETS y Ficheros
  • Gestión de Ficheros
  • Comunicaciones
  • Ecosistema Erlang
  • Instalación de Erlang
  • La línea de comandos
  • Herramientas gráficas

Yo por mi parte ya estoy deseando ver su contenido. Espero que a vosotros también.

domingo, 18 de noviembre de 2012

2

Ya esta aquí. La 5ª Madrid Erlounge se celebrará el próximo día 27 de noviembre a las 19:30 horas en la Sala de Grados de la Facultad de Informática, Universidad Complutense de Madrid.

La charla la impartirá el señor Lars-Ake Fredlund. Su charla versará sobre Quviq QuickCheck, una herramienta de testings creada por la empresa Quiviq. Se trata de framework capaz de generar casos de pruebas a partir de una descripción formal del software puesto a pruebas. Sin lugar a dudas, una demostración de esta herramienta será cuanto menos interesante.

Os dejo, para ir abriendo boca, su intervención (Presentación) en la Erlang User Conference del 2009.

lunes, 29 de octubre de 2012

2

En un país muy, muy lejano. Vivía Verdi. Un chico de buena familia que decidió realizar un blog. A este blog lo nombro, digamos ..., Aprendiendo Erlang. Y como el bien decía a sus amigos "Aprendiendo Erlang surge como un proyecto ocasional y personal. Sin Pretensiones.".

Pero un buen día, en ese país muy, muy lejano, apareció un brujo que dijo a Verdi "Me parece bien, que sea un blog personal, y como tú dices, sin pretensiones" y continuo diciendo con voz aspera "Pero, ¿por qué es tan feo?". Esta última palabra retumbo en la cabeza de Verdi como un mazazo. Verdi se defendió torpemente diciendo "Si es feo y funcional. Cuando encuentre tiempo lo cambiaré.".

Así pasaron los días, las semanas y los meses, sin que Verdi encontrará tiempo para mejorarlo. Pero una buena mañana Verdi recordó las palabras de aquel brujo "feo, FEO, FEOOOOO" y se dijo "Pero que feo es el puñetero blog" y decidió cambiarlo.

Verdi se remango, cogió aire y se sumergió en lo más profundo de la laguna blogera. Y con los más fabulosos materiales realizo su nuevo blog. Para ello, utilizo el ladrillo más vistoso de la cantera HTML. Adorno los torreones CSS y JavaScript's. Y por último, mejoro su estandarte.

Y colorín colorado este remozado se ha acabado

¿Os gusta el nuevo estilo? ¿Qué cambiarías?



Verdi!gracias

sábado, 1 de septiembre de 2012

0

Ya vimos en su momento como realizar un control de errores en sistemas concurrentes. Es decir, el uso de link's y de trap_exit. Los procesos enlazados nos dota de un mecanismo por el cual, dos procesos A y B quedaban enlazados de tal forma que, si el proceso A moría enviaba una señal exit al proceso B y viceversa. Con dicho mecanismo evitábamos que hubieran procesos moribundo creados en nuestro sistema. Muere rápido para recuperarte lo antes posible.

Puede que no deseemos un control tan férreo como nos propone el sistema de procesos enlazados. A lo mejor, sólo necesitamos saber si esta vivo, o no, el proceso A para poder utilizarlo. A lo mejor, no nos supone ningún problema seguir trabajando sin el proceso A. En estos casos, lo que necesitaremos es un monitor. Un monitor es un tipo de enlace asimétrico, mientras que los enlaces (link's) son de carácter simétrico. Es decir, si el proceso M monitoriza al proceso A, el proceso M sabrá sobre el proceso A, pero no al revés, como pasa con los procesos enlazados.

Un ejemplo que ilustre claramente este comportamiento es el caso de servidores de apoyo. Su ponte que disponemos de un servidor central y otro de apoyo. El servidor central un día decide caerse. ¿No estaría bien que los clientes pudieran monitorizar al servidor? Es lo suyo, verdad. Para que en el caso de caídas del servidor central pasar automáticamente al servidor de respaldo. Un comportamiento muy deseable en muchos casos.

Pero para ilustrar el comportamiento de los monitores he decidido utilizar un ejemplo más modesto.

El paciente.

Todos hemos pasado por la desagradable experiencia de visitar una unidad de cuidados intensivos. Los pacientes en estos lugares están enganchados a unos monitores, o mejor dicho, los monitores están enganchados a los pacientes. Pues este, es el modelo elegido para ilustrar el uso de monitores. Empecemos por la implementación del paciente.

-module(paciente).
-export([start/0, reanimar/0, muerto/0, muerte_natural/0, asfixia/0, paro_cardiaco/0, mensaje/1]).

start () ->
    register(?MODULE, spawn(fun proceso/0)).

reanimar () ->
    start().

proceso() ->
    receive
        muerto ->
            io:format ("El paciente ha muerto. Hora de la muerte ~p ~n", [erlang:localtime()]),
            exit(normal);
        {alarma, Motivo} ->
            io:format ("Monitorizado alarma ~p~n", [Motivo]),
            exit(Motivo);
        Otro ->
            io:format("Recibido <~p> ~n", [Otro]), 
            proceso()
    end.

muerto() ->
    ?MODULE!muerto.

muerte_natural() ->
    ?MODULE!{alarma,muerte_natural}.

asfixia() ->
    ?MODULE!{alarma,asfixia}.

paro_cardiaco() ->
    ?MODULE!{alarma,paro_cardiaco}.

mensaje(M) ->
    ?MODULE!M.

Se trata de un proceso simple que puede morir o elevar una alarma. Como no dispongo de ningún paciente cerca para poder poner sensores, he decidido crear un interfaz que nos permita hacer que el paciente sufra alguna crisis. Hasta el momento nada nuevo bajo el sol.

Monitor

Ahora vamos con el monitor. El monitor se encarga de avisar, en caso de algún tipo de alarma o crisis del paciente. El enfermero, doctor o cualquier personal sanitario será el encargado de reiniciar el sistema en caso de recuperación por parte del paciente.

-module(monitor).

-compile(export_all).

start(Pid) ->
    spawn(?MODULE, monitor, [Pid]).

monitor(Pid) ->
    io:format("Monitorizando al paciente ~p. Constantes estables.~n", [Pid]),
    erlang:monitor(process,Pid),
    receive
        {'DOWN', Ref, process, Pid,  normal} ->
            io:format("~p dice que  ~p ha muerto por causas naturales~n",[Ref,Pid]);
        {'DOWN', Ref, process, Pid,  Reason} ->
            io:format("~p dice que  ~p ha muerto por ~p~n",[Ref,Pid,Reason]);
        Otro ->
            io:format("Monitor recibe <~p>~n", [Otro])
    end.

La función start/1 se encarga iniciar el monitor para el paciente que tenga un Pid dado.

Como puede observar, la función que se encarga de indicar al sistema que cree un monitor para un proceso (paciente) es erlang:monitor/2 (documentación). Inmediatamente después pasamos a esperar las posibles incidencias del paciente. El mensaje que podemos recibir tiene el formato {'DOWN', RefMonitor, process, PidMonitorizado, Motivo}.

1> c(paciente).
{ok,paciente}
2> c(monitor).
{ok,monitor}
3> paciente:start().
true
4> monitor:start(whereis(paciente)).
Monitorizando al paciente <0.44.0>. Constantes estables.
<0.46.0>
5> paciente:mensaje(hola).
Recibido <hola> 
hola
6> paciente:muerto().
El paciente ha muerto. Hora de la muerte {{2012,8,9},{19,2,37}} 
muerto
#Ref<0.0.0.109> dice que  <0.44.0> ha muerto por causas naturales

La implementación es sencilla, y el concepto de monitor es cuanto menos valioso. Pero este monitor es la gama más baja de este tipo de cacharros. Así que, un día el hospital decide renovar sus viejos monitores y compra un monitor-reanimador.

Reanimador.

La única diferencia con su predecesor es que le hemos dotado de la posibilidad de reanimar al paciente. En el caso de riesgo.

-module(reanimador).

-compile(export_all).

start() ->
    paciente:start(),
    spawn(?MODULE, monitor, [whereis(paciente)]).

monitor(Pid) ->
    io:format("Monitorizando al paciente ~p. Constantes estables.~n", [Pid]),
    erlang:monitor(process,Pid),
    receive
        {'DOWN', Ref, process, Pid,  normal} ->
            io:format("~p dice que  ~p ha fallecido~n",[Ref,Pid]);
        {'DOWN', Ref, process, Pid,  Reason} ->
            io:format("~p: Alarma. El paciente ~p esta padeciendo un/a  ~p~n",[Ref,Pid,Reason]),
            io:format("Reanimar al paciente. Vuelve ... vuelve ... vive~n"),
            paciente:reanimar (),
            monitor(whereis(paciente))
    end.

veamos como actúa el reanimador.

7> c(reanimador).
{ok,reanimador}
8> reanimador:start().
Monitorizando al paciente <0.39.0>. Constantes estables.
<0.40.0>
9> paciente:asfixia().
Monitorizado alarma asfixia
{alarma,asfixia}
#Ref<0.0.0.63>: Alarma. El paciente <0.39.0> esta padeciendo un/a  asfixia
Reanimar al paciente. Vuelve ... vuelve ... vive
Monitorizando al paciente <0.43.0>. Constantes estables.
10> paciente:paro_cardiaco().
Monitorizado alarma paro_cardiaco
{alarma,paro_cardiaco}
#Ref<0.0.0.72>: Alarma. El paciente <0.43.0> esta padeciendo un/a  paro_cardiaco
Reanimar al paciente. Vuelve ... vuelve ... vive
Monitorizando al paciente <0.45.0>. Constantes estables.
11> paciente:muerto().
El paciente ha muerto. Hora de la muerte {{2012,8,9},{19,46,22}} 
muerto
#Ref<0.0.0.80> dice que  <0.45.0> ha fallecido

Ahora, resuelta más difícil que el paciente fallezca. Otra propiedad que hasta ahora, no había mencionado es la posibilidad de apilar los monitores.

viernes, 10 de agosto de 2012

0

Siguiendo con las sugerencias y mejoras realizadas por nuestro amigo Manuel Rubio (Bombadil) en su página Bosque Viejo. Como te comenté … Ahora, seré yo quien tome prestado el gen_cmd ;þ.

Creando nuestro propio behaviour (comportamiento).

En su momento vimos como utilizar el gen_server de OTP y como dotar a nuestro módulo del comportamiento de un servidor genérico. Pues bien, la idea, es crear un comportamiento (behaviour) para que, podamos dotar de dicho comportamiento a otros módulos.

El comportamiento o Behaviour, es el mecanismo que nos proporciona Erlang para separar la funcionalidad de un comportamiento más o menos genérico. El módulo que implementa el comportamiento, establece sus requisitos e impone sus condiciones al módulo callback, que se encargará de la funcionalidad. Lo podríamos ver como un interfaz que debe implementar todo módulo que desee tener el comportamiento implementado.

Realmente, crear un behaviour es muy simple. Tremendamente simple. Tan sólo tenemos que implementar la función behaviour_info/1 y exportarla. Esta función recibe el átomo callbacks y retorna un lista de pares {función, aridad}. Como ya hemos comentado estas funciones son las que obligamos a implementar en el módulo callback. Veamos un ejemplo de implementación.

-module(simple_behaviour).

-export([start/2, behaviour_info/1]).

behaviour_info(callbacks) ->
    [{funcion1,1}, {funcion2, 2}];

behaviour_info(_) -> 
    undefined.

Nuestro módulo es un comportamiento, que declara que los módulos callbacks que lo quieran utilizar debe implementar la funciones funcion1/1 y funcion2/2. Ahora, veamos como debemos utilizarlo.

-module(simple_behaviour_callback).

-export([funcion1/1, funcion2/2]).

-behaviour(simple_behaviour).

funcion1 (_) ->
    void.

funcion2 (_,_) ->
    void.

Bien. Ahora tenemos un módulo que implementa el comportamiento simple. Como puedes ver la idea es bien sencilla. Y fácil de llevar a cabo.

De myniCommand a gen_cmd.

Si recordáis, en el artículo sobre REPL nos quedamos con un REPL básico que llamamos, con el simpático nombre de myniCommand. Este es su código.

-module(myniCommand).
-export([linea_comandos/2]).

linea_comandos(Prompt, Interprete) ->
    case io:get_line(standard_io, Prompt) of
        {error, Motivo} ->
            io:format(" error: ~p~n", [Motivo]),
            linea_comandos(Prompt, Interprete);
        {eof} -> 
            linea_comandos(Prompt, Interprete);
        Comando -> % operamos con el comando
            case Interprete(Comando) of
                exit ->
                    ok;
                _ ->
                    linea_comandos(Prompt, Interprete)
            end
    end.

Efectivamente, es una funcionalidad básica. Y como bien apuntó Manuel Rubio se puede encapsular en un Behaviour.

-module(gen_cmd).

-export([start/2, behaviour_info/1]).

behaviour_info(callbacks) ->
    [{handle_cmd,2}, {init, 1}];

behaviour_info(_) -> 
    undefined.

start(Prompt, Module) ->
    {ok, State} = Module:init([]),
    linea_comandos(Module, Prompt, State).

linea_comandos(Module, Prompt, State) ->
    case io:get_line(standard_io, Prompt) of
        {error, Motivo} ->
            io:format("error: ~p~n", [Motivo]),
            linea_comandos(Module, Prompt, State);
        {eof} -> 
            linea_comandos(Module, Prompt, State);
        Comando -> % operamos con el comando
            case Module:handle_cmd(Comando, State) of
                {stop, _Reason} ->
                    ok;
                {ok, NewState} ->
                    linea_comandos(Module, Prompt, NewState)
            end
    end.

No es tan simple como "el comportamiento simple", pero casi. Ahora, para poder implementar un REPL debemos implementar una función init/1, para inicializar un estado, y handle_cmd/2 para manejar el comando e imprimir el resultado.

Usando nuestro gen_cmd. Creando myErl.

Para ilustrar el funcionamiento del gen_cmd hemos implementado un pequeño interprete Erlang.

-module(myErl).
-export([init/1, handle_cmd/2, start/0]).

-behaviour(gen_cmd).

start() ->
    io:format("Bienvenido a myErl.~n"),
    gen_cmd:start("Cmd> ", ?MODULE),
    io:format("Adios.~n").

init([]) ->
    {ok, erl_eval:new_bindings()}.

handle_cmd ( "q\n", _State ) ->
    {stop, normal};
handle_cmd ( Comando, State ) ->
    {_State, Resultado} = eval (Comando, State),
    io:format("~p~n", [Resultado]),
    {ok, _State}.

eval ( Expresion, Bindings ) ->
    {ok,Scanned,_} = erl_scan:string(Expresion),
    {ok,Parsed} = erl_parse:parse_exprs(Scanned),
    {value, Resultado, _Bindings} = erl_eval:exprs(Parsed,Bindings),
    {add_bindings(Bindings, _Bindings), Resultado}.

add_bindings ( Bindings, [] ) ->
    Bindings;
add_bindings ( Bindings, [{Variable,Valor}|Resto] ) ->
    add_bindings(erl_eval:add_binding(Variable, Valor, Bindings), Resto).

Olvidemos por un momento la parte de evaluación y centrémonos en el comportamiento. Para empezar, podemos ver como hemos implementado las funciones callbacks impuesta desde nuestro gen_cmd. La función init/1, devuelve el par {ok, Estado}, y la función handle_cmd/2, como no podía ser de otra forma, se encarga de manejar e interpretar los comandos.

Ahora, que sabemos que, cumplimos el comportamiento gen_cmd pasamos a explicar lo que realiza este módulo. Para empezar, vamos ha definir el estado de nuestro REPL. El estado de nuestro REPL son los bindings que se han realizado en nuestro interprete. Los bindings son el conjunto de variables que hemos definido y sus correspondientes valores. Es decir, una lista de pares nombre-valor. Esta estructura se encarga de definirla la función erl_eval:new_bindings/0 del módulo erl_eval. Este módulo es el meta interprete de Erlang.

Pasamos ahora a evaluar el comando que recibimos. La función eval/2 es la encargada de interpretar el comando y devolver un nuevo estado y el resultado de la evaluación. Los pasos para interpretar un comando Erlang son:

  • Extraer los tokens de la cadena (erl_scan:string/1). Para más información erl_scan.
  • Parsear los tokens (erl_parse:parse_exprs/1). Para más información erl_parse.
  • Evaluar (erl_eval:exprs/2). Para más información erl_eval.

La función para evaluar una expresión nos devuelve el resultado y los nuevos bindings que debemos incorporar a nuestro estado anterior.

Ahora como es de rigor, realicemos una pequeña prueba.

1> c(gen_cmd).
{ok,gen_cmd}
2> c(myErl).
{ok,myErl}
3> myErl:start().
Bienvenido a myErl.
Cmd> A=2.
2
Cmd> A.
2
Cmd> B=[1,2,3].
[1,2,3]
Cmd> lists:max(B).
3
Cmd> F = fun (X) -> X + 1 end.
#Fun<erl_eval.6.80247286>
Cmd> lists:map(F, B).
[2,3,4]
Cmd> B.
[1,2,3]
Cmd> q
Adios.
ok

La verdad, es que para ser un interprete para andar por casa no esta nada mal. Ten en cuenta que no hemos realizado ninguna comprobación de errores a la hora de evaluar una expresión. Pero, con un poco más de esfuerzo estoy seguro que puedes mejorarlo y sacarle más partido. Yo, así lo voy hacer.

Y como puedes comprobar, en 0.3 un interprete Erlang.

miércoles, 18 de julio de 2012

0

El software adolece de un mal endémico, y es que, salvo excepciones, es complejo. Es por ello, que se hace necesario un sistema de pruebas que te permita verificar que el código realizado es correcto. En un nivel más básico, están las pruebas unitarias y es donde entra en juego la librería EUnit de Erlang.

Las pruebas unitarias son las que se realizan para verificar, de forma independiente, que un bloque de código, función y/o módulo funcionan correctamente.

La unidad básica de las pruebas unitarias es el caso de prueba. El caso de prueba es, en nuestro caso, una función, o conjunto de ellas, que comprueba la corrección de una funcionalidad concreta. Al conjunto de casos de pruebas se le suele llamar suite de pruebas.

Ventajas.

La principal ventaja de un sistema de pruebas unitarias es evitar o detectar la introducción de fallos en nuestra aplicación y/o librería. Supón que tienes una librería que pasa todas las pruebas, y que sabemos por experiencia que funciona bien. Pues llega un buen día que, decides mejorar el código y lo cambias. Lo normal es que, en algún momento, podamos introducir algún bug en nuestro código sin darnos cuenta. Es ahora donde es realmente útil un sistema de pruebas, ya que al intentar pasar la pruebas detectaremos el error introducido en el código.

Otro punto a su favor es que nos proporciona la respuesta a dos grandes preguntas ¿en qué falla? y ¿dónde falla?. Nuestro caso de prueba, en el momento de fallo, nos proporciona las respuestas a dichas preguntas. Preguntas que se me antojan muy importantes cuando el sistema esta en producción y no se le ha integrado ningún sistema de pruebas.

A todos nos pasa, que cuando realizamos un código, pasado un tiempo, se nos olvida realmente cual era su cometido. Ya sea por que no lo documentamos correctamente o bien, por que en aquel momento era obvia su funcionalidad pero … ahora no. Si en su momento nos preocupamos de integrarlo con un sistema de pruebas, este, nos puede servir de documentación y así saber, que realiza ese dichoso código que ahora no vemos.

Otra gran ventaja. Documentar los fallos. Si, ahora que tenemos nuestro sistema en producción sabes que funciona correctamente pero llega un buen día que … un bug!. Entonces lo corriges y … ¿otro bug? Y lo corriges y … otra vez el primer bug?. Esto no pasaría si en el momento de detectar el primer bug le hubiéramos realizado un caso de prueba que detectará dicho bug. Así de esta forma, no sólo corregimos y documentamos el fallo, sino que además, evitamos que vuelva a suceder.

Inconvenientes.

El principal inconveniente es que hay que realizar una gran cantidad de código extra. Trabajo que a veces no puedes, o no te apetece, realizar y que a la larga acabarás necesitando. Si, acabarás suplicando … cuando los fallos, aparezcan y reaparezcan … cuando donde debía haber … haya … cuando toques aquí y se rompa allá. No nos llevemos a engaño, el software que no se prueba como es debido con el tiempo se deteriora … se vuelve frágil … y en muchos casos inmanejable.

Las ventajas explicadas no son gratis y requiere de un esfuerzo inicial y continuado.

Otro inconveniente es que no es infalible. Como vimos los fallos se seguirán produciendo. No olvidemos que las pruebas las realizamos nosotros con estas manitas.

Las pruebas unitarias no son la panacea. No detectan errores de rendimiento o de integración por poner algunos ejemplos. Lo suyo es que acaben formando parte de un sistemas de pruebas que nos permita verificar otros aspectos del software. Pero en todo caso, debe o debería ser una parte integrante de nuestro que hacer diario.

Convenciones y alguna que otra cosa.

Lo primero que hay que tener claro es … ¿Dónde vamos a realizar los casos de prueba? Se pueden realizar en el mismo módulo que estamos implementando, pero para mi gusto emborronan el código, y prefiero implementarlos en un módulo aparte.

Si como yo, prefieres poner las pruebas o casos de prueba en un módulo aparte, este módulo tendrá el mismo nombre que el módulo original terminado en _tests. Por ejemplo, mi módulo prueba tendrá un módulo para las pruebas llamado prueba_tests.

Otra convención, son los nombres de los casos de prueba. Estos tienen que acabar en _test.

Con estas convenciones, cuando le digamos a EUnit que queremos probar el módulo prueba el sabrá automáticamente que puede tener un módulo llamado prueba_tests y que los casos de prueba son todas aquellas funciones que terminen en _test.

Mis primeras pruebas.

Una vez que tenemos claro algunas convenciones vamos a pasar al código, que es lo nuestro. Lo primero, implementar el módulo a probar. Los muy ortodoxos primero implementan las pruebas, pero para gustos colores.

-module(prueba).
-export([sumar/2, restar/2, multiplicar/2, dividir/2]).

sumar (A, B) ->
    A + B.

restar (A, B) ->
    A - B.

multiplicar (A, B) ->
    A * B.

dividir ( A, B ) ->
    A div B.

Ahora, el módulo de pruebas con los casos de prueba. Lo primero que tenemos que hacer es incluir el fichero cabecera del EUnit. Veamos como:

-module(prueba_tests).
-include_lib("eunit/include/eunit.hrl").

sumar_test () ->
    0 = prueba:sumar(0,0),
    1 = prueba:sumar(1,0),
    4 = prueba:sumar(2,2),
    5 = prueba:sumar(3,2).

restar_test () ->
    0 = prueba:restar(0,0),
    -1 = prueba:restar(0,1),
    1 = prueba:restar(4,3),
    0 = prueba:restar(4,4).

multiplicar_test () ->
    0 = prueba:multiplicar(0, 1),
    0 = prueba:multiplicar(1, 0),
    -2 = prueba:multiplicar(2, -1),
    4 = prueba:multiplicar(2, 2),
    8 = prueba:multiplicar(4, 2).

dividir_test () ->
    0 = prueba:dividir(0, 2),
    1 = prueba:dividir(8, 8),
    2 = prueba:dividir(8, 4),
    -2 = prueba:dividir (-8, 4).

Se trata de un módulo muy simple y por lo tanto, el módulo de pruebas también. Como observarás hemos implementado un caso de prueba por cada función implementada. Lo normal es que exista más de un caso de prueba por función implementada, en el módulo original. También es destacable que no es necesario exportar las funciones que implementemos.

1> c(prueba).
{ok,prueba}
2> c(prueba_tests).
{ok,prueba_tests}
3> eunit:test(prueba).
  All 4 tests passed.
ok

Compilamos y ejecutamos las pruebas de nuestro módulo prueba con una llamada a la función eunit:test/1. Fácil verdad. Pero esta mal. Si, si. Esta mal. Simplemente nos hemos limitado a hacer matching, que pasa si pretendo probar la división por cero ¿Cómo lo hacemos? ¿Cómo comprobamos excepciones? Para resolver este problemas tenemos las afirmaciones o las assertions.

Assertions.

Como medida intuitiva, para realizar pruebas sobre funciones que devuelvan excepciones, sería envolverlas en una sentencia try. Pero imagínate, lo largo que se puede volver, y sobre todo lo que emborronaría el código. Por este motivo, Eunit, nos proporciona un conjunto de macros que pretenden ayudarnos. Estas macros son assertions o afirmaciones.

  • ?assert(Expresión), ?assertNot(Expresión): Se utilizar para validar expresiones booleanas. Es decir, que nos verifica si una expresión dada es cierta o no.
  • ?assertEqual(A, B), ?assertNotEqual(A, B): Afirmación para igualdad (estricta) de A y B. Es decir, si es cierta la igualdad A=:=B.
  • ?assertMatch(Patrón, Expresión), ?assertNotMatch(Patrón, Expresión): Esta macro nos permite realizar afirmaciones de matching. Es decir, validar expresiones del tipo Patrón = Expresión.
  • ?assertError(Patrón, Expresión): Afirmación para la comprobación de errores. Donde el patrón se refiere al tipo de error.
  • ?assertThrow(Patrón, Expresión): No permite comprobar errores elevados con throw(Patrón).
  • ?assertExit(Patrón, Expresión): Comprueba salidas que fueron lanzadas con la función exit(Patrón).
  • ?assertException(Clase, Patrón, Expresión), assertNotException(Clase, Patrón, Expresión): Se trata de una versión genérica de las tres anteriores, donde clase puede ser error , throw o exit.

Ahora ya estamos en disposición de hacer los casos de prueba en condiciones.

Reimplementando los casos de prueba.

Veamos como quedarían nuestras pruebas utilizando los asserts.

-module(prueba_tests).
-include_lib("eunit/include/eunit.hrl").

sumar_test () ->
    ?assertEqual(0, prueba:sumar(0,0)),
    ?assertEqual(1, prueba:sumar(1,0)),
    ?assertMatch(4, prueba:sumar(2,2)),
    ?assert(is_number(prueba:sumar(3,2))).

restar_test () ->
    ?assertEqual(0, prueba:restar(0,0)),
    ?assertEqual(-1, prueba:restar(0,1)),
    ?assertEqual(1, prueba:restar(4,3)),
    ?assertEqual(0, prueba:restar(2,2)).

multiplicar_test () ->
    ?assertEqual(0, prueba:multiplicar(0, 1)),
    ?assertEqual(0, prueba:multiplicar(1, 0)),
    ?assertEqual(-2, prueba:multiplicar(2, -1)),
    ?assertEqual(4, prueba:multiplicar(2, 2)),
    ?assertEqual(8, prueba:multiplicar(4, 2)).

dividir_test () ->
    ?assertEqual(0, prueba:dividir(0, 2)),
    ?assertEqual(1, prueba:dividir(8, 8)),
    ?assertEqual(2, prueba:dividir(8, 4)),
    ?assertEqual(-2, prueba:dividir (-8, 4)),
    ?assertException(error, badarith, prueba:dividir(1, 0)),
    ?assertError(badarith, prueba:dividir(1, 0)).

He intentado diversificar uso de las afirmaciones para que el ejemplo sea más amplio. También he incluido el control de división por cero en el dividir_test/0.

1> c(prueba_tests).
{ok,prueba_tests}
2> eunit:test(prueba).
  All 4 tests passed.
ok

Fallo en las pruebas.

Hasta el momento no hemos visto como fallan las pruebas y que información nos proporciona EUnit. Vamos a crear un test para que falle y así ilustrar este comportamiento.

fallo_test () ->
    ?assertEqual(0, 3).

Esta claro que la afirmación que hemos realizado en el este caso de pruebas no es cierta. Es decir, que no podemos afirmar la igualdad de 0 y 3.

1> c(prueba_tests).
{ok,prueba_tests}
2> eunit:test(prueba).
prueba_tests: fallo_test...*failed*
::error:{assertEqual_failed,[{module,prueba_tests},
                           {line,39},
                           {expression,"3"},
                           {expected,0},
                           {value,3}]}
  in function prueba_tests:'-fallo_test/0-fun-0-'/1


=======================================================
  Failed: 1.  Skipped: 0.  Passed: 4.
error

La gran ventaja de los asserts. Es precisamente cuando falla el test. Ya que el assert nos proporciona una información fundamental para saber ¿dónde falla? y ¿qué falla?.

El fallo del caso de prueba nos esta indicando que ha fallado en el módulo prueba_tests en la línea 39. También nos indica que tenemos una expresión que evalúa al valor 3 y se esperaba un valor 0.

Como diría un amigo, Ahora … probad malditos … prodad…

 
Licencia Creative Commons
Aprendiendo Erlang por Verdi se encuentra bajo una Licencia Creative Commons Atribución-NoComercial-CompartirIgual 3.0 Unported.